--- id: io-01 original_id: P.UNS.FIO.01 level: P impact: 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 ```rust #[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> { // 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 ```rust #[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> { 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: &F) -> std::io::Result> { 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 { 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` | Create from owned fd | | `Into` | Convert to owned fd | ## Windows Equivalents ```rust #[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? ## Related Rules - `ffi-03`: Implement Drop for resource wrappers - `safety-02`: Verify safety invariants