Skip to content

Commit 1e73ec6

Browse files
committed
clean up
1 parent 8e1ebc7 commit 1e73ec6

File tree

1 file changed

+72
-71
lines changed

1 file changed

+72
-71
lines changed

ext/node/ops/fs.rs

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -760,11 +760,36 @@ pub async fn op_node_rmdir(
760760
Ok(())
761761
}
762762

763+
/// Check if a handle is managed by a non-fsFile internal Deno resource.
764+
/// fsFile and signal resources are allowed to be dup'd (for node:fs and
765+
/// native addons like node-pty). Other managed resources (SQLite, internal
766+
/// pipes) are denied. Unmanaged handles are allowed through.
767+
fn deny_internal_resource_handle(
768+
state: &mut OpState,
769+
handle: deno_core::ResourceHandleFd,
770+
) -> Result<(), FsError> {
771+
for (rid, name) in state.resource_table.names() {
772+
if name == "fsFile" || name == "signal" {
773+
continue;
774+
}
775+
if matches!(
776+
state.resource_table.get_handle(rid),
777+
Ok(deno_core::ResourceHandle::Fd(existing)) if existing == handle
778+
) {
779+
return Err(FsError::Io(std::io::Error::new(
780+
std::io::ErrorKind::PermissionDenied,
781+
"File descriptor is managed by an internal Deno resource",
782+
)));
783+
}
784+
}
785+
Ok(())
786+
}
787+
763788
/// Create a file resource from a raw file descriptor by dup'ing it first.
764789
/// This is safe for cross-worker use because the dup'd fd is independently
765790
/// owned and can be closed without affecting the original.
766791
///
767-
/// Safety: allows dup of fds that are either unmanaged (external: child_process
792+
/// Allows dup of fds that are either unmanaged (external: child_process
768793
/// pipes, native addons) or owned by an "fsFile" resource (opened via node:fs).
769794
/// Denies dup of fds managed by non-fsFile internal Deno resources (e.g.
770795
/// SQLite, internal pipes) to prevent unauthorized access.
@@ -790,26 +815,7 @@ fn dup_fd_impl(state: &mut OpState, fd: i32) -> Result<ResourceId, FsError> {
790815
)));
791816
}
792817

793-
// Check if this fd is managed by a non-fsFile internal Deno resource.
794-
// fsFile resources (opened via node:fs) are safe to dup.
795-
// Other managed resources (SQLite, internal pipes) are denied.
796-
// Unmanaged fds (child_process, native addons) are allowed through.
797-
for (rid, name) in state.resource_table.names() {
798-
if name == "fsFile" || name == "signal" {
799-
// node:fs and node:signal resources are allowed to be dup'd for use in
800-
// native addons like node-pty
801-
continue;
802-
}
803-
if matches!(
804-
state.resource_table.get_handle(rid),
805-
Ok(deno_core::ResourceHandle::Fd(existing_fd)) if existing_fd == fd
806-
) {
807-
return Err(FsError::Io(std::io::Error::new(
808-
std::io::ErrorKind::PermissionDenied,
809-
"File descriptor is managed by an internal Deno resource",
810-
)));
811-
}
812-
}
818+
deny_internal_resource_handle(state, fd)?;
813819

814820
// SAFETY: dup() creates a new fd pointing to the same open file description.
815821
let new_fd = unsafe { libc::dup(fd) };
@@ -850,6 +856,48 @@ pub fn op_node_dup_fd(
850856
dup_fd_impl(state, fd)
851857
}
852858

