Skip to content

Commit 20e2172

Browse files
ijacksonMark-Simulacrum
authored andcommitted
std::process::unix: Do not unwind past fork() in child
Unwinding past fork() in the child causes whatever traps the unwind to return twice. This is very strange and clearly not desirable. With the default behaviour of the thread library, this can even result in a panic in the child being transformed into zero exit status (ie, success) as seen in the parent! If unwinding reaches the fork point, the child should abort. Annotating the closure with #[unwind(aborts)] is not sufficiently stable right now, so we use catch_unwind. This requires marking the closure UnwindSafe - which is fine regardless of the contents of the closure, since we abort on unwind and won't therefore touch anything the closure might have captured. Fixes #79740. Signed-off-by: Ian Jackson <[email protected]>
1 parent a2f8f62 commit 20e2172

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

library/std/src/sys/unix/process/process_unix.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::convert::TryInto;
22
use crate::fmt;
33
use crate::io::{self, Error, ErrorKind};
4+
use crate::panic;
45
use crate::ptr;
56
use crate::sys;
67
use crate::sys::cvt;
@@ -53,7 +54,7 @@ impl Command {
5354

5455
let pid = unsafe {
5556
match result {
56-
0 => {
57+
0 => match panic::catch_unwind(panic::AssertUnwindSafe(|| -> ! {
5758
drop(input);
5859
let Err(err) = self.do_exec(theirs, envp.as_ref());
5960
let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
@@ -73,7 +74,9 @@ impl Command {
7374
// we're being torn down regardless
7475
rtassert!(output.write(&bytes).is_ok());
7576
libc::_exit(1)
76-
}
77+
})) {
78+
Err(_) => crate::process::abort(),
79+
},
7780
n => n,
7881
}
7982
};
@@ -533,3 +536,7 @@ impl fmt::Display for ExitStatus {
533536
}
534537
}
535538
}
539+
540+
#[cfg(test)]
541+
#[path = "process_unix/tests.rs"]
542+
mod tests;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#[test]
2+
fn test_command_fork_no_unwind() {
3+
use crate::os::unix::process::CommandExt;
4+
use crate::os::unix::process::ExitStatusExt;
5+
use crate::panic::catch_unwind;
6+
use crate::process::Command;
7+
8+
let got = catch_unwind(|| {
9+
let mut c = Command::new("echo");
10+
c.arg("hi");
11+
unsafe {
12+
c.pre_exec(|| panic!("crash now!"));
13+
}
14+
let st = c.status().expect("failed to get command status");
15+
eprintln!("{:?}", st);
16+
st
17+
});
18+
eprintln!("got={:?}", &got);
19+
let estatus = got.expect("panic unexpectedly propagated");
20+
let signal = estatus.signal().expect("expected child to die of signal");
21+
assert!(signal == libc::SIGABRT || signal == libc::SIGILL);
22+
}

0 commit comments

Comments
 (0)