Skip to content

Commit 995aa02

Browse files
committed
Auto merge of #160976 - maxdexh:fix-thread-local-global-alloc-reenter-2, r=<try>
Ensure TLS accesses don't call the global allocator through panic (part 2) try-job: test-various
2 parents c98d0cb + f77d1cb commit 995aa02

8 files changed

Lines changed: 73 additions & 37 deletions

File tree

library/std/src/sys/thread_local/destructors/list.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ pub unsafe fn register(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
1111
rtabort!("the System allocator may not use TLS with destructors")
1212
};
1313
guard::enable();
14+
15+
// Avoid calling the alloc error hook
16+
if dtors.capacity() == dtors.len() {
17+
dtors.try_reserve(1).unwrap_or_else(|_| rtabort!("Failed to grow TLS destructor list"))
18+
}
1419
dtors.push((t, dtor));
1520
}
1621

library/std/src/sys/thread_local/key/tests.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
#![allow(
2+
clippy::arithmetic_side_effects,
3+
clippy::expect_used,
4+
clippy::unwrap_used,
5+
clippy::indexing_slicing,
6+
clippy::panic,
7+
clippy::unreachable,
8+
clippy::unimplemented
9+
)]
10+
111
use super::{LazyKey, get, set};
212
use crate::ptr;
313

library/std/src/sys/thread_local/key/unix.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ pub fn create(dtor: Option<unsafe extern "C" fn(*mut u8)>) -> Key {
3131
key
3232
}
3333

