Skip to content

std::process::Command and I/O safety #100

@sunfishcode

Description

@sunfishcode

Following up on a discussion in #97: std::process::Command has a safe API for spawning child processes. Its documentation doesn't mention non-O_CLOEXEC file descriptors, but from some experimenting, child processes do inherit non-O_CLOEXEC file descriptors.

Does this violate I/O safety?

When a program calls Command::spawn, all existing memory in the process is replaced with a new program image, so Rust's safety guarantees have typically considered that to be the end of one program and the beginning of a new one. However, this is an area where file descriptors are different from pointers. FIle descriptors not marked O_CLOEXEC persist into the next program. In effect, spawn is implicitly obtaining the values of all such file descriptors in the process, even those meant to be encapsulated, and "passing" them to another program where they could be accessed. That has the shape of an I/O safety violation. And in practice, leaking file descriptors to child processes is a real security problem.

One possible way out of this is to say that safe code can't create non-O_CLOEXEC file descriptors. Rust's std already does open all file descriptors as O_CLOEXEC whenever it can, and sets them to O_CLOEXEC even when it can't create them that way. Perhaps then, rustix's openat function should always set O_CLOEXEC too, and perhaps there should be a special function named something like unsafe fn dup2_no_cloexec for creating non-O_CLOEXEC file descriptors for when those are really needed.

One tricky issue is that not all OS's can set O_CLOEXEC atomically in all cases. Rust uses ioctl with FIOCLEX to set the CLOEXEC flag as soon as it can, but there is a window where another thread could call exec and capture the file descriptor before it has O_CLOEXEC set. A related issue is that users of std::process::Command may already be creating non-O_CLOEXEC file descriptors within safe functions. A possible long-term path out of this is to say that post-exec programs accessing file descriptors passed through exec is in the same category as using a debugger on the pre-exec program, so it's out of scope for I/O safety, and then to provide a new safe way to pass file descriptors to child processes and gradually encourage the Rust ecosystem to adopt it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requestedsemver bumpIssues that will require a semver-incompatible fix

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions