Skip to content

Windows shutdown crash: TLS destructor accesses memory released by VirtualVector #885

Description

@DamoyY

Summary

On Windows, a Rust process using snmalloc-rs as its global allocator can crash during process shutdown when a thread-local value is dropped. The crash is reproducible with a thread-local Arc and does not require Tokio or any application-specific code.

This appears to be an ordering problem: snmalloc's Windows VirtualVector reservation destructor releases allocator memory before Rust's Windows TLS destructor drops the thread-local value. The TLS drop then performs an atomic decrement through memory that has already been released.

Environment

  • snmalloc commit: e9f7b2ebd11fcadd5913c95d90770902e8126646 (snmalloc-rs 0.7.5)
  • Rust: rustc 1.100.0-nightly (5ceaf6608 2026-09-25)
  • Target: x86_64-pc-windows-msvc
  • MSVC toolchain
  • Windows 11 Insider Preview, build 10.0.29671

Minimal reproduction

use std::sync::Arc;

#[global_allocator]
static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc;

thread_local! {
    static VALUE: Arc<usize> = Arc::new(42);
}

fn main() {
    VALUE.with(|value| assert_eq!(**value, 42));
    println!("main completed");
}

Cargo.toml dependency:

[dependencies]
snmalloc-rs = { git = "https://github.com/microsoft/snmalloc.git", rev = "e9f7b2ebd11fcadd5913c95d90770902e8126646" }

Build and run on Windows:

cargo run

Observed output:

main completed
error: process didn't exit successfully (...snmalloc_tls.exe)
(exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

The process exits successfully when the #[global_allocator] declaration is removed.

Debugger evidence

The crash is deterministic in both debug and release builds. The failing instruction is Rust's atomic decrement while dropping the TLS value:

snmalloc_tls!core::sync::atomic::atomic_sub<usize,usize>+0x60
snmalloc_tls!alloc::sync::impl$42::drop<usize,alloc::alloc::Global>
snmalloc_tls!core::ptr::drop_glue<alloc::sync::Arc<usize,alloc::alloc::Global>>
snmalloc_tls!std::sys::thread_local::native::lazy::destroy<alloc::sync::Arc<usize,alloc::alloc::Global>>
ntdll!RtlpFlsDataCleanup

The accessed address is already in a MEM_FREE region. A breakpoint on VirtualFree shows the preceding calls originate from:

snmalloc::VirtualVector::~VirtualVector
snmalloc::`dynamic atexit destructor for 'reservations''

Skipping the VirtualVector destructor in the debugger makes the same binary exit with code 0, which confirms that the shutdown ordering is involved.

Questions

Is this shutdown ordering expected to be supported for Rust TLS values? If so, could the Windows reservation cleanup be deferred until after Rust TLS destructors, or otherwise avoid releasing allocator metadata while TLS destructors can still allocate or deallocate? A documented opt-out for allocator finalization would also be useful for applications where the OS will reclaim memory at process exit.

This is related to #809, but the reproduction here is an immediate use-after-free crash rather than slow post-main cleanup.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions