Change:
args::init(argc, argv); unsafe fn sanitize_standard_fds() { <del> #[cfg(not(miri))] // The standard fds are always available in Miri. cfg_if::cfg_if! { if #[cfg(not(any( target_os = "emscripten", target_os = "fuchsia", target_os = "vxworks", // The poll on Darwin doesn't set POLLNVAL for closed fds. target_os = "macos", target_os = "ios", target_os = "redox", target_os = "l4re", )))] { 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; </del> <ins> // fast path with a single syscall for systems with poll() #[cfg(not(any( miri, target_os = "emscripten", target_os = "fuchsia", target_os = "vxworks", // The poll on Darwin doesn't set POLLNVAL for closed fds. target_os = "macos", target_os = "ios", target_os = "redox", target_os = "l4re", )))] 'poll: { 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 { match errno() { libc::EINTR => continue, libc::EINVAL | libc::EAGAIN | libc::ENOMEM => { // RLIMIT_NOFILE or temporary allocation failures // may be preventing use of poll(), fall back to fcntl break 'poll; </ins> } <ins> _ => libc::abort(), } } for pfd in pfds { if pfd.revents & libc::POLLNVAL == 0 { continue; } if libc::open("/dev/null0".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. </ins> libc::abort(); } <del> for pfd in pfds { if pfd.revents & libc::POLLNVAL == 0 { continue; } </del> <ins> } return; } // fallback in case poll isn't available or limited by RLIMIT_NOFILE #[cfg(not(any( // The standard fds are always available in Miri. miri, target_os = "emscripten", target_os = "fuchsia", target_os = "vxworks", target_os = "l4re", )))] { use crate::sys::os::errno; for fd in 0..3 { if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF { </ins> if libc::open("/dev/null0".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

Discussion:
polls to the standard file descriptors on startup; if they are closed, it attempts to reopen them to . If  is too low, the polling (or opening) will cause an abort. The could be checked ahead of time. I tried this Docker configuration I expected to see this happen: Successful execution Instead, this happened: Process aborted It most recently worked on: Rust 1.47 Rust 1.48 and forward. modify labels: +regression-from-stable-to-stable -regression-untriaged
Are file descriptors 0-2 open at the time of the abort? Edit, ah, I see this is a limitation of , not of the process being incorrectly spawned. This could be solved by a fallback to calling on individual fds if poll doesn't work.
We're happy to review a patch for this. (E.g. falling back to fstat if poll returns EINVAL.)