diff --git a/library/std/src/sys/unix/mod.rs b/library/std/src/sys/unix/mod.rs index eddf00d3979f5..166e28435f0a0 100644 --- a/library/std/src/sys/unix/mod.rs +++ b/library/std/src/sys/unix/mod.rs @@ -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 @@ -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); diff --git a/src/test/ui/closed-std-fds.rs b/src/test/ui/closed-std-fds.rs new file mode 100644 index 0000000000000..906da94433455 --- /dev/null +++ b/src/test/ui/closed-std-fds.rs @@ -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()); + } +}