Skip to content

Commit 1530977

Browse files
committedJun 21, 2024
On target_os = "linux", ensure that only one Rust thread calls libc::exit or returns from main.
1 parent 8cd20cb commit 1530977

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed
 

‎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
}

‎std/src/sys/pal/common/exit_guard.rs

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

‎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)]

‎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)