Skip to main content

std/sys/
exit.rs

1cfg_select! {
2    target_os = "linux" => {
3        /// Mitigation for <https://github.com/rust-lang/rust/issues/126600>
4        ///
5        /// On glibc, `libc::exit` has been observed to not always be thread-safe.
6        /// It is currently unclear whether that is a glibc bug or allowed by the standard.
7        /// To mitigate this problem, we ensure that only one
8        /// Rust thread calls `libc::exit` (or returns from `main`) by calling this function before
9        /// calling `libc::exit` (or returning from `main`).
10        ///
11        /// Technically, this is not enough to ensure soundness, since other code directly calling
12        /// `libc::exit` will still race with this.
13        ///
14        /// *This function does not itself call `libc::exit`.* This is so it can also be used
15        /// to guard returning from `main`.
16        ///
17        /// This function will return only the first time it is called in a process.
18        ///
19        /// * If it is called again on the same thread as the first call, it will abort.
20        /// * If it is called again on a different thread, it will wait in a loop
21        ///   (waiting for the process to exit).
22        pub fn unique_thread_exit() {
23            use crate::ffi::c_int;
24            use crate::ptr;
25            use crate::sync::atomic::AtomicPtr;
26            use crate::sync::atomic::Ordering::{Acquire, Relaxed};
27
28            static EXITING_THREAD_ID: AtomicPtr<c_int> = AtomicPtr::new(ptr::null_mut());
29
30            // We use the address of `errno` as a cheap and safe way to identify
31            // threads. As the C standard mandates that `errno` must have thread
32            // storage duration, we can rely on its address not changing over the
33            // lifetime of the thread. Additionally, accesses to `errno` are
34            // async-signal-safe, so this function is available in all imaginable
35            // circumstances.
36            let this_thread_id = crate::sys::io::errno_location();
37            match EXITING_THREAD_ID.compare_exchange(ptr::null_mut(), this_thread_id, Acquire, Relaxed) {
38                Ok(_) => {
39                    // This is the first thread to call `unique_thread_exit`,
40                    // and this is the first time it is called. Continue exiting.
41                }
42                Err(exiting_thread_id) if exiting_thread_id == this_thread_id => {
43                    // This is the first thread to call `unique_thread_exit`,
44                    // but this is the second time it is called.
45                    // Abort the process.
46                    core::panicking::panic_nounwind("std::process::exit called re-entrantly")
47                }
48                Err(_) => {
49                    // This is not the first thread to call `unique_thread_exit`.
50                    // Pause until the process exits.
51                    loop {
52                        // Safety: libc::pause is safe to call.
53                        unsafe { libc::pause(); }
54                    }
55                }
56            }
57        }
58    }
59    _ => {
60        /// Mitigation for <https://github.com/rust-lang/rust/issues/126600>
61        ///
62        /// Mitigation is ***NOT*** implemented on this platform, either because this platform
63        /// is not affected, or because mitigation is not yet implemented for this platform.
64        #[cfg_attr(any(test, doctest), expect(dead_code))]
65        pub fn unique_thread_exit() {
66            // Mitigation not required on platforms where `exit` is thread-safe.
67        }
68    }
69}
70
71pub fn exit(code: i32) -> ! {
72    cfg_select! {
73        target_os = "hermit" => {
74            unsafe { hermit_abi::exit(code) }
75        }
76        target_os = "linux" => {
77            unsafe {
78                unique_thread_exit();
79                libc::exit(code)
80            }
81        }
82        target_os = "motor" => {
83            moto_rt::process::exit(code)
84        }
85        all(target_vendor = "fortanix", target_env = "sgx") => {
86            crate::sys::pal::abi::exit_with_code(code as _)
87        }
88        target_os = "solid_asp3" => {
89            rtabort!("exit({}) called", code)
90        }
91        target_os = "teeos" => {
92            let _ = code;
93            panic!("TA should not call `exit`")
94        }
95        target_os = "uefi" => {
96            use r_efi::base::Status;
97
98            use crate::os::uefi::env;
99
100            if let (Some(boot_services), Some(handle)) =
101                (env::boot_services(), env::try_image_handle())
102            {
103                let boot_services = boot_services.cast::<r_efi::efi::BootServices>();
104                let _ = unsafe {
105                    ((*boot_services.as_ptr()).exit)(
106                        handle.as_ptr(),
107                        Status::from_usize(code as usize),
108                        0,
109                        crate::ptr::null_mut(),
110                    )
111                };
112            }
113            crate::intrinsics::abort()
114        }
115        any(
116            target_family = "unix",
117            target_os = "wasi",
118        ) => {
119            unsafe { libc::exit(code as crate::ffi::c_int) }
120        }
121        target_os = "vexos" => {
122            let _ = code;
123
124            unsafe {
125                vex_sdk::vexSystemExitRequest();
126
127                loop {
128                    vex_sdk::vexTasksRun();
129                }
130            }
131        }
132        target_os = "windows" => {
133            unsafe { crate::sys::pal::c::ExitProcess(code as u32) }
134        }
135        target_os = "xous" => {
136            crate::os::xous::ffi::exit(code as u32)
137        }
138        _ => {
139            let _ = code;
140            crate::intrinsics::abort()
141        }
142    }
143}