34+
#[cold]
35+
fn fail() -> ! {
36+
rtabort!("Unexpected TLS failure")
37+
}
38+
3439
#[inline]
3540
pub unsafe fn set(key: Key, value: *mut u8) {
36-
#[cold]
37-
fn fail() -> ! {
38-
rtabort!("Failed to set value of thread local")
39-
}
40-
4141
let r = unsafe { libc::pthread_setspecific(key, value as *mut _) };
4242
// May happen on memory exhaustion
4343
if r != 0 {
@@ -54,5 +54,8 @@ pub unsafe fn get(key: Key) -> *mut u8 {
5454
#[inline]
5555
pub unsafe fn destroy(key: Key) {
5656
let r = unsafe { libc::pthread_key_delete(key) };
57-
debug_assert_eq!(r, 0);
57+
// only documented error is for invalid keys
58+
if r != 0 {
59+
fail()
60+
}
5861
}

library/std/src/sys/thread_local/key/windows.rs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ pub struct LazyKey {
4747
once: UnsafeCell<c::INIT_ONCE>,
4848
}
4949

50+
#[cold]
51+
fn fail() -> ! {
52+
rtabort!("Unexpected TLS failure")
53+
}
54+
5055
impl LazyKey {
5156
#[inline]
5257
pub const fn new(dtor: Option<Dtor>) -> LazyKey {
@@ -66,9 +71,9 @@ impl LazyKey {
6671
guard::enable();
6772
}
6873

69-
match self.key.load(Acquire) {
70-
0 => unsafe { self.init() },
71-
key => key - 1,
74+
match self.key.load(Acquire).checked_sub(1) {
75+
None => unsafe { self.init() },
76+
Some(dec) => dec,
7277
}
7378
}
7479

@@ -79,11 +84,13 @@ impl LazyKey {
7984
let r = unsafe {
8085
c::InitOnceBeginInitialize(self.once.get(), 0, &mut pending, ptr::null_mut())
8186
};
82-
assert_eq!(r, c::TRUE);
87+
if r != c::TRUE {
88+
fail()
89+
}
8390

8491
if pending == c::FALSE {
8592
// Some other thread initialized the key, load it.
86-
self.key.load(Relaxed) - 1
93+
self.key.load(Relaxed).wrapping_sub(1)
8794
} else {
8895
let key = unsafe { c::TlsAlloc() };
8996
if key == c::TLS_OUT_OF_INDEXES {
@@ -104,10 +111,12 @@ impl LazyKey {
104111
// and if that sees this write then it will entirely bypass the `InitOnce`. We thus
105112
// need to establish synchronization through `key`. In particular that acquire load
106113
// must happen-after the register_dtor above, to ensure the dtor actually runs!
107-
self.key.store(key + 1, Release);
114+
self.key.store(key.wrapping_add(1), Release);
108115

109116
let r = unsafe { c::InitOnceComplete(self.once.get(), 0, ptr::null_mut()) };
110-
debug_assert_eq!(r, c::TRUE);
117+
if r != c::TRUE {
118+
fail()
119+
}
111120

112121
key
113122
}
@@ -119,14 +128,16 @@ impl LazyKey {
119128
rtabort!("out of TLS indexes");
120129
}
121130

122-
match self.key.compare_exchange(0, key + 1, AcqRel, Acquire) {
131+
match self.key.compare_exchange(0, key.wrapping_add(1), AcqRel, Acquire) {
123132
Ok(_) => key,
124133
Err(new) => unsafe {
125134
// Some other thread completed initialization first, so destroy
126135
// our key and use theirs.
127136
let r = c::TlsFree(key);
128-
debug_assert_eq!(r, c::TRUE);
129-
new - 1
137+
if r != c::TRUE {
138+
fail()
139+
}
140+
new.wrapping_sub(1)
130141
},
131142
}
132143
}
@@ -138,10 +149,6 @@ unsafe impl Sync for LazyKey {}
138149

139150
#[inline]
140151
pub unsafe fn set(key: Key, val: *mut u8) {
141-
#[cold]
142-
fn fail() -> ! {
143-
rtabort!("Failed to set value of thread local")
144-
}
145152
let r = unsafe { c::TlsSetValue(key, val.cast()) };
146153
// According to MS documentation, `TlsSetValue` returns zero "if it fails"
147154
if r != c::TRUE {
@@ -181,22 +188,21 @@ pub unsafe fn run_dtors() {
181188
let mut cur = DTORS.load(Acquire);
182189
while !cur.is_null() {
183190
let pre_key = unsafe { (*cur).key.load(Acquire) };
184-
let dtor = unsafe { (*cur).dtor.unwrap() };
191+
let dtor = unsafe { rtunwrap!(Some, (*cur).dtor) };
185192
cur = unsafe { (*cur).next.load(Relaxed) };
186193

187194
// In LazyKey::init, we register the dtor before setting `key`.
188195
// So if one thread's `run_dtors` races with another thread executing `init` on the same
189196
// `LazyKey`, we can encounter a key of 0 here. That means this key was never
190197
// initialized in this thread so we can safely skip it.
191-
if pre_key == 0 {
198+
let Some(key) = pre_key.checked_sub(1) else {
192199
continue;
193-
}
200+
};
201+
194202
// If this is non-zero, then via the `Acquire` load above we synchronized with
195203
// everything relevant for this key. (It's not clear that this is needed, since the
196204
// release-acquire pair on DTORS also establishes synchronization, but better safe than
197205
// sorry.)
198-
let key = pre_key - 1;
199-
200206
let ptr = unsafe { c::TlsGetValue(key) };
201207
if !ptr.is_null() {
202208
unsafe {

library/std/src/sys/thread_local/key/xous.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ fn tls_table_slow() -> &'static mut [*mut u8] {
104104
TLS_MEMORY_SIZE / size_of::<*mut u8>(),
105105
MemoryFlags::R | MemoryFlags::W,
106106
)
107-
.expect("Unable to allocate memory for thread local storage")
107+
.unwrap_or_else(|_| rtabort!("Unable to allocate memory for thread local storage"))
108108
};
109109

110110
for val in tp.iter() {
111-
assert!((*val).is_null());
111+
rtassert!((*val).is_null());
112112
}
113113

114114
unsafe {
@@ -136,13 +136,14 @@ pub fn create(dtor: Option<Dtor>) -> Key {
136136
pub unsafe fn set(key: Key, value: *mut u8) {
137137
rtassert!((key < 1022) && (key >= 1));
138138
let table = tls_table();
139-
table[key] = value;
139+
*rtunwrap!(Some, table.get_mut(key)) = value;
140140
}
141141

142142
#[inline]
143143
pub unsafe fn get(key: Key) -> *mut u8 {
144144
rtassert!((key < 1022) && (key >= 1));
145-
tls_table()[key]
145+
let table = tls_table();
146+
*rtunwrap!(Some, table.get(key))
146147
}
147148

148149
#[inline]
@@ -186,10 +187,10 @@ pub unsafe fn destroy_tls() {
186187
unsafe { run_dtors() };
187188

188189
// Finally, free the TLS array
189-
unsafe {
190+
let result = unsafe {
190191
unmap_memory(core::slice::from_raw_parts_mut(tp, TLS_MEMORY_SIZE / size_of::<usize>()))
191-
.unwrap()
192192
};
193+
rtunwrap!(Ok, result);
193194
}
194195

195196
// This is marked inline(never) to prevent dealloc calls from being reordered

library/std/src/sys/thread_local/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222
reason = "internal details of the thread_local macro",
2323
issue = "none"
2424
)]
25+
#![deny(
26+
clippy::arithmetic_side_effects,
27+
clippy::expect_used,
28+
clippy::unwrap_used,
29+
clippy::indexing_slicing,
30+
clippy::panic,
31+
clippy::unreachable,
32+
clippy::unimplemented,
33+
reason = "TLS accesses must not call the global allocator, including via panic (#160930)"
34+
)]
2535

2636
cfg_select! {
2737
any(

library/std/src/sys/thread_local/no_threads.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ impl<T> LazyStorage<T> {
9595
let value = i.and_then(Option::take).unwrap_or_else(f);
9696

9797
// Destroy the old value if it is initialized
98-
// FIXME(#110897): maybe panic on recursive initialization.
98+
// FIXME(#110897): maybe abort on recursive initialization.
9999
if self.state.get() == State::Alive {
100100
self.state.set(State::Destroying);
101101
// Safety: we check for no initialization during drop below
@@ -107,7 +107,7 @@ impl<T> LazyStorage<T> {
107107

108108
// Guard against initialization during drop
109109
if self.state.get() == State::Destroying {
110-
panic!("Attempted to initialize thread-local while it is being dropped");
110+
rtabort!("Attempted to initialize thread-local while it is being dropped");
111111
}
112112

113113
unsafe {

library/std/src/sys/thread_local/os.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::key::{Key, LazyKey, get, set};
22
use super::{abort_on_dtor_unwind, guard};
3-
use crate::alloc::{self, GlobalAlloc, Layout, System};
3+
use crate::alloc::{GlobalAlloc, Layout, System};
44
use crate::cell::Cell;
55
use crate::marker::PhantomData;
66
use crate::mem::ManuallyDrop;
@@ -103,13 +103,14 @@ struct AlignedSystemBox<T: 'static, const ALIGN: usize> {
103103
impl<T: 'static, const ALIGN: usize> AlignedSystemBox<T, ALIGN> {
104104
#[inline]
105105
fn new(v: Value<T>) -> Self {
106-
let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
106+
let layout = rtunwrap!(Ok, Layout::new::<Value<T>>().align_to(ALIGN));
107107

108108
// We use the System allocator here to avoid interfering with a potential
109109
// Global allocator using thread-local storage.
110110
let ptr: *mut Value<T> = (unsafe { System.alloc(layout) }).cast();
111111
let Some(ptr) = NonNull::new(ptr) else {
112-
alloc::handle_alloc_error(layout);
112+
// Do not call the alloc error hook here. It may allocate!
113+
rtabort!("Allocation failure");
113114
};
114115
unsafe { ptr.write(v) };
115116
Self { ptr }
@@ -139,7 +140,7 @@ impl<T: 'static, const ALIGN: usize> Deref for AlignedSystemBox<T, ALIGN> {
139140
impl<T: 'static, const ALIGN: usize> Drop for AlignedSystemBox<T, ALIGN> {
140141
#[inline]
141142
fn drop(&mut self) {
142-
let layout = Layout::new::<Value<T>>().align_to(ALIGN).unwrap();
143+
let layout = rtunwrap!(Ok, Layout::new::<Value<T>>().align_to(ALIGN));
143144

144145
unsafe {
145146
let unwind_result = catch_unwind(AssertUnwindSafe(|| self.ptr.drop_in_place()));

0 commit comments

Comments
 (0)