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: exec
ing 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:
- Does I/O safety forbid
dup
ing arbitrary file descriptors? #114167 - Determine and document the distinction between I/O safety and regular safety on unsafe stdlib APIs unsafe-code-guidelines#434
- Zulip, t-opsem thread
- Zulip, t-lang thread
I'm aware of the following proposals to resolve this situation:
- "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 byset_var
though; even if we make thatunsafe
we'd have to phrase its safety contract very carefully if it want to go this route. - 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.
- 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.