Auto merge of #116402 - joboet:global_alloc_tls_unsoundness, r=thomcc,workingjubilee

Panic when the global allocator tries to register a TLS destructor

Using a `RefCell` avoids the undefined behaviour encountered in #116390 and reduces the amount of `unsafe` code in the codebase.
This commit is contained in:
bors 2023-10-19 00:03:42 +00:00
commit 89432aadcb
5 changed files with 51 additions and 29 deletions

View File

@ -5,23 +5,25 @@
// The this solution works like the implementation of macOS and // The this solution works like the implementation of macOS and
// doesn't additional OS support // doesn't additional OS support
use crate::mem; use crate::cell::RefCell;
#[thread_local] #[thread_local]
static mut DTORS: Vec<(*mut u8, unsafe extern "C" fn(*mut u8))> = Vec::new(); static DTORS: RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>> = RefCell::new(Vec::new());
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
let list = &mut DTORS; match DTORS.try_borrow_mut() {
list.push((t, dtor)); Ok(mut dtors) => dtors.push((t, dtor)),
Err(_) => rtabort!("global allocator may not use TLS"),
}
} }
// every thread call this function to run through all possible destructors // every thread call this function to run through all possible destructors
pub unsafe fn run_dtors() { pub unsafe fn run_dtors() {
let mut list = mem::take(&mut DTORS); let mut list = DTORS.take();
while !list.is_empty() { while !list.is_empty() {
for (ptr, dtor) in list { for (ptr, dtor) in list {
dtor(ptr); dtor(ptr);
} }
list = mem::take(&mut DTORS); list = DTORS.take();
} }
} }

View File

@ -4,14 +4,13 @@
// Simplify dtor registration by using a list of destructors. // Simplify dtor registration by using a list of destructors.
use super::{abi, itron::task}; use super::{abi, itron::task};
use crate::cell::Cell; use crate::cell::{Cell, RefCell};
use crate::mem;
#[thread_local] #[thread_local]
static REGISTERED: Cell<bool> = Cell::new(false); static REGISTERED: Cell<bool> = Cell::new(false);
#[thread_local] #[thread_local]
static mut DTORS: Vec<(*mut u8, unsafe extern "C" fn(*mut u8))> = Vec::new(); static DTORS: RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>> = RefCell::new(Vec::new());
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
if !REGISTERED.get() { if !REGISTERED.get() {
@ -22,18 +21,20 @@ pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
REGISTERED.set(true); REGISTERED.set(true);
} }
let list = unsafe { &mut DTORS }; match DTORS.try_borrow_mut() {
list.push((t, dtor)); Ok(mut dtors) => dtors.push((t, dtor)),
Err(_) => rtabort!("global allocator may not use TLS"),
}
} }
pub unsafe fn run_dtors() { pub unsafe fn run_dtors() {
let mut list = mem::take(unsafe { &mut DTORS }); let mut list = DTORS.take();
while !list.is_empty() { while !list.is_empty() {
for (ptr, dtor) in list { for (ptr, dtor) in list {
unsafe { dtor(ptr) }; unsafe { dtor(ptr) };
} }
list = mem::take(unsafe { &mut DTORS }); list = DTORS.take();
} }
} }

View File

@ -69,15 +69,14 @@ pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
// _tlv_atexit. // _tlv_atexit.
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))] #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))]
pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
use crate::cell::Cell; use crate::cell::{Cell, RefCell};
use crate::mem;
use crate::ptr; use crate::ptr;
#[thread_local] #[thread_local]
static REGISTERED: Cell<bool> = Cell::new(false); static REGISTERED: Cell<bool> = Cell::new(false);
#[thread_local] #[thread_local]
static mut DTORS: Vec<(*mut u8, unsafe extern "C" fn(*mut u8))> = Vec::new(); static DTORS: RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>> = RefCell::new(Vec::new());
if !REGISTERED.get() { if !REGISTERED.get() {
_tlv_atexit(run_dtors, ptr::null_mut()); _tlv_atexit(run_dtors, ptr::null_mut());
@ -88,16 +87,18 @@ pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8); fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8);
} }
let list = &mut DTORS; match DTORS.try_borrow_mut() {
list.push((t, dtor)); Ok(mut dtors) => dtors.push((t, dtor)),
Err(_) => rtabort!("global allocator may not use TLS"),
}
unsafe extern "C" fn run_dtors(_: *mut u8) { unsafe extern "C" fn run_dtors(_: *mut u8) {
let mut list = mem::take(&mut DTORS); let mut list = DTORS.take();
while !list.is_empty() { while !list.is_empty() {
for (ptr, dtor) in list { for (ptr, dtor) in list {
dtor(ptr); dtor(ptr);
} }
list = mem::take(&mut DTORS); list = DTORS.take();
} }
} }
} }

