Skip to content

Commit 7d98d22

Browse files
committed
Reopen standard streams when they are closed on Unix
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.
1 parent 4529af9 commit 7d98d22

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)