Skip to content

std: use the address of errno to identify threads in unique_thread_exit #139206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 20 additions & 23 deletions library/std/src/sys/exit_guard.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
/// pthread_t is a pointer on some platforms,
/// so we wrap it in this to impl Send + Sync.
#[derive(Clone, Copy)]
#[repr(transparent)]
struct PThread(libc::pthread_t);
// Safety: pthread_t is safe to send between threads
unsafe impl Send for PThread {}
// Safety: pthread_t is safe to share between threads
unsafe impl Sync for PThread {}
/// Mitigation for <https://github.com/rust-lang/rust/issues/126600>
///
/// On glibc, `libc::exit` has been observed to not always be thread-safe.
Expand All @@ -30,28 +21,34 @@ cfg_if::cfg_if! {
/// (waiting for the process to exit).
#[cfg_attr(any(test, doctest), allow(dead_code))]
pub(crate) fn unique_thread_exit() {
let this_thread_id = unsafe { libc::pthread_self() };
use crate::sync::{Mutex, PoisonError};
static EXITING_THREAD_ID: Mutex<Option<PThread>> = Mutex::new(None);
let mut exiting_thread_id =
EXITING_THREAD_ID.lock().unwrap_or_else(PoisonError::into_inner);
match *exiting_thread_id {
None => {
use crate::ffi::c_int;
use crate::ptr;
use crate::sync::atomic::AtomicPtr;
use crate::sync::atomic::Ordering::{Acquire, Relaxed};

static EXITING_THREAD_ID: AtomicPtr<c_int> = AtomicPtr::new(ptr::null_mut());

// We use the address of `errno` as a cheap and safe way to identify
// threads. As the C standard mandates that `errno` must have thread
// storage duration, we can rely on its address not changing over the
// lifetime of the thread. Additionally, accesses to `errno` are
// async-signal-safe, so this function is available in all imaginable
// circumstances.
let this_thread_id = crate::sys::os::errno_location();
match EXITING_THREAD_ID.compare_exchange(ptr::null_mut(), this_thread_id, Acquire, Relaxed) {
Ok(_) => {
// This is the first thread to call `unique_thread_exit`,
// and this is the first time it is called.
// Set EXITING_THREAD_ID to this thread's ID and return.
*exiting_thread_id = Some(PThread(this_thread_id));
},
Some(exiting_thread_id) if exiting_thread_id.0 == this_thread_id => {
// and this is the first time it is called. Continue exiting.
}
Err(exiting_thread_id) if exiting_thread_id == this_thread_id => {
// This is the first thread to call `unique_thread_exit`,
// but this is the second time it is called.
// Abort the process.
core::panicking::panic_nounwind("std::process::exit called re-entrantly")
}
Some(_) => {
Err(_) => {
// This is not the first thread to call `unique_thread_exit`.
// Pause until the process exits.
drop(exiting_thread_id);
loop {
// Safety: libc::pause is safe to call.
unsafe { libc::pause(); }
Expand Down
2 changes: 1 addition & 1 deletion library/std/src/sys/pal/unix/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ unsafe extern "C" {
#[cfg_attr(target_os = "aix", link_name = "_Errno")]
// SAFETY: this will always return the same pointer on a given thread.
#[unsafe(ffi_const)]
fn errno_location() -> *mut c_int;
pub safe fn errno_location() -> *mut c_int;
}

/// Returns the platform-specific value of errno
Expand Down
Loading