netstack: tighten F002 from_raw_fd signature from usize to RawFd
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'.
This commit is contained in:
+22
-11
@@ -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<Self> {
|
||||
/// 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<Self> {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user