-
Notifications
You must be signed in to change notification settings - Fork 205
Closed
Description
Pretty simple:
- create a thread
- unshare the file descriptor table using this API that's not marked
unsafe
- open a file, create a pipe, whatever you like, get an
OwnedFd
OwnedFd
isSend
so you can move it to another thread, but the fd won't exist there- even worse, if another unrelated fd with the same number gets created in the receiving thread and then you drop the moved fd from the thread then it'll close the other fd, leaving a dangling reference, potentially in far-away and unrelated code
- also nice, but not technically UB: by moving the
OwnedFd
out of the first thread, you'll have effectively leaked the original fd
unshare
is a pretty powerful API. I wouldn't be surprised if some of the other variants caused issues (although we are protected by the kernel requiring single-threaded processes for many of them).
Here's a reproducer:
use std::thread::spawn;
use rustix::{fs::{OFlags, Mode, open, fstat}, thread::{unshare, UnshareFlags}};
fn main() {
let broken_fd = spawn(|| {
unshare(UnshareFlags::FILES).unwrap();
open("/", OFlags::PATH, Mode::empty()).unwrap()
}).join().unwrap();
// fstat() is always successful on a valid fd, right?
fstat(broken_fd).unwrap();
}
[package]
name = "fd-unsafe"
version = "0.1.0"
edition = "2024"
[dependencies]
rustix = { version = "1.0.7", features = ["fs", "thread"] }
when you run it:
$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/fd-unsafe`
fatal runtime error: IO Safety violation: owned file descriptor already closed
Aborted (core dumped)
Metadata
Metadata
Assignees
Labels
No labels