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
#[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
你可能感兴趣的文章
Do Not Expose Raw Pointers in Public APIs
Unsafe Code Authors Must Verify Safety Invariants
Ensure Consistent Data Layout for Custom Types
Add Safety Section in Docs for Public Unsafe Functions
Do Not Use Union Variants Across Different Lifetimes
Use MaybeUninit
Ensure C-ABI Compatibility for Strings Between Rust and C
Do Not Dereference Pointers Cast to Misaligned Types
Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types