View File

@ -16,14 +16,19 @@ static HAS_DTORS: AtomicBool = AtomicBool::new(false);
// Using a per-thread list avoids the problems in synchronizing global state. // Using a per-thread list avoids the problems in synchronizing global state.
#[thread_local] #[thread_local]
#[cfg(target_thread_local)] #[cfg(target_thread_local)]
static mut DESTRUCTORS: Vec<(*mut u8, unsafe extern "C" fn(*mut u8))> = Vec::new(); static DESTRUCTORS: crate::cell::RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>> =
crate::cell::RefCell::new(Vec::new());
// Ensure this can never be inlined because otherwise this may break in dylibs. // Ensure this can never be inlined because otherwise this may break in dylibs.
// See #44391. // See #44391.
#[inline(never)] #[inline(never)]
#[cfg(target_thread_local)] #[cfg(target_thread_local)]
pub unsafe fn register_keyless_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { pub unsafe fn register_keyless_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) {
DESTRUCTORS.push((t, dtor)); match DESTRUCTORS.try_borrow_mut() {
Ok(mut dtors) => dtors.push((t, dtor)),
Err(_) => rtabort!("global allocator may not use TLS"),
}
HAS_DTORS.store(true, Relaxed); HAS_DTORS.store(true, Relaxed);
} }
@ -37,11 +42,17 @@ unsafe fn run_keyless_dtors() {
// the case that this loop always terminates because we provide the // the case that this loop always terminates because we provide the
// guarantee that a TLS key cannot be set after it is flagged for // guarantee that a TLS key cannot be set after it is flagged for
// destruction. // destruction.
while let Some((ptr, dtor)) = DESTRUCTORS.pop() { loop {
// Use a let-else binding to ensure the `RefCell` guard is dropped
// immediately. Otherwise, a panic would occur if a TLS destructor
// tries to access the list.
let Some((ptr, dtor)) = DESTRUCTORS.borrow_mut().pop() else {
break;
};
(dtor)(ptr); (dtor)(ptr);
} }
// We're done so free the memory. // We're done so free the memory.
DESTRUCTORS = Vec::new(); DESTRUCTORS.replace(Vec::new());
} }
type Key = c::DWORD; type Key = c::DWORD;

View File

@ -13,6 +13,7 @@
#![unstable(feature = "thread_local_internals", issue = "none")] #![unstable(feature = "thread_local_internals", issue = "none")]
#![allow(dead_code)] #![allow(dead_code)]
use crate::cell::RefCell;
use crate::ptr; use crate::ptr;
use crate::sys_common::thread_local_key::StaticKey; use crate::sys_common::thread_local_key::StaticKey;
@ -28,17 +29,23 @@ pub unsafe fn register_dtor_fallback(t: *mut u8, dtor: unsafe extern "C" fn(*mut
// flagged for destruction. // flagged for destruction.
static DTORS: StaticKey = StaticKey::new(Some(run_dtors)); static DTORS: StaticKey = StaticKey::new(Some(run_dtors));
type List = Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>; // FIXME(joboet): integrate RefCell into pointer to avoid infinite recursion
// when the global allocator tries to register a destructor and just panic
// instead.
type List = RefCell<Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>>;
if DTORS.get().is_null() { if DTORS.get().is_null() {
let v: Box<List> = Box::new(Vec::new()); let v: Box<List> = Box::new(RefCell::new(Vec::new()));
DTORS.set(Box::into_raw(v) as *mut u8); DTORS.set(Box::into_raw(v) as *mut u8);
} }
let list: &mut List = &mut *(DTORS.get() as *mut List); let list = &*(DTORS.get() as *const List);
list.push((t, dtor)); match list.try_borrow_mut() {
Ok(mut dtors) => dtors.push((t, dtor)),
Err(_) => rtabort!("global allocator may not use TLS"),
}
unsafe extern "C" fn run_dtors(mut ptr: *mut u8) { unsafe extern "C" fn run_dtors(mut ptr: *mut u8) {
while !ptr.is_null() { while !ptr.is_null() {
let list: Box<List> = Box::from_raw(ptr as *mut List); let list = Box::from_raw(ptr as *mut List).into_inner();
for (ptr, dtor) in list.into_iter() { for (ptr, dtor) in list.into_iter() {
dtor(ptr); dtor(ptr);
} }