Skip to content

Commit db7ee7c

Browse files
committed
Auto merge of #75295 - tmiasko:fds, r=Amanieu
Reopen standard file descriptors when they are missing on Unix The syscalls returning a new file descriptors generally return lowest-numbered file descriptor not currently opened, without any exceptions for those corresponding to stdin, sdout, or stderr. Previously when any of standard file descriptors has been closed before starting the application, operations on std::io::{stderr,stdin,stdout} were likely to either succeed while being performed on unrelated file descriptor, or fail with EBADF which is silently ignored. Avoid the issue by using /dev/null as a replacement when the standard file descriptors are missing. The implementation is based on the one found in musl. It was selected among a few others on the basis of the lowest overhead in the case when all descriptors are already present (measured on GNU/Linux). Closes #57728. Closes #46981. Closes #60447. Benefits: * Makes applications robust in the absence of standard file descriptors. * Upholds IntoRawFd / FromRawFd safety contract (which was broken previously). Drawbacks: * Additional syscall during startup. * The standard descriptors might have been closed intentionally. * Requires /dev/null. Alternatives: * Check if stdin, stdout, stderr are opened and provide no-op substitutes in std::io::{stdin,stdout,stderr} without reopening them directly. * Leave the status quo, expect robust applications to reopen them manually.
2 parents c0b15cc + 7d98d22 commit db7ee7c

File tree

2 files changed

+82
-2
lines changed

2 files changed

+82
-2
lines changed

library/std/src/sys/unix/mod.rs

+62
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ pub use crate::sys_common::os_str_bytes as os_str;
7575

7676
#[cfg(not(test))]
7777
pub fn init() {
78+
// The standard streams might be closed on application startup. To prevent
79+
// std::io::{stdin, stdout,stderr} objects from using other unrelated file
80+
// resources opened later, we reopen standards streams when they are closed.
81+
unsafe {
82+
sanitize_standard_fds();
83+
}
84+
7885
// By default, some platforms will send a *signal* when an EPIPE error
7986
// would otherwise be delivered. This runtime doesn't install a SIGPIPE
8087
// handler, causing it to kill the program, which isn't exactly what we
@@ -86,6 +93,61 @@ pub fn init() {
8693
reset_sigpipe();
8794
}
8895

96+
// In the case when all file descriptors are open, the poll has been
97+
// observed to perform better than fcntl (on GNU/Linux).
98+
#[cfg(not(any(
99+
miri,
100+
target_os = "emscripten",
101+
target_os = "fuchsia",
102+
// The poll on Darwin doesn't set POLLNVAL for closed fds.
103+
target_os = "macos",
104+
target_os = "ios",
105+
target_os = "redox",
106+
)))]
107+
unsafe fn sanitize_standard_fds() {
108+
use crate::sys::os::errno;
109+
let pfds: &mut [_] = &mut [
110+
libc::pollfd { fd: 0, events: 0, revents: 0 },
111+
libc::pollfd { fd: 1, events: 0, revents: 0 },
112+
libc::pollfd { fd: 2, events: 0, revents: 0 },
113+
];
114+
while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 {
115+
if errno() == libc::EINTR {
116+
continue;
117+
}
118+
libc::abort();
119+
}
120+
for pfd in pfds {
121+
if pfd.revents & libc::POLLNVAL == 0 {
122+
continue;
123+
}
124+
if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
125+
// If the stream is closed but we failed to reopen it, abort the
126+
// process. Otherwise we wouldn't preserve the safety of
127+
// operations on the corresponding Rust object Stdin, Stdout, or
128+
// Stderr.
129+
libc::abort();
130+
}
131+
}
132+
}
133+
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "redox"))]
134+
unsafe fn sanitize_standard_fds() {
135+
use crate::sys::os::errno;
136+
for fd in 0..3 {
137+
if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF {
138+
if libc::open("/dev/null\0".as_ptr().cast(), libc::O_RDWR, 0) == -1 {
139+
libc::abort();
140+
}
141+
}
142+
}
143+
}
144+
#[cfg(any(
145+
// The standard fds are always available in Miri.
146+
miri,
147+
target_os = "emscripten",
148+
target_os = "fuchsia"))]
149+
unsafe fn sanitize_standard_fds() {}
150+
89151
#[cfg(not(any(target_os = "emscripten", target_os = "fuchsia")))]
90152
unsafe fn reset_sigpipe() {
91153
assert!(signal(libc::SIGPIPE, libc::SIG_IGN) != libc::SIG_ERR);

src/test/ui/no-stdio.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ unsafe fn without_stdio<R, F: FnOnce() -> R>(f: F) -> R {
3636
return r
3737
}
3838

39+
#[cfg(unix)]
40+
fn assert_fd_is_valid(fd: libc::c_int) {
41+
if unsafe { libc::fcntl(fd, libc::F_GETFD) == -1 } {
42+
panic!("file descriptor {} is not valid: {}", fd, io::Error::last_os_error());
43+
}
44+
}
45+
46+
#[cfg(windows)]
47+
fn assert_fd_is_valid(_fd: libc::c_int) {}
48+
3949
#[cfg(windows)]
4050
unsafe fn without_stdio<R, F: FnOnce() -> R>(f: F) -> R {
4151
type DWORD = u32;
@@ -77,10 +87,18 @@ unsafe fn without_stdio<R, F: FnOnce() -> R>(f: F) -> R {
7787

7888
fn main() {
7989
if env::args().len() > 1 {
90+
// Writing to stdout & stderr should not panic.
8091
println!("test");
8192
assert!(io::stdout().write(b"test\n").is_ok());
8293
assert!(io::stderr().write(b"test\n").is_ok());
94+
95+
// Stdin should be at EOF.
8396
assert_eq!(io::stdin().read(&mut [0; 10]).unwrap(), 0);
97+
98+
// Standard file descriptors should be valid on UNIX:
99+
assert_fd_is_valid(0);
100+
assert_fd_is_valid(1);
101+
assert_fd_is_valid(2);
84102
return
85103
}
86104

@@ -109,12 +127,12 @@ fn main() {
109127
.stdout(Stdio::null())
110128
.stderr(Stdio::null())
111129
.status().unwrap();
112-
assert!(status.success(), "{:?} isn't a success", status);
130+
assert!(status.success(), "{} isn't a success", status);
113131

114132
// Finally, close everything then spawn a child to make sure everything is
115133
// *still* ok.
116134
let status = unsafe {
117135
without_stdio(|| Command::new(&me).arg("next").status())
118136
}.unwrap();
119-
assert!(status.success(), "{:?} isn't a success", status);
137+
assert!(status.success(), "{} isn't a success", status);
120138
}

0 commit comments

Comments
 (0)