Skip to content

Commit d1d15a4

Browse files
committed
detect when pthread_cond_t is moved
Closes #3749
1 parent 11da987 commit d1d15a4

File tree

5 files changed

+137
-17
lines changed

5 files changed

+137
-17
lines changed

src/concurrency/sync.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ struct Condvar {
134134
/// Contains the clock of the last thread to
135135
/// perform a condvar-signal.
136136
clock: VClock,
137+
138+
/// Additional data that can be set by shim implementations.
139+
data: Option<Box<dyn Any>>,
137140
}
138141

139142
/// The futex state.
@@ -344,21 +347,49 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
344347
this.machine.sync.rwlocks[id].data.as_deref().and_then(|p| p.downcast_ref::<T>())
345348
}
346349

350+
/// Eagerly create and initialize a new condvar.
351+
fn condvar_create(
352+
&mut self,
353+
condvar: &MPlaceTy<'tcx>,
354+
offset: u64,
355+
data: Option<Box<dyn Any>>,
356+
) -> InterpResult<'tcx, CondvarId> {
357+
let this = self.eval_context_mut();
358+
this.create_id(
359+
condvar,
360+
offset,
361+
|ecx| &mut ecx.machine.sync.condvars,
362+
Condvar { data, ..Default::default() },
363+
)
364+
}
365+
347366
fn condvar_get_or_create_id(
348367
&mut self,
349368
lock: &MPlaceTy<'tcx>,
350369
offset: u64,
370+
initialize_data: impl for<'a> FnOnce(
371+
&'a mut MiriInterpCx<'tcx>,
372+
) -> InterpResult<'tcx, Option<Box<dyn Any>>>,
351373
) -> InterpResult<'tcx, CondvarId> {
352374
let this = self.eval_context_mut();
353375
this.get_or_create_id(
354376
lock,
355377
offset,
356378
|ecx| &mut ecx.machine.sync.condvars,
357-
|_| Ok(Default::default()),
379+
|ecx| initialize_data(ecx).map(|data| Condvar { data, ..Default::default() }),
358380
)?
359381
.ok_or_else(|| err_ub_format!("condvar has invalid ID").into())
360382
}
361383

384+
/// Retrieve the additional data stored for a condvar.
385+
fn condvar_get_data<'a, T: 'static>(&'a mut self, id: CondvarId) -> Option<&'a T>
386+
where
387+
'tcx: 'a,
388+
{
389+
let this = self.eval_context_ref();
390+
this.machine.sync.condvars[id].data.as_deref().and_then(|p| p.downcast_ref::<T>())
391+
}
392+
362393
#[inline]
363394
/// Get the id of the thread that currently owns this lock.
364395
fn mutex_get_owner(&mut self, id: MutexId) -> ThreadId {

src/shims/unix/sync.rs

+28-16
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ fn translate_kind<'tcx>(ecx: &MiriInterpCx<'tcx>, kind: i32) -> InterpResult<'tc
206206
// - id: u32
207207

208208
#[derive(Debug)]
209-
/// Additional data that may be used by shim implementations.
209+
/// Additional data that we attach with each rwlock instance.
210210
pub struct AdditionalRwLockData {
211211
/// The address of the rwlock.
212212
pub address: u64,
@@ -364,25 +364,32 @@ fn cond_clock_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> u64 {
364364
offset
365365
}
366366

367+
#[derive(Debug)]
368+
/// Additional data that we attach with each cond instance.
369+
struct AdditionalCondData {
370+
/// The address of the cond.
371+
address: u64,
372+
}
373+
367374
fn cond_get_id<'tcx>(
368375
ecx: &mut MiriInterpCx<'tcx>,
369376
cond_ptr: &OpTy<'tcx>,
370377
) -> InterpResult<'tcx, CondvarId> {
371378
let cond = ecx.deref_pointer(cond_ptr)?;
372-
ecx.condvar_get_or_create_id(&cond, cond_id_offset(ecx)?)
373-
}
379+
let address = cond.ptr().addr().bytes();
380+
let id = ecx.condvar_get_or_create_id(&cond, cond_id_offset(ecx)?, |_| {
381+
Ok(Some(Box::new(AdditionalCondData { address })))
382+
})?;
374383

