Skip to content

Wrap COM_OBJECTS in ManuallyDrop to prevent crash during TLS teardown#110

Open
yuyoyuppe wants to merge 1 commit intoCiantic:rustfrom
yuyoyuppe:origin_main
Open

Wrap COM_OBJECTS in ManuallyDrop to prevent crash during TLS teardown#110
yuyoyuppe wants to merge 1 commit intoCiantic:rustfrom
yuyoyuppe:origin_main

Conversation

@yuyoyuppe
Copy link
Copy Markdown

Description

During process exit, Rust TLS destructors fire after combase.dll's own thread-local state has been torn down. IUnknown::Release on the cached COM proxies then crashes in combase!TLSAddToMap. Wrapping the thread_local in ManuallyDrop suppresses the destructor entirely, safely leaking the objects. The OS reclaims all memory at process exit and the COM server detects dead clients via OXID pings.

Crash rerpo

You can easily reproduce the issue if you do something like this:

fn main() -> Result<(), winvd::Error> {
    let desktops = winvd::get_desktops()?;
    for (i, desktop) in desktops.iter().enumerate() {
        let name = desktop.get_name()?;
        let id = desktop.get_id()?;
        println!("Desktop {i}: name={name:?} id={id:?}");
    }
    Ok(())
}

And launch it from windbg:

