@@ -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) } ;
@@ -900,24 +906,7 @@ fn dup_fd_impl(state: &mut OpState, fd: i32) -> Result<ResourceId, FsError> {
900906 }
901907 let handle = raw_handle as RawHandle ;
902908
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- }
909+ deny_internal_resource_handle ( state, handle) ?;
921910
922911 // Duplicate the handle so it's independently owned and closeable.
923912 // SAFETY: handle is valid (checked above via get_osfhandle).
0 commit comments