375-
fn cond_reset_id<'tcx>(
376-
ecx: &mut MiriInterpCx<'tcx>,
377-
cond_ptr: &OpTy<'tcx>,
378-
) -> InterpResult<'tcx, ()> {
379-
ecx.deref_pointer_and_write(
380-
cond_ptr,
381-
cond_id_offset(ecx)?,
382-
Scalar::from_i32(0),
383-
ecx.libc_ty_layout("pthread_cond_t"),
384-
ecx.machine.layouts.u32,
385-
)
384+
// Check that the mutex has not been moved since last use.
385+
let data = ecx
386+
.condvar_get_data::<AdditionalCondData>(id)
387+
.expect("data should always exist for pthreads");
388+
if data.address != address {
389+
throw_ub_format!("pthread_cond_t can't be moved after first use")
390+
}
391+
392+
Ok(id)
386393
}
387394

388395
fn cond_get_clock_id<'tcx>(
@@ -821,8 +828,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
821828
condattr_get_clock_id(this, attr_op)?
822829
};
823830

824-
// Write 0 to use the same code path as the static initializers.
825-
cond_reset_id(this, cond_op)?;
831+
let cond = this.deref_pointer(cond_op)?;
832+
let address = cond.ptr().addr().bytes();
833+
this.condvar_create(
834+
&cond,
835+
cond_id_offset(this)?,
836+
Some(Box::new(AdditionalCondData { address })),
837+
)?;
826838

827839
cond_set_clock_id(this, cond_op, clock_id)?;
828840

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: pthread_cond_t can't be moved after first use
2+
--> $DIR/libc_pthread_cond_move.rs:LL:CC
3+
|
4+
LL | libc::pthread_cond_destroy(cond2.as_mut_ptr());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pthread_cond_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_cond_move.rs:LL:CC
11+
note: inside `main`
12+
--> $DIR/libc_pthread_cond_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,37 @@
1+
//@revisions: static_initializer init
2+
//@ignore-target-windows: No pthreads on Windows
3+
4+
/// Test that moving a pthread_cond between uses fails.
5+
6+
fn main() {
7+
check()
8+
}
9+
10+
#[cfg(init)]
11+
fn check() {
12+
unsafe {
13+
use core::mem::MaybeUninit;
14+
let mut cond = MaybeUninit::<libc::pthread_cond_t>::uninit();
15+
16+
libc::pthread_cond_init(cond.as_mut_ptr(), std::ptr::null());
17+
18+
// move pthread_cond_t
19+
let mut cond2 = cond;
20+
21+
libc::pthread_cond_destroy(cond2.as_mut_ptr()); //~[init] ERROR: pthread_cond_t can't be moved after first use
22+
}
23+
}
24+
25+
#[cfg(static_initializer)]
26+
fn check() {
27+
unsafe {
28+
let mut cond = libc::PTHREAD_COND_INITIALIZER;
29+
30+
libc::pthread_cond_signal(&mut cond as *mut _);
31+
32+
// move pthread_cond_t
33+
let mut cond2 = cond;
34+
35+
libc::pthread_cond_destroy(&mut cond2 as *mut _); //~[static_initializer] ERROR: pthread_cond_t can't be moved after first use
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: pthread_cond_t can't be moved after first use
2+
--> $DIR/libc_pthread_cond_move.rs:LL:CC
3+
|
4+
LL | libc::pthread_cond_destroy(&mut cond2 as *mut _);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pthread_cond_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_cond_move.rs:LL:CC
11+
note: inside `main`
12+
--> $DIR/libc_pthread_cond_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)