Skip to content

Commit ba789c6

Browse files
committed
also use new sync obj metadata scheme for INIT_ONCE and os_unfair_lock
1 parent edc13e6 commit ba789c6

File tree

4 files changed

+73
-34
lines changed

4 files changed

+73
-34
lines changed

src/tools/miri/src/concurrency/sync.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
372372
///
373373
/// `new_meta_obj` gets invoked when there is not yet an initialization object.
374374
/// It has to ensure that the in-memory representation indeed matches `uninit_val`.
375+
///
376+
/// The point of storing an `init_val` is so that if this memory gets copied somewhere else,
377+
/// it does not look like the static initializer (i.e., `uninit_val`) any more. For some
378+
/// objects we could just entirely forbid reading their bytes to ensure they don't get copied,
379+
/// but that does not work for objects without a destructor (Windows `InitOnce`, macOS
380+
/// `os_unfair_lock`).
375381
fn get_immovable_sync_with_static_init<'a, T: SyncObj>(
376382
&'a mut self,
377383
obj: &MPlaceTy<'tcx>,
@@ -383,6 +389,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
383389
where
384390
'tcx: 'a,
385391
{
392+
assert!(init_val != uninit_val);
386393
let this = self.eval_context_mut();
387394
this.check_ptr_access(obj.ptr(), obj.layout.size, CheckInAllocMsg::Dereferenceable)?;
388395
assert!(init_offset < obj.layout.size); // ensure our 1-byte flag fits
@@ -398,7 +405,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
398405

399406
// There's no sync object there yet. Create one, and try a CAS for uninit_val to init_val.
400407
let meta_obj = new_meta_obj(this)?;
401-
let (_init, success) = this
408+
let (old_init, success) = this
402409
.atomic_compare_exchange_scalar(
403410
&init_field,
404411
&ImmTy::from_scalar(Scalar::from_u8(uninit_val), this.machine.layouts.u8),
@@ -408,7 +415,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
408415
/* can_fail_spuriously */ false,
409416
)?
410417
.to_scalar_pair();
411-
assert!(success.to_bool()?, "`new_meta_obj` should have ensured that this CAS succeeds.");
418+
if !success.to_bool()? {
419+
// This can happen for the macOS lock if it is already marked as initialized.
420+
assert_eq!(
421+
old_init.to_u8()?,
422+
init_val,
423+
"`new_meta_obj` should have ensured that this CAS succeeds"
424+
);
425+
}
412426

413427
let (alloc_extra, _machine) = this.get_alloc_extra_mut(alloc).unwrap();
414428
assert!(meta_obj.delete_on_write());

src/tools/miri/src/shims/unix/macos/sync.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,22 @@
1313
use std::cell::Cell;
1414
use std::time::Duration;
1515

16-
use rustc_abi::Size;
16+
use rustc_abi::{Endian, FieldIdx, Size};
1717

1818
use crate::concurrency::sync::{FutexRef, SyncObj};
1919
use crate::*;
2020

2121
#[derive(Clone)]
2222
enum MacOsUnfairLock {
23-
Poisoned,
2423
Active { mutex_ref: MutexRef },
24+
PermanentlyLocked,
2525
}
2626

27-
impl SyncObj for MacOsUnfairLock {}
27+
impl SyncObj for MacOsUnfairLock {
28+
fn delete_on_write(&self) -> bool {
29+
true
30+
}
31+
}
2832

2933
pub enum MacOsFutexTimeout<'a, 'tcx> {
3034
None,
@@ -57,22 +61,35 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
5761
where
5862
'tcx: 'a,
5963
{
64+
// `os_unfair_lock_s` wraps a single `u32` field. We use the first byte to store the "init"
65+
// flag. Due to macOS always being little endian, that's the least significant byte.
6066
let this = self.eval_context_mut();
67+
assert!(this.tcx.data_layout.endian == Endian::Little);
68+
6169
let lock = this.deref_pointer_as(lock_ptr, this.libc_ty_layout("os_unfair_lock_s"))?;
62-
this.lazy_sync_get_data(
70+
this.get_immovable_sync_with_static_init(
6371
&lock,
6472
Size::ZERO, // offset for init tracking
65-
|| {
66-
// If we get here, due to how we reset things to zero in `os_unfair_lock_unlock`,
67-
// this means the lock was moved while locked. This can happen with a `std` lock,
68-
// but then any future attempt to unlock will just deadlock. In practice, terrible
69-
// things can probably happen if you swap two locked locks, since they'd wake up
70-
// from the wrong queue... we just won't catch all UB of this library API then (we
71-
// would need to store some unique identifer in-memory for this, instead of a static
72-
// LAZY_INIT_COOKIE). This can't be hit via `std::sync::Mutex`.
73-
interp_ok(MacOsUnfairLock::Poisoned)
73+
/* uninit_val */ 0,
74+
/* init_val */ 1,
75+
|this| {
76+
let field = this.project_field(&lock, FieldIdx::from_u32(0))?;
77+
let val = this.read_scalar(&field)?.to_u32()?;
78+
if val == 0 {
79+
interp_ok(MacOsUnfairLock::Active { mutex_ref: MutexRef::new() })
80+
} else if val == 1 {
81+
// This is a lock that got copied while it is initialized. We de-initialize
82+
// locks when they get released, so it got copied while locked. Unfortunately
83+
// that is something `std` needs to support (the guard could have been leaked).
84+
// So we behave like a futex-based lock whose wait queue got pruned: any attempt
85+
// to acquire the lock will just wait forever.
86+
// In practice there actually could be a wait queue there, if someone moves a
87+
// lock *while threads are queued*; this is UB we will not detect.
88+
interp_ok(MacOsUnfairLock::PermanentlyLocked)
89+
} else {
90+
throw_ub_format!("`os_unfair_lock` was not properly initialized at this location, or it got overwritten");
91+
}
7492
},
75-
|_| interp_ok(MacOsUnfairLock::Active { mutex_ref: MutexRef::new() }),
7693
)
7794
}
7895
}
@@ -336,7 +353,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
336353
let this = self.eval_context_mut();
337354

338355
let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
339-
// The lock is poisoned, who knows who owns it... we'll pretend: someone else.
356+
// A perma-locked lock is definitely not held by us.
340357
throw_machine_stop!(TerminationInfo::Abort(
341358
"attempted to unlock an os_unfair_lock not owned by the current thread".to_owned()
342359
));
@@ -365,7 +382,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
365382
let this = self.eval_context_mut();
366383

367384
let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
368-
// The lock is poisoned, who knows who owns it... we'll pretend: someone else.
385+
// A perma-locked lock is definitely not held by us.
369386
throw_machine_stop!(TerminationInfo::Abort(
370387
"called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread".to_owned()
371388
));
@@ -387,7 +404,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
387404
let this = self.eval_context_mut();
388405

389406
let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
390-
// The lock is poisoned, who knows who owns it... we'll pretend: someone else.
407+
// A perma-locked lock is definitely not held by us.
391408
return interp_ok(());
392409
};
393410
let mutex_ref = mutex_ref.clone();

src/tools/miri/src/shims/windows/sync.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::time::Duration;
22

3-
use rustc_abi::Size;
3+
use rustc_abi::{FieldIdx, Size};
44

55
use crate::concurrency::init_once::{EvalContextExt as _, InitOnceStatus};
66
use crate::concurrency::sync::{FutexRef, SyncObj};
@@ -11,7 +11,11 @@ struct WindowsInitOnce {
1111
init_once: InitOnceRef,
1212
}
1313

14-
impl SyncObj for WindowsInitOnce {}
14+
impl SyncObj for WindowsInitOnce {
15+
fn delete_on_write(&self) -> bool {
16+
true
17+
}
18+
}
1519

1620
struct WindowsFutex {
1721
futex: FutexRef,
@@ -22,7 +26,7 @@ impl SyncObj for WindowsFutex {}
2226
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
2327
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
2428
// Windows sync primitives are pointer sized.
25-
// We only use the first 4 bytes for the id.
29+
// We only use the first byte for the "init" flag.
2630

2731
fn init_once_get_data<'a>(
2832
&'a mut self,
@@ -37,13 +41,19 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
3741
this.deref_pointer_as(init_once_ptr, this.windows_ty_layout("INIT_ONCE"))?;
3842
let init_offset = Size::ZERO;
3943

40-
this.lazy_sync_get_data(
44+
this.get_immovable_sync_with_static_init(
4145
&init_once,
4246
init_offset,
43-
|| throw_ub_format!("`INIT_ONCE` can't be moved after first use"),
44-
|_| {
45-
// TODO: check that this is still all-zero.
46-
interp_ok(WindowsInitOnce { init_once: InitOnceRef::new() })
47+
/* uninit_val */ 0,
48+
/* init_val */ 1,
49+
|this| {
50+
let ptr_field = this.project_field(&init_once, FieldIdx::from_u32(0))?;
51+
let val = this.read_target_usize(&ptr_field)?;
52+
if val == 0 {
53+
interp_ok(WindowsInitOnce { init_once: InitOnceRef::new() })
54+
} else {
55+
throw_ub_format!("`INIT_ONCE` was not properly initialized at this location, or it got overwritten");
56+
}
4757
},
4858
)
4959
}

src/tools/miri/tests/pass-dep/concurrency/apple-os-unfair-lock.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ fn main() {
1414
libc::os_unfair_lock_assert_not_owner(lock.get());
1515
}
1616

17-
// `os_unfair_lock`s can be moved and leaked.
18-
// In the real implementation, even moving it while locked is possible
19-
// (and "forks" the lock, i.e. old and new location have independent wait queues).
20-
// We only test the somewhat sane case of moving while unlocked that `std` plans to rely on.
17+
// `os_unfair_lock`s can be moved, and even acquired again then.
2118
let lock = lock;
22-
let locked = unsafe { libc::os_unfair_lock_trylock(lock.get()) };
23-
assert!(locked);
24-
let _lock = lock;
19+
assert!(unsafe { libc::os_unfair_lock_trylock(lock.get()) });
20+
// We can even move it while locked, but then we cannot acquire it any more.
21+
let lock = lock;
22+
assert!(!unsafe { libc::os_unfair_lock_trylock(lock.get()) });
2523
}

0 commit comments

Comments
 (0)