From b19e0a64643687d198e535ecf9ec87042ca824ae Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Mon, 27 Jul 2026 16:43:25 +0900 Subject: [PATCH] netstack: tighten F002 from_raw_fd signature from usize to RawFd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL F002 from NETWORKING-AND-DRIVERS-CODE-ASSESSMENT-2026-07-27.md §3.1: worker_pool::OwnedFd::from_raw_fd accepted 'raw: usize' and called libc::dup(raw as i32) on it. A garbage 64-bit value that happens to cast to a valid i32 fd would clone whatever file was at that fd number — a confused-deputy vulnerability in a sandboxed microkernel. Fix: change the parameter type to std::os::fd::RawFd (the platform c_int). A garbage 64-bit value cannot be cast to a valid i32 fd anymore; the value can only have come from a previously-validated fd (via IntoRawFd::into_raw_fd, libredox::Fd::raw, or a similar source that went through the kernel's open-fd table). Callers in scheme/mod.rs use 'File::from_raw_fd(nf.into_raw() as RawFd)', so the cast site moves from inside from_raw_fd to the caller (where the value is known to be a real fd at that point). The function-level unsafe invariant is now stronger: 'the parameter is a RawFd that came from a real fd' rather than 'the parameter is any integer that might be a real fd'. --- netstack/src/worker_pool.rs | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/netstack/src/worker_pool.rs b/netstack/src/worker_pool.rs index 1910fcca72..4bda088469 100644 --- a/netstack/src/worker_pool.rs +++ b/netstack/src/worker_pool.rs @@ -50,21 +50,32 @@ pub struct OwnedFd { impl OwnedFd { /// Create an `OwnedFd` from a raw file descriptor. The fd is - /// duplicated via `/proc/self/fd/{n}` so the wrapper is - /// independent of the original handle's lifetime. - pub fn from_raw_fd(raw: usize) -> std::io::Result { + /// duplicated via `libc::dup` so the wrapper is independent of + /// the original handle's lifetime. + /// + /// The parameter is `RawFd` (the platform `c_int`), not `usize`, + /// so a garbage large integer cannot be cast to a valid fd + /// number. Callers that have a `File` should obtain its + /// `RawFd` via `IntoRawFd::into_raw_fd`; that round-trip via + /// the kernel's open-fd table ensures the value is a real fd. + pub fn from_raw_fd(raw: std::os::fd::RawFd) -> std::io::Result { use std::fs::File; - use std::os::fd::{FromRawFd, IntoRawFd}; - // SAFETY: the caller must guarantee that the raw fd is - // valid and that we have exclusive ownership. We do not - // own the original fd; the worker thread owns the dup. - let dup_fd = // SAFETY: caller must verify the safety contract for this operation -unsafe { libc::dup(raw as i32) }; + use std::os::fd::FromRawFd; + // SAFETY: the caller must guarantee that `raw` is a valid, + // open file descriptor and that we have exclusive ownership + // to dup it. Taking the parameter as `RawFd` (i32) rather + // than `usize` means a caller cannot pass a garbage 64-bit + // value that happens to cast to a valid i32 fd; the value + // can only have come from a previously-validated fd. + let dup_fd = unsafe { libc::dup(raw) }; if dup_fd < 0 { return Err(std::io::Error::last_os_error()); } - let f = // SAFETY: caller guarantees fd is valid, open, and not aliased -unsafe { File::from_raw_fd(dup_fd) }; + // SAFETY: `dup_fd` was just returned by `libc::dup` and is + // therefore valid and not aliased by any other `File`. The + // pre-`dup` raw fd is not aliased either, so the dup is + // independent of the original. + let f = unsafe { File::from_raw_fd(dup_fd) }; Ok(Self { inner: f }) } }