Skip to content

Commit 5c676be

Browse files
committed
On unix, ensure that only one Rust thread calls libc::exit or returns from main.
1 parent 1138036 commit 5c676be

File tree

4 files changed

+90
-0
lines changed

4 files changed

+90
-0
lines changed

library/std/src/rt.rs

+3
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,8 @@ fn lang_start<T: crate::process::Termination + 'static>(
161161
argv,
162162
sigpipe,
163163
);
164+
// Guard against multple threads calling `libc::exit` concurrently.
165+
// See the documentation for `unique_thread_exit` for more information.
166+
crate::sys::common::exit_guard::unique_thread_exit();
164167
v
165168
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/// Mitigation for https://github.com/rust-lang/rust/issues/126600
2+
///
3+
/// On `unix` (where `libc::exit` is not thread-safe), ensure that only one Rust thread
4+
/// calls `libc::exit` (or returns from `main`) by calling this function before calling
5+
/// `libc::exit` (or returning from `main`).
6+
///
7+
/// Technically not enough to ensure soundness, since other code directly calling
8+
/// libc::exit will still race with this.
9+
///
10+
/// *This function does not itself call `libc::exit`.* This is so it can also be used
11+
/// to guard returning from `main`.
12+
///
13+
/// This function will return only the first time it is called in a process.
14+
///
15+
/// * If it is called again on the same thread as the first call, it will abort.
16+
/// * If it is called again on a different thread, it will `thread::park()` in a loop
17+
/// (waiting for the process to exit).
18+
#[cfg(unix)]
19+
pub(crate) fn unique_thread_exit() {
20+
let this_thread_id = unsafe { libc::gettid() };
21+
debug_assert_ne!(this_thread_id, 0, "thread ID cannot be zero");
22+
#[cfg(target_has_atomic = "32")]
23+
{
24+
use crate::sync::atomic::{AtomicI32, Ordering};
25+
static EXITING_THREAD_ID: AtomicI32 = AtomicI32::new(0);
26+
match EXITING_THREAD_ID.compare_exchange(
27+
0,
28+
this_thread_id,
29+
Ordering::Relaxed,
30+
Ordering::Relaxed,
31+
) {
32+
Ok(_zero) => {
33+
// This is the first thread to call `unique_thread_exit`,
34+
// and this is the first time it is called.
35+
// Set EXITING_THREAD_ID to this thread's ID (done by the
36+
// compare_exchange) and return.
37+
}
38+
Err(id) if id == this_thread_id => {
39+
// This is the first thread to call `unique_thread_exit`,
40+
// but this is the second time it is called.
41+
// Abort the process.
42+
core::panicking::panic_nounwind("std::process::exit called re-entrantly")
43+
}
44+
Err(_) => {
45+
// This is not the first thread to call `unique_thread_exit`.
46+
// Park until the process exits.
47+
loop {
48+
crate::thread::park();
49+
}
50+
}
51+
}
52+
}
53+
#[cfg(not(target_has_atomic = "32"))]
54+
{
55+
use crate::sync::{Mutex, PoisonError};
56+
static EXITING_THREAD_ID: Mutex<i32> = Mutex::new(0);
57+
let mut exiting_thread_id =
58+
EXITING_THREAD_ID.lock().unwrap_or_else(PoisonError::into_inner);
59+
if *exiting_thread_id == 0 {
60+
// This is the first thread to call `unique_thread_exit`,
61+
// and this is the first time it is called.
62+
// Set EXITING_THREAD_ID to this thread's ID and return.
63+
*exiting_thread_id = this_thread_id;
64+
} else if *exiting_thread_id == this_thread_id {
65+
// This is the first thread to call `unique_thread_exit`,
66+
// but this is the second time it is called.
67+
// Abort the process.
68+
core::panicking::panic_nounwind("std::process::exit called re-entrantly")
69+
} else {
70+
// This is not the first thread to call `unique_thread_exit`.
71+
// Park until the process exits.
72+
drop(exiting_thread_id);
73+
loop {
74+
crate::thread::park();
75+
}
76+
}
77+
}
78+
}
79+
80+
/// Mitigation for https://github.com/rust-lang/rust/issues/126600
81+
/// Not required on platforms where `libc::exit` is thread-safe.
82+
#[cfg(not(unix))]
83+
pub(crate) fn unique_thread_exit() {
84+
// Mitigation not required on platforms where `exit` is thread-safe.
85+
}

library/std/src/sys/pal/common/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#![allow(dead_code)]
1212

1313
pub mod alloc;
14+
pub mod exit_guard;
1415
pub mod small_c_string;
1516

1617
#[cfg(test)]

library/std/src/sys/pal/unix/os.rs

+1
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ pub fn home_dir() -> Option<PathBuf> {
758758
}
759759

760760
pub fn exit(code: i32) -> ! {
761+
crate::sys::common::exit_guard::unique_thread_exit();
761762
unsafe { libc::exit(code as c_int) }
762763
}
763764

0 commit comments

Comments
 (0)