Skip to content

Commit 916d7fc

Browse files
committed
Ensure that only one Rust thread calls libc::exit or returns from main.
1 parent 1138036 commit 916d7fc

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

library/std/src/rt.rs

+4
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,9 @@ 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+
#[cfg(unix)]
167+
crate::sys::os::unique_thread_exit();
164168
v
165169
}

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

+80
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,87 @@ pub fn home_dir() -> Option<PathBuf> {
757757
}
758758
}
759759

760+
/// Mitigation for https://github.com/rust-lang/rust/issues/126600
761+
///
762+
/// Ensure that only one Rust thread calls `libc::exit` (or returns from `main`) by
763+
/// calling this function before calling `libc::exit` (or returning from `main`).
764+
/// Technically not enough to ensure soundness, since other code directly calling
765+
/// libc::exit will still race with this.
766+
///
767+
/// *This function does not itself call `libc::exit`.* This is so it can also be used
768+
/// to guard returning from `main`.
769+
///
770+
/// This function will return only the first time it is called in a process.
771+
///
772+
/// * If it is called again on the same thread as the first call, it will abort.
773+
/// * If it is called again on a different thread, it will `thread::park()` in a loop
774+
/// (waiting for the process to exit).
775+
/// * If it is called in a situation where `std::thread::current()` fails, it will abort.
776+
pub(crate) fn unique_thread_exit() {
777+
let this_thread_id =
778+
crate::thread::try_current().unwrap_or_else(|| crate::process::abort()).id().as_u64().get();
779+
// Defense against refactors of `.as_u64()`
780+
debug_assert_ne!(this_thread_id, 0, "thread ID cannot be zero");
781+
#[cfg(target_has_atomic = "64")]
782+
{
783+
use crate::sync::atomic::{AtomicU64, Ordering};
784+
static EXITING_THREAD_ID: AtomicU64 = AtomicU64::new(0);
785+
match EXITING_THREAD_ID.compare_exchange(
786+
0,
787+
this_thread_id,
788+
Ordering::Relaxed,
789+
Ordering::Relaxed,
790+
) {
791+
Ok(_zero) => {
792+
// This is the first thread to call `unique_thread_exit`,
793+
// and this is the first time it is called.
794+
// Set EXITING_THREAD_ID to this thread's ID (done by the
795+
// compare_exchange) and return.
796+
}
797+
Err(id) if id == this_thread_id => {
798+
// This is the first thread to call `unique_thread_exit`,
799+
// but this is the second time it is called.
800+
// Abort the process.
801+
crate::process::abort();
802+
}
803+
Err(_) => {
804+
// This is not the first thread to call `unique_thread_exit`.
805+
// Park until the process exits.
806+
loop {
807+
crate::thread::park();
808+
}
809+
}
810+
}
811+
}
812+
#[cfg(not(target_has_atomic = "64"))]
813+
{
814+
use crate::sync::{Mutex, PoisonError};
815+
static EXITING_THREAD_ID: Mutex<u64> = Mutex::new(0);
816+
let mut exiting_thread_id =
817+
EXITING_THREAD_ID.lock().unwrap_or_else(PoisonError::into_inner);
818+
if *exiting_thread_id == 0 {
819+
// This is the first thread to call `unique_thread_exit`,
820+
// and this is the first time it is called.
821+
// Set EXITING_THREAD_ID to this thread's ID and return.
822+
*exiting_thread_id = this_thread_id;
823+
} else if *exiting_thread_id == this_thread_id {
824+
// This is the first thread to call `unique_thread_exit`,
825+
// but this is the second time it is called.
826+
// Abort the process.
827+
crate::process::abort();
828+
} else {
829+
// This is not the first thread to call `unique_thread_exit`.
830+
// Park until the process exits.
831+
drop(exiting_thread_id);
832+
loop {
833+
crate::thread::park();
834+
}
835+
}
836+
}
837+
}
838+
760839
pub fn exit(code: i32) -> ! {
840+
unique_thread_exit();
761841
unsafe { libc::exit(code as c_int) }
762842
}
763843

0 commit comments

Comments
 (0)