@@ -757,7 +757,87 @@ pub fn home_dir() -> Option<PathBuf> {
757757 }
758758}
759759
760+ /// Mitigation for https://github.com/rust-lang/rust/issues/126600
761+ ///
762+ /// Ensure that only one Rust thread calls `libc::exit` (or returns from `main`) by
763+ /// calling this function before calling `libc::exit` (or returning from `main`).
764+ /// Technically not enough to ensure soundness, since other code directly calling
765+ /// libc::exit will still race with this.
766+ ///
767+ /// *This function does not itself call `libc::exit`.* This is so it can also be used
768+ /// to guard returning from `main`.
769+ ///
770+ /// This function will return only the first time it is called in a process.
771+ ///
772+ /// * If it is called again on the same thread as the first call, it will abort.
773+ /// * If it is called again on a different thread, it will `thread::park()` in a loop
774+ /// (waiting for the process to exit).
775+ /// * If it is called in a situation where `std::thread::current()` fails, it will abort.
776+ pub ( crate ) fn unique_thread_exit ( ) {
777+ let this_thread_id =
778+ crate :: thread:: try_current ( ) . unwrap_or_else ( || crate :: process:: abort ( ) ) . id ( ) . as_u64 ( ) . get ( ) ;
779+ // Defense against refactors of `.as_u64()`
780+ debug_assert_ne ! ( this_thread_id, 0 , "thread ID cannot be zero" ) ;
781+ #[ cfg( target_has_atomic = "64" ) ]
782+ {
783+ use crate :: sync:: atomic:: { AtomicU64 , Ordering } ;
784+ static EXITING_THREAD_ID : AtomicU64 = AtomicU64 :: new ( 0 ) ;
785+ match EXITING_THREAD_ID . compare_exchange (
786+ 0 ,
787+ this_thread_id,
788+ Ordering :: Relaxed ,
789+ Ordering :: Relaxed ,
790+ ) {
791+ Ok ( _zero) => {
792+ // This is the first thread to call `unique_thread_exit`,
793+ // and this is the first time it is called.
794+ // Set EXITING_THREAD_ID to this thread's ID (done by the
795+ // compare_exchange) and return.
796+ }
797+ Err ( id) if id == this_thread_id => {
798+ // This is the first thread to call `unique_thread_exit`,
799+ // but this is the second time it is called.
800+ // Abort the process.
801+ crate :: process:: abort ( ) ;
802+ }
803+ Err ( _) => {
804+ // This is not the first thread to call `unique_thread_exit`.
805+ // Park until the process exits.
806+ loop {
807+ crate :: thread:: park ( ) ;
808+ }
809+ }
810+ }
811+ }
812+ #[ cfg( not( target_has_atomic = "64" ) ) ]
813+ {
814+ use crate :: sync:: { Mutex , PoisonError } ;
815+ static EXITING_THREAD_ID : Mutex < u64 > = Mutex :: new ( 0 ) ;
816+ let mut exiting_thread_id =
817+ EXITING_THREAD_ID . lock ( ) . unwrap_or_else ( PoisonError :: into_inner) ;
818+ if * exiting_thread_id == 0 {
819+ // This is the first thread to call `unique_thread_exit`,
820+ // and this is the first time it is called.
821+ // Set EXITING_THREAD_ID to this thread's ID and return.
822+ * exiting_thread_id = this_thread_id;
823+ } else if * exiting_thread_id == this_thread_id {
824+ // This is the first thread to call `unique_thread_exit`,
825+ // but this is the second time it is called.
826+ // Abort the process.
827+ crate :: process:: abort ( ) ;
828+ } else {
829+ // This is not the first thread to call `unique_thread_exit`.
830+ // Park until the process exits.
831+ drop ( exiting_thread_id) ;
832+ loop {
833+ crate :: thread:: park ( ) ;
834+ }
835+ }
836+ }
837+ }
838+
760839pub fn exit ( code : i32 ) -> ! {
840+ unique_thread_exit ( ) ;
761841 unsafe { libc:: exit ( code as c_int ) }
762842}
763843
0 commit comments