Files
Sprimo/skills/unsafe-checker/rules/io-01-raw-handle.md
2026-02-12 22:58:33 +08:00

3.7 KiB

id, original_id, level, impact
id original_id level impact
io-01 P.UNS.FIO.01 P HIGH

Ensure I/O Safety When Using Raw Handles

Summary

When working with raw file descriptors or handles, ensure they are valid for the duration of use and properly ownership-tracked.

Rationale

  • Raw handles can be closed by other code
  • Using a closed handle is undefined behavior
  • Handle reuse can cause data corruption
  • Rust 1.63+ provides I/O safety traits

Bad Example

#[cfg(unix)]
mod bad_example {
    use std::os::unix::io::RawFd;

    // DON'T: Accept raw handle without ownership
    fn bad_read(fd: RawFd) -> std::io::Result<Vec<u8>> {
        // What if fd was closed? What if it's reused?
        let mut buf = vec![0u8; 1024];
        let n = unsafe {
            libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len())
        };
        if n < 0 {
            Err(std::io::Error::last_os_error())
        } else {
            buf.truncate(n as usize);
            Ok(buf)
        }
    }

    // DON'T: Store raw handle without tracking ownership
    struct BadFileRef {
        fd: RawFd,  // Who owns this? Who closes it?
    }
}

Good Example

#[cfg(unix)]
mod good_example {
    use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd, FromRawFd, AsRawFd};
    use std::fs::File;

    // DO: Use BorrowedFd for borrowed access (Rust 1.63+)
    fn good_read(fd: BorrowedFd<'_>) -> std::io::Result<Vec<u8>> {
        let mut buf = vec![0u8; 1024];
        // BorrowedFd guarantees the fd is valid for this call
        let n = unsafe {
            libc::read(
                fd.as_raw_fd(),
                buf.as_mut_ptr() as *mut libc::c_void,
                buf.len()
            )
        };
        if n < 0 {
            Err(std::io::Error::last_os_error())
        } else {
            buf.truncate(n as usize);
            Ok(buf)
        }
    }

    // DO: Use OwnedFd for owned handles
    struct GoodFileOwner {
        fd: OwnedFd,  // Clearly owns the handle
    }

    impl Drop for GoodFileOwner {
        fn drop(&mut self) {
            // OwnedFd closes automatically
        }
    }

    // DO: Use generic AsFd bound for flexibility
    fn generic_read<F: AsFd>(f: &F) -> std::io::Result<Vec<u8>> {
        good_read(f.as_fd())
    }

    // Usage
    fn example() -> std::io::Result<()> {
        let file = File::open("test.txt")?;

        // Pass as BorrowedFd
        let data = good_read(file.as_fd())?;

        // Or use generic function
        let data = generic_read(&file)?;

        Ok(())
    }

    // DO: Take ownership from raw fd
    fn from_raw(fd: i32) -> Option<GoodFileOwner> {
        if fd < 0 {
            return None;
        }
        // SAFETY: Caller guarantees fd is valid and ownership is transferred
        let owned = unsafe { OwnedFd::from_raw_fd(fd) };
        Some(GoodFileOwner { fd: owned })
    }
}

I/O Safety Types (Rust 1.63+)

Type Meaning
OwnedFd Owns a file descriptor, closes on drop
BorrowedFd<'a> Borrows a fd for lifetime 'a
RawFd Raw integer, no safety guarantees
AsFd Trait for types that have a fd
From<OwnedFd> Create from owned fd
Into<OwnedFd> Convert to owned fd

Windows Equivalents

#[cfg(windows)]
use std::os::windows::io::{
    OwnedHandle, BorrowedHandle, RawHandle,
    AsHandle, FromRawHandle,
    OwnedSocket, BorrowedSocket, RawSocket,
    AsSocket, FromRawSocket,
};

Checklist

  • Am I using BorrowedFd/OwnedFd instead of RawFd?
  • Is ownership of handles clear?
  • Am I using the AsFd trait for generic code?
  • Is the fd guaranteed valid for the duration of use?
  • ffi-03: Implement Drop for resource wrappers
  • safety-02: Verify safety invariants