@@ -21,6 +21,12 @@ impl Signal {
2121 set = cvar. wait ( set) . unwrap ( ) ;
2222 }
2323 }
24+
25+ fn is_set ( & self ) -> bool {
26+ let ( set, _cvar) = & * self . 0 ;
27+ let set = set. lock ( ) . unwrap ( ) ;
28+ * set
29+ }
2430}
2531
2632struct NotifyOnDrop ( Signal ) ;
@@ -97,6 +103,7 @@ fn smoke_dtor() {
97103 } ) ;
98104 } ) ;
99105 signal. wait ( ) ;
106+ assert ! ( signal. is_set( ) ) ;
100107 t. join ( ) . unwrap ( ) ;
101108 }
102109}
@@ -393,3 +400,51 @@ fn thread_current_in_dtor() {
393400 let name = name. as_ref ( ) . unwrap ( ) ;
394401 assert_eq ! ( name, "test" ) ;
395402}
403+
404+ // Test that if a thread uses fibers while the dtors callback is running,
405+ // we do NOT trigger dtors.
406+ // This prevents a UAF if a fiber is the first to use Tls and is deleted,
407+ // like in the example here:
408+ // https://github.com/rust-lang/rust/pull/148799#issuecomment-3731806901
409+ #[ cfg( target_os = "windows" ) ]
410+ #[ test]
411+ fn fiber_does_not_trigger_dtor ( ) {
412+ use core:: ffi:: c_void;
413+ use std:: ptr;
414+
415+ unsafe extern "system" {
416+ fn ConvertFiberToThread ( ) -> i32 ;
417+ fn ConvertThreadToFiber ( lpParameter : * const c_void ) -> * mut c_void ;
418+ }
419+
420+ thread_local ! ( static FOO : UnsafeCell <Option <NotifyOnDrop >> = UnsafeCell :: new( None ) ) ;
421+ let signal = Signal :: default ( ) ;
422+
423+ let signal2 = signal. clone ( ) ;
424+ let t = thread:: spawn ( move || unsafe {
425+ let mut signal = Some ( signal2) ;
426+ FOO . with ( |f| {
427+ * f. get ( ) = Some ( NotifyOnDrop ( signal. take ( ) . unwrap ( ) ) ) ;
428+ } ) ;
429+ let _main = ConvertThreadToFiber ( ptr:: null ( ) ) ;
430+ // A user can then switch to a new fiber and delete the main fiber.
431+ // If destructors are triggered when the fiber is deleted,
432+ // the new fiber will be able to observe already-destructed values.
433+ } ) ;
434+ t. join ( ) . unwrap ( ) ;
435+ assert ! ( !signal. is_set( ) ) ;
436+
437+ // As long as we stop using fibers before thread teardown, everything works as expected.
438+ let signal2 = signal. clone ( ) ;
439+ let t = thread:: spawn ( move || unsafe {
440+ let mut signal = Some ( signal2) ;
441+ FOO . with ( |f| {
442+ * f. get ( ) = Some ( NotifyOnDrop ( signal. take ( ) . unwrap ( ) ) ) ;
443+ } ) ;
444+ let _ = ConvertThreadToFiber ( ptr:: null ( ) ) ;
445+ let _ = ConvertFiberToThread ( ) ;
446+ } ) ;
447+ signal. wait ( ) ;
448+ assert ! ( signal. is_set( ) ) ;
449+ t. join ( ) . unwrap ( ) ;
450+ }
0 commit comments