Skip to content

I/O safety forbids the "pass FD via env var" pattern (e.g., jobserver) #116059

Open

Description

In #114780 we have clarified what is meany by I/O safety. In particular:

//! To uphold I/O safety, it is crucial that no code acts on file descriptors it does not own or
//! borrow, and no code closes file descriptors it does not own. In other words, a safe function
//! that takes a regular integer, treats it as a file descriptor, and acts on it, is *unsound*.

This is unfortunately in conflict with a reasonably common pattern on Unix systems: execing a process with a file descriptor initialized somewhere, and setting an env var to tell it where to find the FD. This pattern is used, for instance, by the jobserver protocol. The jobserver crate hence just takes an integer from an env var, turns it into a file descriptor, and reads/writes to that -- a violation of I/O safety. Crates like cc hence are technically unsound when they expose a safe function that internally uses the jobserver crate. The potential concern here is that the env var might be wrong, the jobserver crate now acts on a random FD by someone else, and that someone might have relied on I/O safety to protect their FD from foreign influence.

Assuming that we don't want to tell the cc maintainer that cc::Build::new() must be unsafe, what shall we do about this?
There's been a lot of prior discussion:

I'm aware of the following proposals to resolve this situation:

  1. "export" the safety burden to the environment: starting a process that uses cc and setting the env var the wrong way is violating a precondition of this process, and Rust's soundness guarantees do not apply. This is undermined by set_var though; even if we make that unsafe we'd have to phrase its safety contract very carefully if it want to go this route.
  2. Weaken the entire concept of I/O safety to no longer provide any protection against random FDs being read/written, and only protect against random FDs being closed.
  3. Develop some elaborate system of "initially existing global FDs" that cc/jobserver could use to be sure that the FD they work on already existed when the program started, and is not some other crate's private property. See here for a bit of elaboration on that idea.

We should pick one, or develop some alternative that provides a satisfying answer to how cc::Build::new() can be a sound function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    A-ioArea: `std::io`, `std::fs`, `std::net` and `std::path`T-langRelevant to the language team, which will review and decide on the PR/issue.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions