blob: 53fb92ba077e0a19b6a4ef7fe2c2ccd751e7bdee [file] [log] [blame]
cfg_select! {
target_os = "linux" => {
/// 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).
pub fn unique_thread_exit() {
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::io::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. 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")
}
Err(_) => {
// This is not the first thread to call `unique_thread_exit`.
// Pause until the process exits.
loop {
// Safety: libc::pause is safe to call.
unsafe { libc::pause(); }
}
}
}
}
}
_ => {
/// 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), expect(dead_code))]
pub fn unique_thread_exit() {
// Mitigation not required on platforms where `exit` is thread-safe.
}
}
}
pub fn exit(code: i32) -> ! {
cfg_select! {
target_os = "hermit" => {
unsafe { hermit_abi::exit(code) }
}
target_os = "linux" => {
unsafe {
unique_thread_exit();
libc::exit(code)
}
}
target_os = "motor" => {
moto_rt::process::exit(code)
}
all(target_vendor = "fortanix", target_env = "sgx") => {
crate::sys::pal::abi::exit_with_code(code as _)
}
target_os = "solid_asp3" => {
rtabort!("exit({}) called", code)
}
target_os = "teeos" => {
let _ = code;
panic!("TA should not call `exit`")
}
target_os = "uefi" => {
use r_efi::base::Status;
use crate::os::uefi::env;
if let (Some(boot_services), Some(handle)) =
(env::boot_services(), env::try_image_handle())
{
let boot_services = boot_services.cast::<r_efi::efi::BootServices>();
let _ = unsafe {
((*boot_services.as_ptr()).exit)(
handle.as_ptr(),
Status::from_usize(code as usize),
0,
crate::ptr::null_mut(),
)
};
}
crate::intrinsics::abort()
}
any(
target_family = "unix",
target_os = "wasi",
) => {
unsafe { libc::exit(code as crate::ffi::c_int) }
}
target_os = "vexos" => {
let _ = code;
unsafe {
vex_sdk::vexSystemExitRequest();
loop {
vex_sdk::vexTasksRun();
}
}
}
target_os = "windows" => {
unsafe { crate::sys::pal::c::ExitProcess(code as u32) }
}
target_os = "xous" => {
crate::os::xous::ffi::exit(code as u32)
}
_ => {
let _ = code;
crate::intrinsics::abort()
}
}
}