Skip to content

Commit 1df484c

Browse files
committed
Detect pthread_mutex_t is moved
See: #3749
1 parent 4b649eb commit 1df484c

10 files changed

+242
-9
lines changed

src/concurrency/sync.rs

+49-4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ pub(super) use declare_id;
6666

6767
declare_id!(MutexId);
6868

69+
/// The mutex kind.
70+
#[derive(Debug, Clone, Copy)]
71+
pub struct MutexKind(i32);
72+
73+
impl MutexKind {
74+
pub fn new(k: i32) -> Self {
75+
Self(k)
76+
}
77+
78+
pub fn to_i32(&self) -> i32 {
79+
self.0
80+
}
81+
}
82+
83+
#[derive(Debug)]
84+
/// Additional data that may be used by shim implementations.
85+
pub struct AdditionalMutexData {
86+
/// The mutex kind, used by some mutex implementations like pthreads mutexes.
87+
pub kind: MutexKind,
88+
89+
/// The address of the mutex.
90+
pub address: Pointer,
91+
}
92+
6993
/// The mutex state.
7094
#[derive(Default, Debug)]
7195
struct Mutex {
@@ -77,6 +101,9 @@ struct Mutex {
77101
queue: VecDeque<ThreadId>,
78102
/// Mutex clock. This tracks the moment of the last unlock.
79103
clock: VClock,
104+
105+
/// Additional data that can be set by shim implementations.
106+
data: Option<AdditionalMutexData>,
80107
}
81108

82109
declare_id!(RwLockId);
@@ -200,9 +227,14 @@ pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
200227
/// Provides the closure with the next MutexId. Creates that mutex if the closure returns None,
201228
/// otherwise returns the value from the closure.
202229
#[inline]
203-
fn mutex_get_or_create<F>(&mut self, existing: F) -> InterpResult<'tcx, MutexId>
230+
fn mutex_get_or_create<F, I>(
231+
&mut self,
232+
existing: F,
233+
initialize_data: I,
234+
) -> InterpResult<'tcx, MutexId>
204235
where
205236
F: FnOnce(&mut MiriInterpCx<'tcx>, MutexId) -> InterpResult<'tcx, Option<MutexId>>,
237+
I: FnOnce() -> InterpResult<'tcx, Option<AdditionalMutexData>>,
206238
{
207239
let this = self.eval_context_mut();
208240
let next_index = this.machine.sync.mutexes.next_index();
@@ -214,6 +246,8 @@ pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
214246
} else {
215247
let new_index = this.machine.sync.mutexes.push(Default::default());
216248
assert_eq!(next_index, new_index);
249+
let data = initialize_data()?;
250+
this.machine.sync.mutexes[new_index].data = data;
217251
Ok(new_index)
218252
}
219253
}
@@ -291,11 +325,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
291325
lock_op: &OpTy<'tcx>,
292326
lock_layout: TyAndLayout<'tcx>,
293327
offset: u64,
328+
initialize_data: impl FnOnce() -> InterpResult<'tcx, Option<AdditionalMutexData>>,
294329
) -> InterpResult<'tcx, MutexId> {
295330
let this = self.eval_context_mut();
296-
this.mutex_get_or_create(|ecx, next_id| {
297-
ecx.get_or_create_id(next_id, lock_op, lock_layout, offset)
298-
})
331+
this.mutex_get_or_create(
332+
|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, lock_layout, offset),
333+
initialize_data,
334+
)
335+
}
336+
337+
/// Retrieve the additional data stored for a mutex.
338+
fn mutex_get_data<'a>(&'a mut self, id: MutexId) -> Option<&'a AdditionalMutexData>
339+
where
340+
'tcx: 'a,
341+
{
342+
let this = self.eval_context_ref();
343+
this.machine.sync.mutexes[id].data.as_ref()
299344
}
300345

