Skip to content

Commit

Permalink
Reopen standard streams when they are closed on Unix
Browse files Browse the repository at this point in the history
The syscalls returning a new file descriptors generally use
lowest-numbered file descriptor not currently opened, without any
exceptions for those corresponding to the standard streams.

Previously when any of standard streams has been closed before starting
the application, operations on std::io::{stderr,stdin,stdout} objects
were likely to operate on other logically unrelated file resources
opened afterwards.

Avoid the issue by reopening the standard streams when they are closed.
  • Loading branch information
tmiasko committed Sep 25, 2020
1 parent 87d262a commit 44e083e
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
57 changes: 57 additions & 0 deletions library/std/src/sys/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ pub use crate::sys_common::os_str_bytes as os_str;

#[cfg(not(test))]
pub fn init() {
// The standard streams might be closed on application startup. To prevent
// std::io::{stdin, stdout,stderr} objects from using other unrelated file
// resources opened later, we reopen standards streams when they are closed.
unsafe {
sanitize_standard_fds();
}

// By default, some platforms will send a *signal* when an EPIPE error
// would otherwise be delivered. This runtime doesn't install a SIGPIPE
// handler, causing it to kill the program, which isn't exactly what we
Expand All @@ -86,6 +93,56 @@ pub fn init() {
reset_sigpipe();
}

// In the case when all file descriptors are open, the poll has been
// observed to perform better than fcntl (on GNU/Linux).
#[cfg(not(any(
target_os = "emscripten",
target_os = "fuchsia",
// The poll on Darwin doesn't set POLLNVAL for closed fds.
target_os = "macos",
target_os = "ios",
target_os = "redox",
)))]
unsafe fn sanitize_standard_fds() {
use crate::sys::os::errno;
let pfds: &mut [_] = &mut [
libc::pollfd { fd: 0, events: 0, revents: 0 },
libc::pollfd { fd: 1, events: 0, revents: 0 },
libc::pollfd { fd: 2, events: 0, revents: 0 },
];
while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 {
if errno() == libc::EINTR {
continue;
}
libc::abort();
}
for pfd in pfds {
if pfd.revents & libc::POLLNVAL == 0 {
continue;
}
if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
// If the stream is closed but we failed to reopen it, abort the
// process. Otherwise we wouldn't preserve the safety of
// operations on the corresponding Rust object Stdin, Stdout, or
// Stderr.
libc::abort();
}
}
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "redox"))]
unsafe fn sanitize_standard_fds() {
use crate::sys::os::errno;
for fd in 0..3 {
if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF {
if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
libc::abort();
}
}
}
}
#[cfg(any(target_os = "emscripten", target_os = "fuchsia"))]
unsafe fn sanitize_standard_fds() {}

#[cfg(not(any(target_os = "emscripten", target_os = "fuchsia")))]
unsafe fn reset_sigpipe() {
assert!(signal(libc::SIGPIPE, libc::SIG_IGN) != libc::SIG_ERR);
Expand Down
69 changes: 69 additions & 0 deletions src/test/ui/closed-std-fds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Verifies that std provides replacement for the standard file descriptors when they are missing.
//
// run-pass
// ignore-windows unix specific test
// ignore-cloudabi no processes
// ignore-emscripten no processes
// ignore-sgx no processes

#![feature(rustc_private)]
extern crate libc;

use std::io::{self, Read};
use std::os::unix::process::CommandExt;
use std::process::Command;

fn main() {
let mut args = std::env::args();
let argv0 = args.next().expect("argv0");
match args.next().as_deref() {
Some("child") => child(),
None => parent(&argv0),
_ => unreachable!(),
}
}

fn parent(argv0: &str) {
let status = unsafe { Command::new(argv0)
.arg("child")
.pre_exec(close_std_fds_on_exec)
.status()
.expect("failed to execute child process")
};
if !status.success() {
panic!("child failed with {}", status);
}
}

fn close_std_fds_on_exec() -> io::Result<()> {
for fd in 0..3 {
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) == -1 } {
return Err(io::Error::last_os_error())
}
}
Ok(())
}

fn child() {
// Standard file descriptors should be valid.
assert_fd_is_valid(0);
assert_fd_is_valid(1);
assert_fd_is_valid(2);

// Writing to stdout & stderr should not panic.
println!("a");
println!("b");
eprintln!("c");
eprintln!("d");

// Stdin should be at EOF.
let mut buffer = Vec::new();
let n = io::stdin().read_to_end(&mut buffer).unwrap();
assert_eq!(n, 0);
}

fn assert_fd_is_valid(fd: libc::c_int) {
if unsafe { libc::fcntl(fd, libc::F_GETFD) == -1 } {
panic!("file descriptor {} is not valid {}", fd, io::Error::last_os_error());
}
}

0 comments on commit 44e083e

Please sign in to comment.