859+
/// Like `libc::get_osfhandle` but safe to call with invalid fds.
860+
///
861+
/// On Windows, CRT functions invoke the "invalid parameter handler" when
862+
/// given a bad fd, which by default terminates the process. We temporarily
863+
/// install a no-op handler so `get_osfhandle` returns -1 instead of
864+
/// aborting. This is the same pattern libuv uses (`uv__get_osfhandle`).
865+
///
866+
/// Returns the raw HANDLE as `isize`, or -1 / -2 for invalid fds.
867+
#[cfg(not(unix))]
868+
fn safe_get_osfhandle(fd: i32) -> isize {
869+
extern "C" fn silent_invalid_parameter_handler(
870+
_: *const u16,
871+
_: *const u16,
872+
_: *const u16,
873+
_: u32,
874+
_: usize,
875+
) {
876+
}
877+
878+
unsafe extern "C" {
879+
fn _set_thread_local_invalid_parameter_handler(
880+
handler: Option<
881+
unsafe extern "C" fn(*const u16, *const u16, *const u16, u32, usize),
882+
>,
883+
) -> Option<
884+
unsafe extern "C" fn(*const u16, *const u16, *const u16, u32, usize),
885+
>;
886+
}
887+
888+
// SAFETY: _set_thread_local_invalid_parameter_handler and
889+
// get_osfhandle are standard CRT calls. The no-op handler is
890+
// thread-local and immediately restored after the call.
891+
unsafe {
892+
let prev = _set_thread_local_invalid_parameter_handler(Some(
893+
silent_invalid_parameter_handler,
894+
));
895+
let handle = libc::get_osfhandle(fd);
896+
_set_thread_local_invalid_parameter_handler(prev);
897+
handle
898+
}
899+
}
900+
853901
#[cfg(not(unix))]
854902
fn dup_fd_impl(state: &mut OpState, fd: i32) -> Result<ResourceId, FsError> {
855903
use std::fs::File as StdFile;
@@ -863,64 +911,17 @@ fn dup_fd_impl(state: &mut OpState, fd: i32) -> Result<ResourceId, FsError> {
863911
)));
864912
}
865913

866-
// Convert CRT fd to Windows HANDLE.
867-
// get_osfhandle invokes the CRT invalid parameter handler for invalid
868-
// fds, which by default terminates the process. We install a no-op
869-
// handler (like libuv does) so it returns -1 instead.
870-
// SAFETY: _set_thread_local_invalid_parameter_handler and
871-
// get_osfhandle are safe CRT calls. The no-op handler prevents
872-
// process termination for invalid fds.
873-
let raw_handle = unsafe {
874-
extern "C" fn silent_handler(
875-
_: *const u16,
876-
_: *const u16,
877-
_: *const u16,
878-
_: u32,
879-
_: usize,
880-
) {
881-
}
882-
unsafe extern "C" {
883-
fn _set_thread_local_invalid_parameter_handler(
884-
handler: Option<
885-
unsafe extern "C" fn(*const u16, *const u16, *const u16, u32, usize),
886-
>,
887-
) -> Option<
888-
unsafe extern "C" fn(*const u16, *const u16, *const u16, u32, usize),
889-
>;
890-
}
891-
let prev =
892-
_set_thread_local_invalid_parameter_handler(Some(silent_handler));
893-
let handle = libc::get_osfhandle(fd);
894-
_set_thread_local_invalid_parameter_handler(prev);
895-
handle
896-
};
914+
let raw_handle = safe_get_osfhandle(fd);
897915
if raw_handle == -1 || raw_handle == -2 {
898916
// ERROR_INVALID_HANDLE (6) maps to EBADF via uvTranslateSysError
899917
return Err(FsError::Io(std::io::Error::from_raw_os_error(6)));
900918
}
901919
let handle = raw_handle as RawHandle;
902920

903-
// Check if this handle is managed by a non-fsFile internal Deno resource.
904-
// fsFile resources (opened via node:fs) are safe to dup.
905-
// Other managed resources (SQLite, internal pipes) are denied.
906-
// Unmanaged fds (child_process, native addons) are allowed through.
907-
for (rid, name) in state.resource_table.names() {
908-
if name == "fsFile" || name == "signal" {
909-
continue;
910-
}
911-
if matches!(
912-
state.resource_table.get_handle(rid),
913-
Ok(deno_core::ResourceHandle::Fd(existing_handle)) if existing_handle == handle
914-
) {
915-
return Err(FsError::Io(std::io::Error::new(
916-
std::io::ErrorKind::PermissionDenied,
917-
"File descriptor is managed by an internal Deno resource",
918-
)));
919-
}
920-
}
921+
deny_internal_resource_handle(state, handle)?;
921922

922923
// Duplicate the handle so it's independently owned and closeable.
923-
// SAFETY: handle is valid (checked above via get_osfhandle).
924+
// SAFETY: handle is valid (checked above via safe_get_osfhandle).
924925
let borrowed = unsafe { BorrowedHandle::borrow_raw(handle) };
925926
let owned = borrowed.try_clone_to_owned().map_err(FsError::Io)?;
926927

0 commit comments

Comments
 (0)