301346
fn rwlock_get_or_create_id(

src/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ pub use crate::concurrency::{
131131
cpu_affinity::MAX_CPUS,
132132
data_race::{AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _},
133133
init_once::{EvalContextExt as _, InitOnceId},
134-
sync::{CondvarId, EvalContextExt as _, MutexId, RwLockId, SynchronizationObjects},
134+
sync::{
135+
AdditionalMutexData, CondvarId, EvalContextExt as _, MutexId, MutexKind, RwLockId,
136+
SynchronizationObjects,
137+
},
135138
thread::{
136139
BlockReason, EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager,
137140
TimeoutAnchor, TimeoutClock, UnblockCallback,

src/shims/unix/macos/sync.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
1919
// os_unfair_lock holds a 32-bit value, is initialized with zero and
2020
// must be assumed to be opaque. Therefore, we can just store our
2121
// internal mutex ID in the structure without anyone noticing.
22-
this.mutex_get_or_create_id(lock_op, this.libc_ty_layout("os_unfair_lock"), 0)
22+
this.mutex_get_or_create_id(lock_op, this.libc_ty_layout("os_unfair_lock"), 0, || Ok(None))
2323
}
2424
}
2525

src/shims/unix/sync.rs

+41-3
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,29 @@ fn mutex_get_id<'tcx>(
118118
ecx: &mut MiriInterpCx<'tcx>,
119119
mutex_op: &OpTy<'tcx>,
120120
) -> InterpResult<'tcx, MutexId> {
121+
let address = ecx.deref_pointer_as(mutex_op, ecx.libc_ty_layout("pthread_mutex_t"))?.ptr();
122+
let kind = MutexKind::new(mutex_get_kind(ecx, mutex_op)?);
121123
ecx.mutex_get_or_create_id(
122124
mutex_op,
123125
ecx.libc_ty_layout("pthread_mutex_t"),
124126
mutex_id_offset(ecx)?,
127+
|| Ok(Some(AdditionalMutexData { kind, address })),
125128
)
126129
}
127130

131+
fn mutex_check_moved<'tcx>(
132+
ecx: &mut MiriInterpCx<'tcx>,
133+
mutex_op: &OpTy<'tcx>,
134+
id: MutexId,
135+
) -> InterpResult<'tcx, ()> {
136+
let addr = ecx.deref_pointer_as(mutex_op, ecx.libc_ty_layout("pthread_mutex_t"))?.ptr().addr();
137+
let data = ecx.mutex_get_data(id).expect("data should be always exist for pthreads");
138+
if data.address.addr() != addr {
139+
throw_ub_format!("pthread_mutex_t can't be moved after first use")
140+
}
141+
Ok(())
142+
}
143+
128144
fn mutex_reset_id<'tcx>(
129145
ecx: &mut MiriInterpCx<'tcx>,
130146
mutex_op: &OpTy<'tcx>,
@@ -457,6 +473,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
457473

458474
mutex_set_kind(this, mutex_op, kind)?;
459475

476+
// Fetch (and ignore) the mutex's id to go through the initialization
477+
// code, and thus register the mutex's address.
478+
mutex_get_id(this, mutex_op)?;
479+
460480
Ok(())
461481
}
462482

@@ -467,8 +487,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
467487
) -> InterpResult<'tcx> {
468488
let this = self.eval_context_mut();
469489

470-
let kind = mutex_get_kind(this, mutex_op)?;
471490
let id = mutex_get_id(this, mutex_op)?;
491+
let kind = this
492+
.mutex_get_data(id)
493+
.expect("data should always exist for pthread mutexes")
494+
.kind
495+
.to_i32();
496+
497+
mutex_check_moved(this, mutex_op, id)?;
472498

