More global allocator reentrancy issues; this time `thread_local`
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Like #160837 and #160793
Edit: This issue affects a lot more code than I initially thought. Until we have a way to auto detect unknown/panicking/allocating code being reachable (these are all equivalent), it's probably for the best to keep it open.
GlobalAlloc guarantees that implementors can use thread_local for global allocators without being called reentrantly. Therefore, a panic from the implementation of TLS (thread local storage) in std violates that guarantee, as it results in a call to the global allocator. This is what I will explore in this issue.
Note that thread locals are generally lazy and can have destructors. Obviously noone expects those to somehow avoid the global allocator if they call the global allocator in the initializer or Drop impl. However, if the user specifies a thread local that does not involve the global allocator, then our guarantee better hold.
There are a number of different TLS implementations used in the standard library. For targets with platform support for TLS with destructors (cfg(target_thread_local)), we use native module, which defers to the #[thread_local] attr, which (IIUC) defers to LLVM. I did not find any global allocator calls in that code.
code that selects the TLS impl
For non-single-threaded platforms without such support (for example windows), the implementation lives in the sys::thread_local::os submodule. It implements TLS on top of some items provided for the platform: a Key type and some functions like unsafe fn set(key: Key, value: *mut u8). Note that due to laziness, both get and set can potentially be called from the global allocator.
get and set can cause panics on the following platforms:
unix
https://github.com/rust-lang/rust/blob/a04c7a037d59b6dedc9f921e84e391f13a257aa4/library/std/src/sys/thread_local/key/unix.rs#L35-L38
pthread_setspecific may return nonzero from memory exhaustion (docs).
windows
https://github.com/rust-lang/rust/blob/a04c7a037d59b6dedc9f921e84e391f13a257aa4/library/std/src/sys/thread_local/key/windows.rs#L140-L143
TlsSetValue may return zero "if it fails" (thank you microsoft)
xous
https://github.com/rust-lang/rust/blob/a04c7a037d59b6dedc9f921e84e391f13a257aa4/library/std/src/sys/thread_local/key/xous.rs#L136-L146
No idea if these can actually happen. Should still be aborts instead.
The other implementation, namely sgx, does it right by using rtassert/rtabort, which do not call the global allocator.
@rustbot label A-thread T-libs I-unsound A-global-allocator-reentry
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the TLS implementation selector in library/std/src/sys/thread_local/mod.rs, then trace the failure paths in key/unix.rs, key/windows.rs, and key/xous.rs. Compare them with the sgx implementation's rtassert/rtabort handling and the GlobalAlloc re-entrance guarantee. Done means the affected TLS paths handle failures without re-entering the global allocator, with platform-specific behavior verified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100