3.7 KiB
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?
Related Rules
ffi-03: Implement Drop for resource wrapperssafety-02: Verify safety invariants