[0x0]   combase!TLSAddToMap+0x6d   0x70eb0ff1b0   0x7ffeb42b9be2   
[0x1]   combase!TLSPreallocateData+0xaf   (Inline Function)   (Inline Function)   
[0x2]   combase!COleTls::TLSAllocData+0xd2   0x70eb0ff1e0   0x7ffeb42e0f02   
[0x3]   combase!InternalTlsAllocData+0xa   (Inline Function)   (Inline Function)   
[0x4]   combase!COleTls::{ctor}+0xc7   (Inline Function)   (Inline Function)   
[0x5]   combase!GetCurrentApartmentId+0xc7   (Inline Function)   (Inline Function)   
[0x6]   combase!CStdIdentity::IsCallableFromCurrentApartment+0xd8   (Inline Function)   (Inline Function)   
[0x7]   combase!CStdIdentity::CInternalUnk::ReleaseWithCallerAddress+0x133   (Inline Function)   (Inline Function)   
[0x8]   combase!CStdIdentity::CInternalUnk::Release+0x142   0x70eb0ff210   0x7ff7427ee8a6   
[0x9]   winvd_smoke!windows_core::unknown::impl$2::drop+0xe6   0x70eb0ff240   0x7ff7427eed1e   
[0xa]   winvd_smoke!core::ptr::drop_in_place<windows_core::unknown::IUnknown>+0xe   0x70eb0ff2b0   0x7ff7427e9cee   
[0xb]   winvd_smoke!core::ptr::drop_in_place<winvd::interfaces::IServiceProvider>+0xe   0x70eb0ff2e0   0x7ff7427ed398   
[0xc]   winvd_smoke!alloc::rc::Rc<winvd::interfaces::IServiceProvider,alloc::alloc::Global>::drop_slow<winvd::interfaces::IServiceProvider,alloc::alloc::Global>+0x38   0x70eb0ff310   0x7ff7427ed75d   
[0xd]   winvd_smoke!alloc::rc::impl$35::drop<winvd::interfaces::IServiceProvider,alloc::alloc::Global>+0x3d   0x70eb0ff370   0x7ff7427e9dce   
[0xe]   winvd_smoke!core::ptr::drop_in_place<alloc::rc::Rc<winvd::interfaces::IServiceProvider,alloc::alloc::Global> >+0xe   0x70eb0ff3c0   0x7ff7427e94e5   
[0xf]   winvd_smoke!core::ptr::drop_in_place<enum2$<core::option::Option<alloc::rc::Rc<winvd::interfaces::IServiceProvider,alloc::alloc::Global> > > >+0x35   0x70eb0ff3f0   0x7ff7427e97be   
[0x10]   winvd_smoke!core::ptr::drop_in_place<core::cell::UnsafeCell<enum2$<core::option::Option<alloc::rc::Rc<winvd::interfaces::IServiceProvider,alloc::alloc::Global> > > > >+0xe   0x70eb0ff430   0x7ff7427e9762   
[0x11]   winvd_smoke!core::ptr::drop_in_place<core::cell::RefCell<enum2$<core::option::Option<alloc::rc::Rc<winvd::interfaces::IServiceProvider,alloc::alloc::Global> > > > >+0x12   0x70eb0ff460   0x7ff7427e9b6f   
[0x12]   winvd_smoke!core::ptr::drop_in_place<winvd::comobjects::ComObjects>+0x1f   0x70eb0ff490   0x7ff7427ecada   
[0x13]   winvd_smoke!std::sys::thread_local::native::lazy::destroy::closure$0<winvd::comobjects::ComObjects>+0x3a   0x70eb0ff4e0   0x7ff7427ebe5b   
[0x14]   winvd_smoke!std::sys::thread_local::abort_on_dtor_unwind<std::sys::thread_local::native::lazy::destroy::closure_env$0<winvd::comobjects::ComObjects> >+0x1b   0x70eb0ff530   0x7ff7427eca6f   
[0x15]   winvd_smoke!std::sys::thread_local::native::lazy::destroy<winvd::comobjects::ComObjects>+0x1f   0x70eb0ff580   0x7ff7427f7e11   
[0x16]   winvd_smoke!std::sys::thread_local::destructors::list::run+0x60   (Inline Function)   (Inline Function)   
[0x17]   winvd_smoke!std::sys::thread_local::guard::windows::tls_callback+0x81   0x70eb0ff5c0   0x7ffeb4d699bb   
[0x18]   ntdll!ImageTlsCallbackCaller+0x1b   0x70eb0ff610   0x7ffeb4ddf73a   
[0x19]   ntdll!LdrpCallInitRoutineInternal+0x22   0x70eb0ff640   0x7ffeb4c8bc33   
[0x1a]   ntdll!LdrpCallInitRoutine+0x93   0x70eb0ff670   0x7ffeb4c8bfde   
[0x1b]   ntdll!LdrpCallTlsInitializers+0x19e   0x70eb0ff940   0x7ffeb4d0d570   
[0x1c]   ntdll!LdrShutdownProcess+0x2f0   0x70eb0ffa10   0x7ffeb4d0c5ce   
[0x1d]   ntdll!RtlExitUserProcess+0x9e   0x70eb0ffb20   0x7ffeb37f18ab   
[0x1e]   KERNEL32!ExitProcessImplementation+0xb   0x70eb0ffb50   0x7ffeb21d0093   
[0x1f]   ucrtbase!common_exit+0xc7   0x70eb0ffb80   0x7ff74280993f   
[0x20]   winvd_smoke!__scrt_common_main_seh+0x173   0x70eb0ffbe0   0x7ffeb37de8d7   
[0x21]   KERNEL32!BaseThreadInitThunk+0x17   0x70eb0ffc20   0x7ffeb4d0c48c   
[0x22]   ntdll!RtlUserThreadStart+0x2c   0x70eb0ffc50   0x0   

During process exit, Rust TLS destructors fire after combase.dll's own thread-local state has been torn down. IUnknown::Release on the cached COM proxies then crashes in combase!TLSAddToMap. Wrapping the thread_local in ManuallyDrop suppresses the destructor entirely, safely leaking the objects. The OS reclaims all memory at process exit and the COM server detects dead clients via OXID pings.
@Ciantic
Copy link
Copy Markdown
Owner

Ciantic commented Mar 26, 2026

Hmm, this seems fine, I will see when I have time to check and merge, I haven't seen it crash on my apps though.

@yuyoyuppe
Copy link
Copy Markdown
Author

I have it 100% of the time on both of my 25H2 machines. Note that it happens late in the process deinit routine, so if you're unloading a .dll with VDA, you won't experience it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants