Change:
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.)

Discussion:
#![feature(dropck_eyepatch)] #![feature(exhaustive_patterns)] #![feature(intra_doc_pointers)] <ins> #![feature(label_break_value)] </ins> #![feature(lang_items)] #![feature(let_chains)] #![feature(linkage)]

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

libc::abort(); } } <del> } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "redox"))] { 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/null0".as_ptr().cast(), libc::O_RDWR, 0) == -1 { libc::abort(); } } } </del> } } }

<ins> // Check that statically linked binary executes successfully // with RLIMIT_NOFILE resource lowered to zero. Regression // test for issue #96621. // // run-pass // dont-check-compiler-stderr // only-linux // no-prefer-dynamic // compile-flags: -Ctarget-feature=+crt-static -Crpath=no #![feature(exit_status_error)] #![feature(rustc_private)] extern crate libc; use std::os::unix::process::CommandExt; use std::process::Command; fn main() { let mut args = std::env::args(); let this = args.next().unwrap(); match args.next().as_deref() { None => { let mut cmd = Command::new(this); cmd.arg("Ok!"); unsafe { cmd.pre_exec(|| { let rlim = libc::rlimit { rlim_cur: 0, rlim_max: 0, }; if libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) == -1 { Err(std::io::Error::last_os_error()) } else { Ok(()) } }) }; let output = cmd.output().unwrap(); println!("{:?}", output); output.status.exit_ok().unwrap(); assert!(output.stdout.starts_with(b"Ok!")); } Some(word) => { println!("{}", word); } } } </ins>