From 91e8c19065758bc76e876df06fb651e39290d894 Mon Sep 17 00:00:00 2001 From: Leon Schuermann Date: Mon, 6 Jul 2026 17:21:29 -0700 Subject: [PATCH] litebox/tls: don't re-tag pointer as unique while aliased Previously, the code for TLS deinitialization ran `Box::from_raw`, even though this function can run in a `Tls::with` closure where another, shared-aliased reference to the underlying storage exists. Instead, we should check the number of users through a raw-pointer read first, which is safe as the TLS can't be shared with other threads and its contents are stored in an `UnsafeCell`, with the `users` field not directly accessible (so there won't be any unsound aliasing). This issue was found by Miri: $ MIRIFLAGS='-Zmiri-permissive-provenance -Zmiri-disable-isolation -Zmiri-ignore-leaks' rustup run nightly cargo miri test -p litebox test_in_use_tls Finished `test` profile [unoptimized + debuginfo] target(s) in 0.25s Running unittests src/lib.rs (target/miri/x86_64-unknown-linux-gnu/debug/deps/litebox-656da94cab53557b) running 1 test test tls::tests::test_in_use_tls - should panic ... error: Undefined Behavior: trying to retag from <277607> for SharedReadOnly permission at alloc108407[0x0], but that tag does not exist in the borrow stack for this location --> $HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967:22 | 967 | let result = crate::intrinsics::read_via_copy(dest); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | this error occurs as part of retag at alloc108407[0x0..0x18] | while retagging field ..0. | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <277607> was created by a SharedReadOnly retag at offsets [0x0..0x8] --> litebox/src/tls.rs:155:22 | 155 | let _guard = crate::utils::defer(|| { | ______________________^ 156 | | tls.users.set(tls.users.get().checked_sub(1).unwrap()); 157 | | }); | |__________^ help: <277607> was later invalidated at offsets [0x0..0x18] by a Unique retag --> litebox/src/tls.rs:125:13 | 125 | Box::from_raw(ptr) | ^^^^^^^^^^^^^^^^^^ = note: this is on thread `tls::tests::tes` = note: stack backtrace: 0: core::mem::replace::> at $HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967:22: 967:60 1: core::option::Option::<{closure@litebox/src/tls.rs:155:42: 155:44}>::take at $HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:1900:9: 1900:33 2: as core::ops::Drop>::drop at litebox/src/utils/mod.rs:122:26: 122:39 3: core::ptr::drop_glue::> - shim(Some(utils::Defer<{closure@litebox/src/tls.rs:155:42: 155:44}>)) at $HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:825:1: 827:25 4: tls::TlsKey::, platform::mock::MockPlatform>::with::<(), {closure@litebox/src/tls.rs:226:23: 226:29}> at litebox/src/tls.rs:159:5: 159:6 5: tls::tests::test_in_use_tls at litebox/src/tls.rs:226:9: 228:11 6: tls::tests::test_in_use_tls::{closure#0} at litebox/src/tls.rs:224:25: 224:2 --- litebox/src/tls.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/litebox/src/tls.rs b/litebox/src/tls.rs index 1a42ddfd51..379b865592 100644 --- a/litebox/src/tls.rs +++ b/litebox/src/tls.rs @@ -117,21 +117,21 @@ impl TlsKey { /// Panics if this TLS is not initialized on this thread, or if it is still /// in use via calls to `with`. pub fn deinit(&'static self) -> T { - // Validate the TLS is set and of the right type before taking it out. - let _ = self.get_ptr(); - let tls = unsafe { + // Validate the TLS is set, of the right type, and has zero users before + // taking it out and re-tagging as a unique (Box'd) pointer: + { + let tls = unsafe { &*self.get_ptr() }; + assert_eq!(tls.users.get(), 0, "tls is still in use on this thread"); + } + + // Because it's a thread-local value, we know that no one else can have + // acquired a TLS reference between the above check and the below + // de-initialization: + unsafe { let ptr = Platform::replace_thread_local_storage(ptr::null_mut()).cast::>(); - Box::from_raw(ptr) - }; - let users = tls.users.get(); - if users != 0 { - // Put it back in case panic unwinds and something is - // referencing it. - unsafe { Platform::replace_thread_local_storage(Box::into_raw(tls).cast()) }; - panic!("tls is still in use on this thread"); + Box::from_raw(ptr).data } - tls.data } fn get_ptr(&'static self) -> *const Tls {