Skip to content

Commit b1aaf1a

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

File tree

5 files changed

+182
-57
lines changed

5 files changed

+182
-57
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

+73-56
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,
@@ -286,6 +286,19 @@ fn condattr_get_clock_id<'tcx>(
286286
.to_i32()
287287
}
288288

289+
fn translate_clock_id<'tcx>(ecx: &MiriInterpCx<'tcx>, raw_id: i32) -> InterpResult<'tcx, ClockId> {
290+
// To ensure compatibility with PTHREAD_COND_INITIALIZER on all platforms,
291+
// we can't just compare with CLOCK_REALTIME: on Solarish, PTHREAD_COND_INITIALIZER
292+
// makes the clock 0 but CLOCK_REALTIME is 3.
293+
Ok(if raw_id == ecx.eval_libc_i32("CLOCK_REALTIME") || raw_id == 0 {
294+
ClockId::Realtime
295+
} else if raw_id == ecx.eval_libc_i32("CLOCK_MONOTONIC") {
296+
ClockId::Monotonic
297+
} else {
298+
throw_unsup_format!("unsupported clock id: {raw_id}");
299+
})
300+
}
301+
289302
fn condattr_set_clock_id<'tcx>(
290303
ecx: &mut MiriInterpCx<'tcx>,
291304
attr_ptr: &OpTy<'tcx>,
@@ -331,16 +344,6 @@ fn cond_id_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, u64> {
331344
Ok(offset)
332345
}
333346

334-
/// Determines whether this clock represents the real-time clock, CLOCK_REALTIME.
335-
fn is_cond_clock_realtime<'tcx>(ecx: &MiriInterpCx<'tcx>, clock_id: i32) -> bool {
336-
// To ensure compatibility with PTHREAD_COND_INITIALIZER on all platforms,
337-
// we can't just compare with CLOCK_REALTIME: on Solarish, PTHREAD_COND_INITIALIZER
338-
// makes the clock 0 but CLOCK_REALTIME is 3.
339-
// However, we need to always be able to distinguish this from CLOCK_MONOTONIC.
340-
clock_id == ecx.eval_libc_i32("CLOCK_REALTIME")
341-
|| (clock_id == 0 && clock_id != ecx.eval_libc_i32("CLOCK_MONOTONIC"))
342-
}
343-
344347
fn cond_clock_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> u64 {
345348
// macOS doesn't have a clock attribute, but to keep the code uniform we store
346349
// a clock ID in the pthread_cond_t anyway. There's enough space.
@@ -355,34 +358,57 @@ fn cond_clock_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> u64 {
355358
.offset(Size::from_bytes(offset), ecx.machine.layouts.i32, ecx)
356359
.unwrap();
357360
let id = ecx.read_scalar(&id_field).unwrap().to_i32().unwrap();
361+
let id = translate_clock_id(ecx, id).expect("static initializer should be valid");
358362
assert!(
359-
is_cond_clock_realtime(ecx, id),
363+
matches!(id, ClockId::Realtime),
360364
"PTHREAD_COND_INITIALIZER is incompatible with our pthread_cond layout: clock is not CLOCK_REALTIME"
361365
);
362366
}
363367

364368
offset
365369
}
366370

371+
#[derive(Debug, Clone, Copy)]
372+
enum ClockId {
373+
Realtime,
374+
Monotonic,
375+
}
376+
377+
#[derive(Debug)]
378+
/// Additional data that we attach with each cond instance.
379+
struct AdditionalCondData {
380+
/// The address of the cond.
381+
address: u64,
382+
383+
/// The clock id of the cond.
384+
clock_id: ClockId,
385+
}
386+
367387
fn cond_get_id<'tcx>(
368388
ecx: &mut MiriInterpCx<'tcx>,
369389
cond_ptr: &OpTy<'tcx>,
370390
) -> InterpResult<'tcx, CondvarId> {
371391
let cond = ecx.deref_pointer(cond_ptr)?;
372-
ecx.condvar_get_or_create_id(&cond, cond_id_offset(ecx)?)
373-
}
392+
let address = cond.ptr().addr().bytes();
393+
let id = ecx.condvar_get_or_create_id(&cond, cond_id_offset(ecx)?, |ecx| {
394+
let raw_id = if ecx.tcx.sess.target.os == "macos" {
395+
ecx.eval_libc_i32("CLOCK_REALTIME")
396+
} else {
397+
cond_get_clock_id(ecx, cond_ptr)?
398+
};
399+
let clock_id = translate_clock_id(ecx, raw_id)?;
400+
Ok(Some(Box::new(AdditionalCondData { address, clock_id })))
401+
})?;
374402

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-
)
403+
// Check that the mutex has not been moved since last use.
404+
let data = ecx
405+
.condvar_get_data::<AdditionalCondData>(id)
406+
.expect("data should always exist for pthreads");
407+
if data.address != address {
408+
throw_ub_format!("pthread_cond_t can't be moved after first use")
409+
}
410+
411+
Ok(id)
386412
}
387413