473499
let ret = if this.mutex_is_locked(id) {
474500
let owner_thread = this.mutex_get_owner(id);
@@ -504,8 +530,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
504530
fn pthread_mutex_trylock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
505531
let this = self.eval_context_mut();
506532

507-
let kind = mutex_get_kind(this, mutex_op)?;
508533
let id = mutex_get_id(this, mutex_op)?;
534+
let kind = this
535+
.mutex_get_data(id)
536+
.expect("data should always exist for pthread mutexes")
537+
.kind
538+
.to_i32();
539+
540+
mutex_check_moved(this, mutex_op, id)?;
509541

510542
Ok(Scalar::from_i32(if this.mutex_is_locked(id) {
511543
let owner_thread = this.mutex_get_owner(id);
@@ -536,8 +568,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
536568
fn pthread_mutex_unlock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
537569
let this = self.eval_context_mut();
538570

539-
let kind = mutex_get_kind(this, mutex_op)?;
540571
let id = mutex_get_id(this, mutex_op)?;
572+
let kind = this
573+
.mutex_get_data(id)
574+
.expect("data should always exist for pthread mutexes")
575+
.kind
576+
.to_i32();
577+
578+
mutex_check_moved(this, mutex_op, id)?;
541579

542580
if let Some(_old_locked_count) = this.mutex_unlock(id)? {
543581
// The mutex was locked by the current thread.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: pthread_mutex_t can't be moved after first use
2+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
3+
|
4+
LL | libc::pthread_mutex_lock(&mut m2 as *mut _);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pthread_mutex_t can't be moved after first use
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `check` at $DIR/libc_pthread_mutex_move.rs:LL:CC
11+
note: inside `main`
12+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
13+
|
14+
LL | check();
15+
| ^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: pthread_mutex_t can't be moved after first use
2+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
3+
|
4+
LL | libc::pthread_mutex_lock(&mut m2 as *mut _);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pthread_mutex_t can't be moved after first use
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `check` at $DIR/libc_pthread_mutex_move.rs:LL:CC
11+
note: inside `main`
12+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
13+
|
14+
LL | check();
15+
| ^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//@ignore-target-windows: No pthreads on Windows
2+
//@revisions: lock trylock unlock init
3+
4+
fn main() {
5+
check();
6+
}
7+
8+
#[cfg(init)]
9+
fn check() {
10+
unsafe {
11+
let mut m: libc::pthread_mutex_t = std::mem::zeroed();
12+
assert_eq!(libc::pthread_mutex_init(&mut m as *mut _, std::ptr::null()), 0);
13+
14+
let mut m2 = m;
15+
libc::pthread_mutex_lock(&mut m2 as *mut _); //~[init] ERROR: pthread_mutex_t can't be moved after first use
16+
}
17+
}
18+
19+
#[cfg(lock)]
20+
fn check() {
21+
unsafe {
22+
let mut m: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER;
23+
libc::pthread_mutex_lock(&mut m as *mut _);
24+
// libc::pthread_mutex_unlock(&mut m as *mut _);
25+
26+
let mut m2 = m;
27+
libc::pthread_mutex_lock(&mut m2 as *mut _); //~[lock] ERROR: pthread_mutex_t can't be moved after first use
28+
}
29+
}
30+
31+
#[cfg(trylock)]
32+
fn check() {
33+
unsafe {
34+
let mut m: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER;
35+
libc::pthread_mutex_trylock(&mut m as *mut _);
36+
// libc::pthread_mutex_unlock(&mut m as *mut _);
37+
38+
let mut m2 = m;
39+
libc::pthread_mutex_trylock(&mut m2 as *mut _); //~[trylock] ERROR: pthread_mutex_t can't be moved after first use
40+
}
41+
}
42+
43+
#[cfg(unlock)]
44+
fn check() {
45+
unsafe {
46+
let mut m: libc::pthread_mutex_t = libc::PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
47+
libc::pthread_mutex_unlock(&mut m as *mut _);
48+
49+
let mut m2 = m;
50+
libc::pthread_mutex_unlock(&mut m2 as *mut _); //~[unlock] ERROR: pthread_mutex_t can't be moved after first use
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: pthread_mutex_t can't be moved
2+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
3+
|
4+
LL | libc::pthread_mutex_lock(&mut mutex2 as *mut _);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pthread_mutex_t can't be moved
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/libc_pthread_mutex_move.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: pthread_mutex_t can't be moved after first use
2+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
3+
|
4+
LL | libc::pthread_mutex_trylock(&mut m2 as *mut _);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pthread_mutex_t can't be moved after first use
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `check` at $DIR/libc_pthread_mutex_move.rs:LL:CC
11+
note: inside `main`
12+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
13+
|
14+
LL | check();
15+
| ^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: pthread_mutex_t can't be moved after first use
2+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
3+
|
4+
LL | libc::pthread_mutex_unlock(&mut m2 as *mut _);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pthread_mutex_t can't be moved after first use
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `check` at $DIR/libc_pthread_mutex_move.rs:LL:CC
11+
note: inside `main`
12+
--> $DIR/libc_pthread_mutex_move.rs:LL:CC
13+
|
14+
LL | check();
15+
| ^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+

0 commit comments

Comments
 (0)