Skip to content

rustix::thread::unshare(UnshareFlags::FILES) can violate io_safety #1479

@allisonkarlitskaya

Description

@allisonkarlitskaya

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 is Send 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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions