about summary refs log tree commit diff
path: root/library/std/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-07-13 01:18:59 +0000
committerbors <bors@rust-lang.org>2024-07-13 01:18:59 +0000
commit03c2100dede5d8779a032cf1fc1c28201c37048a (patch)
treeb523640cb7fcfd7a223917d18e99be4c6478c65a /library/std/src
parent336e89bd1518f2cd81574384037b3cffe03c63a8 (diff)
parent8bcbab5dd1c6449c2acd3ea6a94b245c0936722d (diff)
downloadrust-03c2100dede5d8779a032cf1fc1c28201c37048a.tar.gz
rust-03c2100dede5d8779a032cf1fc1c28201c37048a.zip
Auto merge of #126606 - zachs18:patch-2, r=joboet
Guard against calling `libc::exit` multiple times on Linux.

Mitigates (but does not fix) #126600 by ensuring only one thread which calls Rust `exit` actually calls `libc::exit`, and all other callers of Rust `exit` block.
Diffstat (limited to 'library/std/src')
-rw-r--r--library/std/src/rt.rs3
-rw-r--r--library/std/src/sys/exit_guard.rs72
-rw-r--r--library/std/src/sys/mod.rs1
-rw-r--r--library/std/src/sys/pal/unix/os.rs1
4 files changed, 77 insertions, 0 deletions
diff --git a/library/std/src/rt.rs b/library/std/src/rt.rs
index b03fa1c01f2..c0a1c5f5594 100644
--- a/library/std/src/rt.rs
+++ b/library/std/src/rt.rs
@@ -144,6 +144,9 @@ fn lang_start_internal(
             rtabort!("drop of the panic payload panicked");
         });
     panic::catch_unwind(cleanup).map_err(rt_abort)?;
+    // Guard against multple threads calling `libc::exit` concurrently.
+    // See the documentation for `unique_thread_exit` for more information.
+    panic::catch_unwind(|| crate::sys::exit_guard::unique_thread_exit()).map_err(rt_abort)?;
     ret_code
 }
 
diff --git a/library/std/src/sys/exit_guard.rs b/library/std/src/sys/exit_guard.rs
new file mode 100644
index 00000000000..5a090f50666
--- /dev/null
+++ b/library/std/src/sys/exit_guard.rs
@@ -0,0 +1,72 @@
+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.
+        /// It is currently unclear whether that is a glibc bug or allowed by the standard.
+        /// To mitigate this problem, we ensure that only one
+        /// Rust thread calls `libc::exit` (or returns from `main`) by calling this function before
+        /// calling `libc::exit` (or returning from `main`).
+        ///
+        /// Technically, this is not enough to ensure soundness, since other code directly calling
+        /// `libc::exit` will still race with this.
+        ///
+        /// *This function does not itself call `libc::exit`.* This is so it can also be used
+        /// to guard returning from `main`.
+        ///
+        /// This function will return only the first time it is called in a process.
+        ///
+        /// * If it is called again on the same thread as the first call, it will abort.
+        /// * If it is called again on a different thread, it will wait in a loop
+        ///   (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 => {
+                    // 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 => {
+                    // 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(_) => {
+                    // 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(); }
+                    }
+                }
+            }
+        }
+    } else {
+        /// Mitigation for <https://github.com/rust-lang/rust/issues/126600>
+        ///
+        /// Mitigation is ***NOT*** implemented on this platform, either because this platform
+        /// is not affected, or because mitigation is not yet implemented for this platform.
+        #[cfg_attr(any(test, doctest), allow(dead_code))]
+        pub(crate) fn unique_thread_exit() {
+            // Mitigation not required on platforms where `exit` is thread-safe.
+        }
+    }
+}
diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs
index 8aa35c40fe0..4092e6080d5 100644
--- a/library/std/src/sys/mod.rs
+++ b/library/std/src/sys/mod.rs
@@ -7,6 +7,7 @@ mod personality;
 
 pub mod backtrace;
 pub mod cmath;
+pub mod exit_guard;
 pub mod os_str;
 pub mod path;
 pub mod sync;
diff --git a/library/std/src/sys/pal/unix/os.rs b/library/std/src/sys/pal/unix/os.rs
index 40e2d1403ef..9adc2b94e59 100644
--- a/library/std/src/sys/pal/unix/os.rs
+++ b/library/std/src/sys/pal/unix/os.rs
@@ -758,6 +758,7 @@ pub fn home_dir() -> Option<PathBuf> {
 }
 
 pub fn exit(code: i32) -> ! {
+    crate::sys::exit_guard::unique_thread_exit();
     unsafe { libc::exit(code as c_int) }
 }