Skip to content

Commit fbb6275

Browse files
committed
Auto merge of #53108 - RalfJung:mutex, r=alexcrichton
clarify partially initialized Mutex issues Using a `sys_common::mutex::Mutex` without calling `init` is dangerous, and yet there are some places that do this. I tried to find all of them and add an appropriate comment about reentrancy. I found two places where (I think) reentrancy can actually occur, and was not able to come up with an argument for why this is okay. Someone who knows `io::lazy` and/or `sys_common::at_exit_imp` should have a careful look at this.
2 parents 76b69a6 + 25db842 commit fbb6275

File tree

9 files changed

+38
-6
lines changed

9 files changed

+38
-6
lines changed

src/libstd/io/lazy.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use sys_common;
1515
use sys_common::mutex::Mutex;
1616

1717
pub struct Lazy<T> {
18+
// We never call `lock.init()`, so it is UB to attempt to acquire this mutex reentrantly!
1819
lock: Mutex,
1920
ptr: Cell<*mut Arc<T>>,
2021
init: fn() -> Arc<T>,
@@ -26,7 +27,9 @@ const fn done<T>() -> *mut Arc<T> { 1_usize as *mut _ }
2627
unsafe impl<T> Sync for Lazy<T> {}
2728

2829
impl<T: Send + Sync + 'static> Lazy<T> {
29-
pub const fn new(init: fn() -> Arc<T>) -> Lazy<T> {
30+
/// Safety: `init` must not call `get` on the variable that is being
31+
/// initialized.
32+
pub const unsafe fn new(init: fn() -> Arc<T>) -> Lazy<T> {
3033
Lazy {
3134
lock: Mutex::new(),
3235
ptr: Cell::new(ptr::null_mut()),
@@ -48,6 +51,7 @@ impl<T: Send + Sync + 'static> Lazy<T> {
4851
}
4952
}
5053

54+
// Must only be called with `lock` held
5155
unsafe fn init(&'static self) -> Arc<T> {
5256
// If we successfully register an at exit handler, then we cache the
5357
// `Arc` allocation in our own internal box (it will get deallocated by
@@ -60,6 +64,9 @@ impl<T: Send + Sync + 'static> Lazy<T> {
6064
};
6165
drop(Box::from_raw(ptr))
6266
});
67+
// This could reentrantly call `init` again, which is a problem
68+
// because our `lock` allows reentrancy!
69+
// That's why `new` is unsafe and requires the caller to ensure no reentrancy happens.
6370
let ret = (self.init)();
6471
if registered.is_ok() {
6572
self.ptr.set(Box::into_raw(Box::new(ret.clone())));

src/libstd/io/stdio.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,13 @@ pub struct StdinLock<'a> {
197197
/// ```
198198
#[stable(feature = "rust1", since = "1.0.0")]
199199
pub fn stdin() -> Stdin {
200-
static INSTANCE: Lazy<Mutex<BufReader<Maybe<StdinRaw>>>> = Lazy::new(stdin_init);
200+
static INSTANCE: Lazy<Mutex<BufReader<Maybe<StdinRaw>>>> = unsafe { Lazy::new(stdin_init) };
201201
return Stdin {
202202
inner: INSTANCE.get().expect("cannot access stdin during shutdown"),
203203
};
204204

205205
fn stdin_init() -> Arc<Mutex<BufReader<Maybe<StdinRaw>>>> {
206+
// This must not reentrantly access `INSTANCE`
206207
let stdin = match stdin_raw() {
207208
Ok(stdin) => Maybe::Real(stdin),
208209
_ => Maybe::Fake
@@ -396,12 +397,13 @@ pub struct StdoutLock<'a> {
396397
#[stable(feature = "rust1", since = "1.0.0")]
397398
pub fn stdout() -> Stdout {
398399
static INSTANCE: Lazy<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>>
399-
= Lazy::new(stdout_init);
400+
= unsafe { Lazy::new(stdout_init) };
400401
return Stdout {
401402
inner: INSTANCE.get().expect("cannot access stdout during shutdown"),
402403
};
403404

404405
fn stdout_init() -> Arc<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>> {
406+
// This must not reentrantly access `INSTANCE`
405407
let stdout = match stdout_raw() {
406408
Ok(stdout) => Maybe::Real(stdout),
407409
_ => Maybe::Fake,
@@ -531,12 +533,14 @@ pub struct StderrLock<'a> {
531533
/// ```
532534
#[stable(feature = "rust1", since = "1.0.0")]
533535
pub fn stderr() -> Stderr {
534-
static INSTANCE: Lazy<ReentrantMutex<RefCell<Maybe<StderrRaw>>>> = Lazy::new(stderr_init);
536+
static INSTANCE: Lazy<ReentrantMutex<RefCell<Maybe<StderrRaw>>>> =
537+
unsafe { Lazy::new(stderr_init) };
535538
return Stderr {
536539
inner: INSTANCE.get().expect("cannot access stderr during shutdown"),
537540
};
538541

539542
fn stderr_init() -> Arc<ReentrantMutex<RefCell<Maybe<StderrRaw>>>> {
543+
// This must not reentrantly access `INSTANCE`
540544
let stderr = match stderr_raw() {
541545
Ok(stderr) => Maybe::Real(stderr),
542546
_ => Maybe::Fake,

src/libstd/sys/unix/args.rs

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ mod imp {
8080

8181
static mut ARGC: isize = 0;
8282
static mut ARGV: *const *const u8 = ptr::null();
83+
// We never call `ENV_LOCK.init()`, so it is UB to attempt to
84+
// acquire this mutex reentrantly!
8385
static LOCK: Mutex = Mutex::new();
8486

8587
pub unsafe fn init(argc: isize, argv: *const *const u8) {

src/libstd/sys/unix/mutex.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ unsafe impl Sync for Mutex {}
2525
#[allow(dead_code)] // sys isn't exported yet
2626
impl Mutex {
2727
pub const fn new() -> Mutex {
28-
// Might be moved and address is changing it is better to avoid
29-
// initialization of potentially opaque OS data before it landed
28+
// Might be moved to a different address, so it is better to avoid
29+
// initialization of potentially opaque OS data before it landed.
30+
// Be very careful using this newly constructed `Mutex`, reentrant
31+
// locking is undefined behavior until `init` is called!
3032
Mutex { inner: UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER) }
3133
}
3234
#[inline]

src/libstd/sys/unix/os.rs

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ use sys::fd;
3333
use vec;
3434

3535
const TMPBUF_SZ: usize = 128;
36+
// We never call `ENV_LOCK.init()`, so it is UB to attempt to
37+
// acquire this mutex reentrantly!
3638
static ENV_LOCK: Mutex = Mutex::new();
3739

3840

src/libstd/sys_common/at_exit_imp.rs

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type Queue = Vec<Box<dyn FnBox()>>;
2323
// on poisoning and this module needs to operate at a lower level than requiring
2424
// the thread infrastructure to be in place (useful on the borders of
2525
// initialization/destruction).
26+
// We never call `LOCK.init()`, so it is UB to attempt to
27+
// acquire this mutex reentrantly!
2628
static LOCK: Mutex = Mutex::new();
2729
static mut QUEUE: *mut Queue = ptr::null_mut();
2830

@@ -61,6 +63,7 @@ pub fn cleanup() {
6163
if !queue.is_null() {
6264
let queue: Box<Queue> = Box::from_raw(queue);
6365
for to_run in *queue {
66+
// We are not holding any lock, so reentrancy is fine.
6467
to_run();
6568
}
6669
}
@@ -72,6 +75,8 @@ pub fn push(f: Box<dyn FnBox()>) -> bool {
7275
unsafe {
7376
let _guard = LOCK.lock();
7477
if init() {
78+
// We are just moving `f` around, not calling it.
79+
// There is no possibility of reentrancy here.
7580
(*QUEUE).push(f);
7681
true
7782
} else {

src/libstd/sys_common/mutex.rs

+6
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@ impl Mutex {
2424
///
2525
/// Behavior is undefined if the mutex is moved after it is
2626
/// first used with any of the functions below.
27+
/// Also, until `init` is called, behavior is undefined if this
28+
/// mutex is ever used reentrantly, i.e., `raw_lock` or `try_lock`
29+
/// are called by the thread currently holding the lock.
2730
pub const fn new() -> Mutex { Mutex(imp::Mutex::new()) }
2831

2932
/// Prepare the mutex for use.
3033
///
3134
/// This should be called once the mutex is at a stable memory address.
35+
/// If called, this must be the very first thing that happens to the mutex.
36+
/// Calling it in parallel with or after any operation (including another
37+
/// `init()`) is undefined behavior.
3238
#[inline]
3339
pub unsafe fn init(&mut self) { self.0.init() }
3440

src/libstd/sys_common/thread_local.rs

+2
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ impl StaticKey {
161161
// Additionally a 0-index of a tls key hasn't been seen on windows, so
162162
// we just simplify the whole branch.
163163
if imp::requires_synchronized_create() {
164+
// We never call `INIT_LOCK.init()`, so it is UB to attempt to
165+
// acquire this mutex reentrantly!
164166
static INIT_LOCK: Mutex = Mutex::new();
165167
let _guard = INIT_LOCK.lock();
166168
let mut key = self.key.load(Ordering::SeqCst);

src/libstd/thread/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,8 @@ pub struct ThreadId(u64);
940940
impl ThreadId {
941941
// Generate a new unique thread ID.
942942
fn new() -> ThreadId {
943+
// We never call `GUARD.init()`, so it is UB to attempt to
944+
// acquire this mutex reentrantly!
943945
static GUARD: mutex::Mutex = mutex::Mutex::new();
944946
static mut COUNTER: u64 = 0;
945947

0 commit comments

Comments
 (0)