388414
fn cond_get_clock_id<'tcx>(
@@ -398,20 +424,6 @@ fn cond_get_clock_id<'tcx>(
398424
.to_i32()
399425
}
400426

401-
fn cond_set_clock_id<'tcx>(
402-
ecx: &mut MiriInterpCx<'tcx>,
403-
cond_ptr: &OpTy<'tcx>,
404-
clock_id: i32,
405-
) -> InterpResult<'tcx, ()> {
406-
ecx.deref_pointer_and_write(
407-
cond_ptr,
408-
cond_clock_offset(ecx),
409-
Scalar::from_i32(clock_id),
410-
ecx.libc_ty_layout("pthread_cond_t"),
411-
ecx.machine.layouts.i32,
412-
)
413-
}
414-
415427
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
416428
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
417429
fn pthread_mutexattr_init(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
@@ -820,11 +832,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
820832
} else {
821833
condattr_get_clock_id(this, attr_op)?
822834
};
823-
824-
// Write 0 to use the same code path as the static initializers.
825-
cond_reset_id(this, cond_op)?;
826-
827-
cond_set_clock_id(this, cond_op, clock_id)?;
835+
let clock_id = translate_clock_id(this, clock_id)?;
836+
837+
let cond = this.deref_pointer(cond_op)?;
838+
let address = cond.ptr().addr().bytes();
839+
this.condvar_create(
840+
&cond,
841+
cond_id_offset(this)?,
842+
Some(Box::new(AdditionalCondData { address, clock_id })),
843+
)?;
828844

829845
Ok(())
830846
}
@@ -879,7 +895,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
879895
let mutex_id = mutex_get_id(this, mutex_op)?;
880896

881897
// Extract the timeout.
882-
let clock_id = cond_get_clock_id(this, cond_op)?;
898+
let clock_id = this
899+
.condvar_get_data::<AdditionalCondData>(id)
900+
.expect("additional data should always be present for pthreads")
901+
.clock_id;
883902
let duration = match this
884903
.read_timespec(&this.deref_pointer_as(abstime_op, this.libc_ty_layout("timespec"))?)?
885904
{
@@ -890,13 +909,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
890909
return Ok(());
891910
}
892911
};
893-
let timeout_clock = if is_cond_clock_realtime(this, clock_id) {
894-
this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
895-
TimeoutClock::RealTime
896-
} else if clock_id == this.eval_libc_i32("CLOCK_MONOTONIC") {
897-
TimeoutClock::Monotonic
898-
} else {
899-
throw_unsup_format!("unsupported clock id: {}", clock_id);
912+
let timeout_clock = match clock_id {
913+
ClockId::Realtime => {
914+
this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
915+
TimeoutClock::RealTime
916+
}
917+
ClockId::Monotonic => TimeoutClock::Monotonic,
900918
};
901919

902920
this.condvar_wait(
@@ -912,17 +930,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
912930
}
913931

914932
fn pthread_cond_destroy(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
933+
//NOTE: Destroying an uninit pthread_cond is UB. Make sure it's not uninit,
934+
// by accessing at least once all of its fields that we use.
935+
915936
let this = self.eval_context_mut();
916937

917938
let id = cond_get_id(this, cond_op)?;
918939
if this.condvar_is_awaited(id) {
919940
throw_ub_format!("destroying an awaited conditional variable");
920941
}
921942

922-
// Destroying an uninit pthread_cond is UB, so check to make sure it's not uninit.
923-
cond_get_id(this, cond_op)?;
924-
cond_get_clock_id(this, cond_op)?;
925-
926943
// This might lead to false positives, see comment in pthread_mutexattr_destroy
927944
this.write_uninit(&this.deref_pointer_as(cond_op, this.libc_ty_layout("pthread_cond_t"))?)?;
928945
// FIXME: delete interpreter state associated with this condvar.
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)