| 
 | 1 | +cfg_if::cfg_if! {  | 
 | 2 | +    if #[cfg(target_os = "linux")] {  | 
 | 3 | +        /// pthread_t is a pointer on some platforms,  | 
 | 4 | +        /// so we wrap it in this to impl Send + Sync.  | 
 | 5 | +        #[derive(Clone, Copy)]  | 
 | 6 | +        #[repr(transparent)]  | 
 | 7 | +        struct PThread(libc::pthread_t);  | 
 | 8 | +        // Safety: pthread_t is safe to send between threads  | 
 | 9 | +        unsafe impl Send for PThread {}  | 
 | 10 | +        // Safety: pthread_t is safe to share between threads  | 
 | 11 | +        unsafe impl Sync for PThread {}  | 
 | 12 | +        /// Mitigation for <https://github.com/rust-lang/rust/issues/126600>  | 
 | 13 | +        ///  | 
 | 14 | +        /// On glibc, `libc::exit` has been observed to not always be thread-safe.  | 
 | 15 | +        /// It is currently unclear whether that is a glibc bug or allowed by the standard.  | 
 | 16 | +        /// To mitigate this problem, we ensure that only one  | 
 | 17 | +        /// Rust thread calls `libc::exit` (or returns from `main`) by calling this function before  | 
 | 18 | +        /// calling `libc::exit` (or returning from `main`).  | 
 | 19 | +        ///  | 
 | 20 | +        /// Technically, this is not enough to ensure soundness, since other code directly calling  | 
 | 21 | +        /// `libc::exit` will still race with this.  | 
 | 22 | +        ///  | 
 | 23 | +        /// *This function does not itself call `libc::exit`.* This is so it can also be used  | 
 | 24 | +        /// to guard returning from `main`.  | 
 | 25 | +        ///  | 
 | 26 | +        /// This function will return only the first time it is called in a process.  | 
 | 27 | +        ///  | 
 | 28 | +        /// * If it is called again on the same thread as the first call, it will abort.  | 
 | 29 | +        /// * If it is called again on a different thread, it will wait in a loop  | 
 | 30 | +        ///   (waiting for the process to exit).  | 
 | 31 | +        #[cfg_attr(any(test, doctest), allow(dead_code))]  | 
 | 32 | +        pub(crate) fn unique_thread_exit() {  | 
 | 33 | +            let this_thread_id = unsafe { libc::pthread_self() };  | 
 | 34 | +            use crate::sync::{Mutex, PoisonError};  | 
 | 35 | +            static EXITING_THREAD_ID: Mutex<Option<PThread>> = Mutex::new(None);  | 
 | 36 | +            let mut exiting_thread_id =  | 
 | 37 | +                EXITING_THREAD_ID.lock().unwrap_or_else(PoisonError::into_inner);  | 
 | 38 | +            match *exiting_thread_id {  | 
 | 39 | +                None => {  | 
 | 40 | +                    // This is the first thread to call `unique_thread_exit`,  | 
 | 41 | +                    // and this is the first time it is called.  | 
 | 42 | +                    // Set EXITING_THREAD_ID to this thread's ID and return.  | 
 | 43 | +                    *exiting_thread_id = Some(PThread(this_thread_id));  | 
 | 44 | +                },  | 
 | 45 | +                Some(exiting_thread_id) if exiting_thread_id.0 == this_thread_id => {  | 
 | 46 | +                    // This is the first thread to call `unique_thread_exit`,  | 
 | 47 | +                    // but this is the second time it is called.  | 
 | 48 | +                    // Abort the process.  | 
 | 49 | +                    core::panicking::panic_nounwind("std::process::exit called re-entrantly")  | 
 | 50 | +                }  | 
 | 51 | +                Some(_) => {  | 
 | 52 | +                    // This is not the first thread to call `unique_thread_exit`.  | 
 | 53 | +                    // Pause until the process exits.  | 
 | 54 | +                    drop(exiting_thread_id);  | 
 | 55 | +                    loop {  | 
 | 56 | +                        // Safety: libc::pause is safe to call.  | 
 | 57 | +                        unsafe { libc::pause(); }  | 
 | 58 | +                    }  | 
 | 59 | +                }  | 
 | 60 | +            }  | 
 | 61 | +        }  | 
 | 62 | +    } else {  | 
 | 63 | +        /// Mitigation for <https://github.com/rust-lang/rust/issues/126600>  | 
 | 64 | +        ///  | 
 | 65 | +        /// Mitigation is ***NOT*** implemented on this platform, either because this platform  | 
 | 66 | +        /// is not affected, or because mitigation is not yet implemented for this platform.  | 
 | 67 | +        #[cfg_attr(any(test, doctest), allow(dead_code))]  | 
 | 68 | +        pub(crate) fn unique_thread_exit() {  | 
 | 69 | +            // Mitigation not required on platforms where `exit` is thread-safe.  | 
 | 70 | +        }  | 
 | 71 | +    }  | 
 | 72 | +}  | 
0 commit comments