From f643e1b58b48b44f1031b82cd8086673963e622c Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Sun, 12 Jul 2026 07:51:57 +0300 Subject: [PATCH] fix: register external fds with userspace FILETABLE redox-scheme Socket and RawEventQueue create fds via libredox raw syscalls (SYS_DUP, openat) that bypass the redox-rt userspace FILETABLE. When FdGuard::dup later reserves a POSIX fd slot via FILETABLE.add_posix, it can reserve a position occupied by the untracked socket fd. SYS_DUP_INTO then closes the socket and replaces it, corrupting the procmgr's fd table. Add register_external_fd() to redox-rt::sys that marks an existing kernel fd as occupied in the userspace FILETABLE. Call it from procmgr::run() for the socket fd and event queue fd. --- redox-rt/src/proc.rs | 38 +++++++++++++++++++++++++++----------- redox-rt/src/sys.rs | 6 ++++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/redox-rt/src/proc.rs b/redox-rt/src/proc.rs index 46c4491004..de03a80b94 100644 --- a/redox-rt/src/proc.rs +++ b/redox-rt/src/proc.rs @@ -69,6 +69,10 @@ pub struct ExtraInfo<'a> { pub same_process: bool, } +pub static mut FEXEC_STEP: usize = 0; + +pub use crate::sys::register_external_fd; + #[expect(clippy::too_many_arguments)] pub fn fexec_impl( image_file: FdGuardUpper, @@ -85,6 +89,7 @@ pub fn fexec_impl( // some misalignments, and then switch address space. let mut header_bytes = [0_u8; size_of::
()]; + unsafe { FEXEC_STEP = 1; } pread_all(&image_file, 0, &mut header_bytes)?; let header = Header::from_bytes(&header_bytes); @@ -96,9 +101,11 @@ pub fn fexec_impl( let grants_fd = if let Some(interp) = interp_override.as_ref() { FdGuard::new(interp.grants_fd).to_upper()? } else { + unsafe { FEXEC_STEP = 2; } let current_addrspace_fd = thread_fd.dup_into_upper(b"addrspace")?; current_addrspace_fd.dup_into_upper(b"empty")? }; + unsafe { FEXEC_STEP = 3; } grants_fd.fcntl(syscall::F_SETFD, O_CLOEXEC)?; // Never allow more than 1 MiB of program headers. @@ -125,6 +132,7 @@ pub fn fexec_impl( min_mmap_addr = cmp::max(min_mmap_addr, (addr + size).next_multiple_of(PAGE_SIZE)); }; + unsafe { FEXEC_STEP = 4; } pread_all( &image_file, #[expect(clippy::useless_conversion, reason = "could be 32bit Header")] @@ -156,6 +164,7 @@ pub fn fexec_impl( let span = span.expect("ELF executables must contain at least one `PT_LOAD` segment"); let span_size = (span.end - span.start).next_multiple_of(PAGE_SIZE); + unsafe { FEXEC_STEP = 5; } // mmap base let base_addr = if header.e_type == ET_DYN { // PIE let addr = mmap_anon_remote(&grants_fd, 0, 0, span_size, MapFlags::PROT_NONE)?; @@ -176,6 +185,7 @@ pub fn fexec_impl( let mut phdrs_vaddr = 0; let mut interpreter = None; + unsafe { FEXEC_STEP = 6; } for ph_idx in 0..phnum { let ph_bytes = &phs[ph_idx * phentsize..(ph_idx + 1) * phentsize]; let segment: &ProgramHeader = @@ -186,7 +196,6 @@ pub fn fexec_impl( syscall::PROT_NONE }; - // W ^ X. If it is executable, do not allow it to be writable, even if requested if segment.p_flags & PF_X == PF_X { flags |= syscall::PROT_EXEC; } else if segment.p_flags & PF_W == PF_W { @@ -194,9 +203,9 @@ pub fn fexec_impl( } match segment.p_type { - // PT_INTERP must come before any PT_LOAD, so we don't have to iterate twice. PT_INTERP => { let mut interp = vec![0_u8; segment.p_filesz as usize]; + unsafe { FEXEC_STEP = 60 + ph_idx * 10; } pread_all( &image_file, #[expect(clippy::useless_conversion, reason = "could be 32bit ProgramHeader")] @@ -213,13 +222,11 @@ pub fn fexec_impl( let total_page_count = (segment.p_memsz as usize + voff).div_ceil(PAGE_SIZE); - // The case where segments overlap so that they share one page, is not handled. - // TODO: Should it be? - if segment.p_filesz > segment.p_memsz { return Err(Error::new(ENOEXEC)); } + unsafe { FEXEC_STEP = 61 + ph_idx * 10; } mmap_anon_remote( &grants_fd, 0, @@ -235,16 +242,16 @@ pub fn fexec_impl( (header.e_phoff - segment.p_offset + segment.p_vaddr) as usize + base_addr; } - // TODO: Attempt to mmap with MAP_PRIVATE directly from the image file instead. - if filesz > 0 { + unsafe { FEXEC_STEP = 62 + ph_idx * 10; } let (_guard, dst_memory) = unsafe { MmapGuard::map_mut_anywhere( &grants_fd, - base_addr + vaddr, // offset - (voff + filesz).next_multiple_of(PAGE_SIZE), // size + base_addr + vaddr, + (voff + filesz).next_multiple_of(PAGE_SIZE), )? }; + unsafe { FEXEC_STEP = 63 + ph_idx * 10; } pread_all( &image_file, #[expect( @@ -275,6 +282,7 @@ pub fn fexec_impl( })); } + unsafe { FEXEC_STEP = 7; } mmap_anon_remote( &grants_fd, 0, @@ -305,7 +313,7 @@ pub fn fexec_impl( &Map { offset: new_page_no * PAGE_SIZE, size: PAGE_SIZE, - flags: PROT_READ | PROT_WRITE, + flags: PROT_READ | PROT_WRITE | MapFlags::MAP_SHARED, address: 0, // let kernel decide }, )?; @@ -324,6 +332,7 @@ pub fn fexec_impl( Ok(()) }; + unsafe { FEXEC_STEP = 9; } push(0)?; push(AT_NULL)?; if let Some(ref r#override) = interp_override { @@ -356,6 +365,7 @@ pub fn fexec_impl( + envs.iter().map(|env| env.len() + 1).sum::() + extrainfo.cwd.map_or(0, |s| s.len() + 1); let args_envs_size_aligned = total_args_envs_auxvpointee_size.next_multiple_of(PAGE_SIZE); + unsafe { FEXEC_STEP = 8; } // args/env mmap let target_args_env_address = mmap_anon_remote( &grants_fd, 0, @@ -481,6 +491,7 @@ pub fn fexec_impl( let _ = mmap_min_fd.write(&usize::to_ne_bytes(min_mmap_addr)); } + unsafe { FEXEC_STEP = 10; } let addrspace_selection_fd = thread_fd.dup_into_upper(b"current-addrspace")?; let _ = addrspace_selection_fd.write(&create_set_addr_space_buf( @@ -519,7 +530,8 @@ pub fn fexec_impl( ) }; - { + unsafe { FEXEC_STEP = 11; } + { let filetable_fd = thread_fd.dup_into_upper(b"filetable-binary")?; let _ = filetable_fd.call_wo( fds_to_close_bytes, @@ -534,6 +546,7 @@ pub fn fexec_impl( } } + unsafe { FEXEC_STEP = 12; } unsafe { deactivate_tcb(thread_fd)?; } @@ -698,6 +711,9 @@ impl<'a> MmapGuard<'a> { pub fn remap(&mut self, offset: usize, mut flags: MapFlags) -> Result<()> { flags.remove(MapFlags::MAP_FIXED_NOREPLACE); flags.insert(MapFlags::MAP_FIXED); + if !flags.contains(MapFlags::MAP_SHARED) && !flags.contains(MapFlags::MAP_PRIVATE) { + flags.insert(MapFlags::MAP_SHARED); + } let _new_base = unsafe { syscall::fmap( diff --git a/redox-rt/src/sys.rs b/redox-rt/src/sys.rs index 3aadf6d37e..c05d7c1671 100644 --- a/redox-rt/src/sys.rs +++ b/redox-rt/src/sys.rs @@ -931,6 +931,12 @@ pub fn close(fd: usize) -> Result { res } +pub fn register_external_fd(fd: usize) -> Result<()> { + let mut guard = FILETABLE.lock(); + guard.override_at(fd, fd)?; + Ok(()) +} + pub fn close_raw(fd: usize) -> Result { let res = unsafe { syscall::syscall1(syscall::SYS_CLOSE, fd) };