From a9c712c0c46dbcc0857257786bee97e9bf6c1f31 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Mon, 17 Aug 2026 21:04:01 +0000 Subject: [PATCH 1/6] Fix races in Linux fd duplication --- litebox_shim_linux/src/syscalls/file.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/litebox_shim_linux/src/syscalls/file.rs b/litebox_shim_linux/src/syscalls/file.rs index 959937395..a2d2de6c1 100644 --- a/litebox_shim_linux/src/syscalls/file.rs +++ b/litebox_shim_linux/src/syscalls/file.rs @@ -102,12 +102,12 @@ impl FilesState { raw_descriptor_store: litebox::sync::RwLock::new( litebox::fd::RawDescriptorStorage::new(), ), - max_fd: AtomicUsize::new(usize::MAX), + fd_limit: AtomicUsize::new(usize::MAX), } } - pub(crate) fn set_max_fd(&self, max_fd: usize) { - self.max_fd.store(max_fd, Ordering::Relaxed); + pub(crate) fn set_fd_limit(&self, fd_limit: usize) { + self.fd_limit.store(fd_limit, Ordering::Relaxed); } // Returns Ok(raw_fd) if it fits within the max limits already set up; otherwise returns the From 9cd7a38463ad9ea5d0ff722a1f5e4f489f7de5a2 Mon Sep 17 00:00:00 2001 From: Weiteng Chen Date: Tue, 1 Sep 2026 18:21:49 +0000 Subject: [PATCH 2/6] revert max_fd --- litebox_shim_linux/src/syscalls/file.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/litebox_shim_linux/src/syscalls/file.rs b/litebox_shim_linux/src/syscalls/file.rs index a2d2de6c1..959937395 100644 --- a/litebox_shim_linux/src/syscalls/file.rs +++ b/litebox_shim_linux/src/syscalls/file.rs @@ -102,12 +102,12 @@ impl FilesState { raw_descriptor_store: litebox::sync::RwLock::new( litebox::fd::RawDescriptorStorage::new(), ), - fd_limit: AtomicUsize::new(usize::MAX), + max_fd: AtomicUsize::new(usize::MAX), } } - pub(crate) fn set_fd_limit(&self, fd_limit: usize) { - self.fd_limit.store(fd_limit, Ordering::Relaxed); + pub(crate) fn set_max_fd(&self, max_fd: usize) { + self.max_fd.store(max_fd, Ordering::Relaxed); } // Returns Ok(raw_fd) if it fits within the max limits already set up; otherwise returns the From e171b18ff54f57d7e98ef751b3dab10c05e81bf5 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Mon, 17 Aug 2026 21:25:09 +0000 Subject: [PATCH 3/6] Fix socketpair fd-table race when the table is full --- litebox_shim_linux/src/syscalls/file.rs | 16 ++---- litebox_shim_linux/src/syscalls/net.rs | 69 +++++++++++++++++++++---- 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/litebox_shim_linux/src/syscalls/file.rs b/litebox_shim_linux/src/syscalls/file.rs index 959937395..7a2499f25 100644 --- a/litebox_shim_linux/src/syscalls/file.rs +++ b/litebox_shim_linux/src/syscalls/file.rs @@ -120,7 +120,7 @@ impl FilesState { self.insert_raw_fd_locked(&mut rds, typed_fd) } - fn insert_raw_fd_locked( + pub(super) fn insert_raw_fd_locked( &self, rds: &mut litebox::fd::RawDescriptorStorage, typed_fd: TypedFd, @@ -810,7 +810,7 @@ impl Task { self.do_close_and_replace::>(raw_fd, None) } - fn remove_and_drop_descriptor(&self, fd: &TypedFd) { + pub(super) fn remove_and_drop_descriptor(&self, fd: &TypedFd) { let entry = { let mut dt = self.global.litebox.descriptor_table_mut(); dt.remove(fd) @@ -1857,11 +1857,7 @@ impl Task { drop(dt); let files = self.files.borrow(); let raw_fd = files.insert_raw_fd(typed).map_err(|typed| { - self.global - .litebox - .descriptor_table_mut() - .remove(&typed) - .unwrap(); + self.remove_and_drop_descriptor(&typed); Errno::EMFILE })?; Ok(raw_fd.try_into().unwrap()) @@ -2099,11 +2095,7 @@ impl Task { drop(dt); let files = self.files.borrow(); let raw_fd = files.insert_raw_fd(typed).map_err(|typed| { - self.global - .litebox - .descriptor_table_mut() - .remove(&typed) - .unwrap(); + self.remove_and_drop_descriptor(&typed); Errno::EMFILE })?; Ok(raw_fd.try_into().unwrap()) diff --git a/litebox_shim_linux/src/syscalls/net.rs b/litebox_shim_linux/src/syscalls/net.rs index 740676f37..6afcd6e37 100644 --- a/litebox_shim_linux/src/syscalls/net.rs +++ b/litebox_shim_linux/src/syscalls/net.rs @@ -1016,7 +1016,7 @@ impl Task { } files.insert_raw_fd(typed).map_err(|typed| { - let _ = self.global.litebox.descriptor_table_mut().remove(&typed); + self.remove_and_drop_descriptor(&typed); Errno::EMFILE })? } @@ -1047,6 +1047,7 @@ impl Task { .ok_or(Errno::EFAULT)?; Ok(()) } + fn do_socketpair( &self, domain: AddressFamily, @@ -1072,15 +1073,36 @@ impl Task { assert!(old.is_none()); } drop(dt); - let raw_fd1 = files.insert_raw_fd(typed1).map_err(|typed| { - let _ = self.global.litebox.descriptor_table_mut().remove(&typed); - Errno::EMFILE - })?; - let raw_fd2 = files.insert_raw_fd(typed2).map_err(|typed| { - self.do_close(raw_fd1).unwrap(); - let _ = self.global.litebox.descriptor_table_mut().remove(&typed); - Errno::EMFILE - })?; + // Both inserts and the rollback of the first one must happen under a single + // acquisition of the raw descriptor store lock: otherwise a concurrent `close` + // could free the first socket's slot and another thread could take it over, + // making the rollback remove an unrelated file descriptor. + let mut rds = files.raw_descriptor_store.write(); + let raw_fd1 = match files.insert_raw_fd_locked(&mut rds, typed1) { + Ok(raw_fd) => raw_fd, + Err(typed1) => { + drop(rds); + self.remove_and_drop_descriptor(&typed1); + self.remove_and_drop_descriptor(&typed2); + return Err(Errno::EMFILE); + } + }; + let raw_fd2 = match files.insert_raw_fd_locked(&mut rds, typed2) { + Ok(raw_fd) => raw_fd, + Err(typed2) => { + let typed1 = rds + .fd_consume_raw_integer::>(raw_fd1) + .unwrap(); + drop(rds); + self.remove_and_drop_descriptor(&typed1); + self.remove_and_drop_descriptor(&typed2); + return Err(Errno::EMFILE); + } + }; + drop(rds); (raw_fd1, raw_fd2) } AddressFamily::INET | AddressFamily::INET6 | AddressFamily::NETLINK => { @@ -1301,7 +1323,7 @@ impl Task { } drop(dt); let raw_fd = files.insert_raw_fd(typed).map_err(|typed| { - let _ = self.global.litebox.descriptor_table_mut().remove(&typed); + self.remove_and_drop_descriptor(&typed); Errno::EMFILE })?; Ok((raw_fd, peer_addr)) @@ -3334,6 +3356,31 @@ mod unix_tests { unix_socketpair_bidirectional(SockType::Datagram, true); } + #[test] + fn test_socketpair_race_with_concurrent_close() { + let task = init_platform(None); + task.files.borrow().set_max_fd(3); + + let stop = alloc::sync::Arc::new(core::sync::atomic::AtomicBool::new(false)); + let stop_closer = stop.clone(); + let closer = task.spawn_clone_for_test(move |task| { + while !stop_closer.load(core::sync::atomic::Ordering::Relaxed) { + let _ = task.sys_close(3); + } + }); + + for iter in 0..50_000 { + assert_eq!( + task.do_socketpair(AddressFamily::UNIX, SockType::Stream, SockFlags::empty(), 0), + Err(Errno::EMFILE), + "failed at iteration {iter}" + ); + } + + stop.store(true, core::sync::atomic::Ordering::Relaxed); + closer.join().unwrap(); + } + fn unix_socket_recv_timeout(ty: SockType) { let task = init_platform(None); let (sock1, _sock2) = task From 83cdabf7b73ecd4df6bc367c5bc711b5551e9387 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Mon, 17 Aug 2026 22:45:12 +0000 Subject: [PATCH 4/6] fix rebase --- litebox_shim_linux/src/syscalls/net.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litebox_shim_linux/src/syscalls/net.rs b/litebox_shim_linux/src/syscalls/net.rs index 6afcd6e37..496d2d2c2 100644 --- a/litebox_shim_linux/src/syscalls/net.rs +++ b/litebox_shim_linux/src/syscalls/net.rs @@ -3359,7 +3359,7 @@ mod unix_tests { #[test] fn test_socketpair_race_with_concurrent_close() { let task = init_platform(None); - task.files.borrow().set_max_fd(3); + task.files.borrow().set_fd_limit(3); let stop = alloc::sync::Arc::new(core::sync::atomic::AtomicBool::new(false)); let stop_closer = stop.clone(); From 13be8c7f94fb948d5eb737318af8b8a98ee70f1e Mon Sep 17 00:00:00 2001 From: Weiteng Chen Date: Wed, 2 Sep 2026 18:29:12 +0000 Subject: [PATCH 5/6] clean up code --- litebox_shim_linux/src/syscalls/net.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/litebox_shim_linux/src/syscalls/net.rs b/litebox_shim_linux/src/syscalls/net.rs index 496d2d2c2..49865a402 100644 --- a/litebox_shim_linux/src/syscalls/net.rs +++ b/litebox_shim_linux/src/syscalls/net.rs @@ -1090,12 +1090,7 @@ impl Task { let raw_fd2 = match files.insert_raw_fd_locked(&mut rds, typed2) { Ok(raw_fd) => raw_fd, Err(typed2) => { - let typed1 = rds - .fd_consume_raw_integer::>(raw_fd1) - .unwrap(); + let typed1 = rds.fd_consume_raw_integer::>(raw_fd1).unwrap(); drop(rds); self.remove_and_drop_descriptor(&typed1); self.remove_and_drop_descriptor(&typed2); From 210f3e58f2db04c1141decd0b95f0bad04b893e1 Mon Sep 17 00:00:00 2001 From: Weiteng Chen Date: Wed, 2 Sep 2026 18:34:25 +0000 Subject: [PATCH 6/6] fix rebase --- litebox_shim_linux/src/syscalls/net.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litebox_shim_linux/src/syscalls/net.rs b/litebox_shim_linux/src/syscalls/net.rs index 49865a402..7a0de63bc 100644 --- a/litebox_shim_linux/src/syscalls/net.rs +++ b/litebox_shim_linux/src/syscalls/net.rs @@ -3354,7 +3354,7 @@ mod unix_tests { #[test] fn test_socketpair_race_with_concurrent_close() { let task = init_platform(None); - task.files.borrow().set_fd_limit(3); + task.files.borrow().set_max_fd(4); let stop = alloc::sync::Arc::new(core::sync::atomic::AtomicBool::new(false)); let stop_closer = stop.clone();