Skip to content

Commit 23a9d02

Browse files
committed
Auto merge of #1933 - 5225225:1931-condvar-false-positive, r=RalfJung
Fix false positive use of uninit bytes when calling `libc::pthread_condattr_destroy` Fixes: #1931
2 parents 81e59e6 + fd830e7 commit 23a9d02

8 files changed

+165
-19
lines changed

src/shims/posix/sync.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,12 @@ fn condattr_set_clock_id<'mir, 'tcx: 'mir>(
186186
attr_op: &OpTy<'tcx, Tag>,
187187
clock_id: impl Into<ScalarMaybeUninit<Tag>>,
188188
) -> InterpResult<'tcx, ()> {
189-
ecx.write_scalar_at_offset(attr_op, 0, clock_id, ecx.machine.layouts.i32)
189+
ecx.write_scalar_at_offset(
190+
attr_op,
191+
0,
192+
clock_id,
193+
layout_of_maybe_uninit(ecx.tcx, ecx.machine.layouts.i32.ty),
194+
)
190195
}
191196

192197
// pthread_cond_t
@@ -359,6 +364,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
359364
fn pthread_mutexattr_destroy(&mut self, attr_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
360365
let this = self.eval_context_mut();
361366

367+
// Destroying an uninit pthread_mutexattr is UB, so check to make sure it's not uninit.
368+
mutexattr_get_kind(this, attr_op)?.check_init()?;
369+
370+
// To catch double-destroys, we de-initialize the mutexattr.
371+
// This is technically not right and might lead to false positives. For example, the below
372+
// code is *likely* sound, even assuming uninit numbers are UB, but miri with
373+
// -Zmiri-check-number-validity complains
374+
//
375+
// let mut x: MaybeUninit<libc::pthread_mutexattr_t> = MaybeUninit::zeroed();
376+
// libc::pthread_mutexattr_init(x.as_mut_ptr());
377+
// libc::pthread_mutexattr_destroy(x.as_mut_ptr());
378+
// x.assume_init();
379+
//
380+
// However, the way libstd uses the pthread APIs works in our favor here, so we can get away with this.
381+
// This can always be revisited to have some external state to catch double-destroys
382+
// but not complain about the above code. See https://github.com/rust-lang/miri/pull/1933
383+
362384
mutexattr_set_kind(this, attr_op, ScalarMaybeUninit::Uninit)?;
363385

364386
Ok(0)
@@ -497,6 +519,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
497519
throw_ub_format!("destroyed a locked mutex");
498520
}
499521

522+
// Destroying an uninit pthread_mutex is UB, so check to make sure it's not uninit.
523+
mutex_get_kind(this, mutex_op)?.check_init()?;
524+
mutex_get_id(this, mutex_op)?.check_init()?;
525+
526+
// This might lead to false positives, see comment in pthread_mutexattr_destroy
500527
mutex_set_kind(this, mutex_op, ScalarMaybeUninit::Uninit)?;
501528
mutex_set_id(this, mutex_op, ScalarMaybeUninit::Uninit)?;
502529
// FIXME: delete interpreter state associated with this mutex.
@@ -598,6 +625,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
598625
throw_ub_format!("destroyed a locked rwlock");
599626
}
600627

628+
// Destroying an uninit pthread_rwlock is UB, so check to make sure it's not uninit.
629+
rwlock_get_id(this, rwlock_op)?.check_init()?;
630+
631+
// This might lead to false positives, see comment in pthread_mutexattr_destroy
601632
rwlock_set_id(this, rwlock_op, ScalarMaybeUninit::Uninit)?;
602633
// FIXME: delete interpreter state associated with this rwlock.
603634

@@ -652,6 +683,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
652683
fn pthread_condattr_destroy(&mut self, attr_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
653684
let this = self.eval_context_mut();
654685

686+
// Destroying an uninit pthread_condattr is UB, so check to make sure it's not uninit.
687+
condattr_get_clock_id(this, attr_op)?.check_init()?;
688+
689+
// This might lead to false positives, see comment in pthread_mutexattr_destroy
655690
condattr_set_clock_id(this, attr_op, ScalarMaybeUninit::Uninit)?;
656691

657692
Ok(0)
@@ -789,6 +824,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
789824
if this.condvar_is_awaited(id) {
790825
throw_ub_format!("destroying an awaited conditional variable");
791826
}
827+
828+
// Destroying an uninit pthread_cond is UB, so check to make sure it's not uninit.
829+
cond_get_id(this, cond_op)?.check_init()?;
830+
cond_get_clock_id(this, cond_op)?.check_init()?;
831+
832+
// This might lead to false positives, see comment in pthread_mutexattr_destroy
792833
cond_set_id(this, cond_op, ScalarMaybeUninit::Uninit)?;
793834
cond_set_clock_id(this, cond_op, ScalarMaybeUninit::Uninit)?;
794835
// FIXME: delete interpreter state associated with this condvar.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// ignore-windows: No libc on Windows
2+
#![feature(rustc_private)]
3+
4+
/// Test that destroying a pthread_cond twice fails, even without a check for number validity
5+
extern crate libc;
6+
7+
fn main() {
8+
unsafe {
9+
use core::mem::MaybeUninit;
10+
let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
11+
libc::pthread_condattr_init(attr.as_mut_ptr());
12+
13+
let mut cond = MaybeUninit::<libc::pthread_cond_t>::uninit();
14+
15+
libc::pthread_cond_init(cond.as_mut_ptr(), attr.as_ptr());
16+
17+
libc::pthread_cond_destroy(cond.as_mut_ptr());
18+
19+
libc::pthread_cond_destroy(cond.as_mut_ptr());
20+
//~^ Undefined Behavior: using uninitialized data, but this operation requires initialized memory
21+
}
22+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// ignore-windows: No libc on Windows
2+
#![feature(rustc_private)]
3+
4+
/// Test that destroying a pthread_condattr twice fails, even without a check for number validity
5+
extern crate libc;
6+
7+
fn main() {
8+
unsafe {
9+
use core::mem::MaybeUninit;
10+
let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
11+
12+
libc::pthread_condattr_init(attr.as_mut_ptr());
13+
14+
libc::pthread_condattr_destroy(attr.as_mut_ptr());
15+
16+
libc::pthread_condattr_destroy(attr.as_mut_ptr());
17+
//~^ Undefined Behavior: using uninitialized data, but this operation requires initialized memory
18+
}
19+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// ignore-windows: No libc on Windows
2+
#![feature(rustc_private)]
3+
4+
/// Test that destroying a pthread_mutex twice fails, even without a check for number validity
5+
extern crate libc;
6+
7+
fn main() {
8+
unsafe {
9+
use core::mem::MaybeUninit;
10+
11+
let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
12+
libc::pthread_mutexattr_init(attr.as_mut_ptr());
13+
14+
let mut mutex = MaybeUninit::<libc::pthread_mutex_t>::uninit();
15+
16+
libc::pthread_mutex_init(mutex.as_mut_ptr(), attr.as_ptr());
17+
18+
libc::pthread_mutex_destroy(mutex.as_mut_ptr());
19+
20+
libc::pthread_mutex_destroy(mutex.as_mut_ptr());
21+
//~^ Undefined Behavior: using uninitialized data, but this operation requires initialized memory
22+
}
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// ignore-windows: No libc on Windows
2+
#![feature(rustc_private)]
3+
4+
/// Test that destroying a pthread_mutexattr twice fails, even without a check for number validity
5+
extern crate libc;
6+
7+
fn main() {
8+
unsafe {
9+
use core::mem::MaybeUninit;
10+
let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
11+
12+
libc::pthread_mutexattr_init(attr.as_mut_ptr());
13+
14+
libc::pthread_mutexattr_destroy(attr.as_mut_ptr());
15+
16+
libc::pthread_mutexattr_destroy(attr.as_mut_ptr());
17+
//~^ Undefined Behavior: using uninitialized data, but this operation requires initialized memory
18+
}
19+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// ignore-windows: No libc on Windows
2+
#![feature(rustc_private)]
3+
4+
/// Test that destroying a pthread_rwlock twice fails, even without a check for number validity
5+
extern crate libc;
6+
7+
fn main() {
8+
unsafe {
9+
let mut lock = libc::PTHREAD_RWLOCK_INITIALIZER;
10+
11+
libc::pthread_rwlock_destroy(&mut lock);
12+
13+
libc::pthread_rwlock_destroy(&mut lock);
14+
//~^ Undefined Behavior: using uninitialized data, but this operation requires initialized memory
15+
}
16+
}

tests/run-pass/concurrency/libc_pthread_cond.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,47 @@
11
// ignore-windows: No libc on Windows
22
// ignore-macos: pthread_condattr_setclock is not supported on MacOS.
3-
// compile-flags: -Zmiri-disable-isolation
3+
// compile-flags: -Zmiri-disable-isolation -Zmiri-check-number-validity
44

55
#![feature(rustc_private)]
66

77
/// Test that conditional variable timeouts are working properly with both
88
/// monotonic and system clocks.
99
extern crate libc;
1010

11-
use std::mem;
11+
use std::mem::MaybeUninit;
1212
use std::time::Instant;
1313

1414
fn test_timed_wait_timeout(clock_id: i32) {
1515
unsafe {
16-
let mut attr: libc::pthread_condattr_t = mem::zeroed();
17-
assert_eq!(libc::pthread_condattr_init(&mut attr as *mut _), 0);
18-
assert_eq!(libc::pthread_condattr_setclock(&mut attr as *mut _, clock_id), 0);
16+
let mut attr: MaybeUninit<libc::pthread_condattr_t> = MaybeUninit::uninit();
17+
assert_eq!(libc::pthread_condattr_init(attr.as_mut_ptr()), 0);
18+
assert_eq!(libc::pthread_condattr_setclock(attr.as_mut_ptr(), clock_id), 0);
1919

20-
let mut cond: libc::pthread_cond_t = mem::zeroed();
21-
assert_eq!(libc::pthread_cond_init(&mut cond as *mut _, &attr as *const _), 0);
22-
assert_eq!(libc::pthread_condattr_destroy(&mut attr as *mut _), 0);
20+
let mut cond: MaybeUninit<libc::pthread_cond_t> = MaybeUninit::uninit();
21+
assert_eq!(libc::pthread_cond_init(cond.as_mut_ptr(), attr.as_ptr()), 0);
22+
assert_eq!(libc::pthread_condattr_destroy(attr.as_mut_ptr()), 0);
2323

24-
let mut mutex: libc::pthread_mutex_t = mem::zeroed();
24+
let mut mutex: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER;
2525

26-
let mut now: libc::timespec = mem::zeroed();
27-
assert_eq!(libc::clock_gettime(clock_id, &mut now), 0);
26+
let mut now_mu: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
27+
assert_eq!(libc::clock_gettime(clock_id, now_mu.as_mut_ptr()), 0);
28+
let now = now_mu.assume_init();
2829
// Waiting for a second... mostly because waiting less requires mich more tricky arithmetic.
2930
// FIXME: wait less.
3031
let timeout = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: now.tv_nsec };
3132

3233
assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0);
3334
let current_time = Instant::now();
3435
assert_eq!(
35-
libc::pthread_cond_timedwait(&mut cond as *mut _, &mut mutex as *mut _, &timeout),
36+
libc::pthread_cond_timedwait(cond.as_mut_ptr(), &mut mutex as *mut _, &timeout),
3637
libc::ETIMEDOUT
3738
);
3839
let elapsed_time = current_time.elapsed().as_millis();
3940
assert!(900 <= elapsed_time && elapsed_time <= 1300);
4041

4142
// Test calling `pthread_cond_timedwait` again with an already elapsed timeout.
4243
assert_eq!(
43-
libc::pthread_cond_timedwait(&mut cond as *mut _, &mut mutex as *mut _, &timeout),
44+
libc::pthread_cond_timedwait(cond.as_mut_ptr(), &mut mutex as *mut _, &timeout),
4445
libc::ETIMEDOUT
4546
);
4647

@@ -49,7 +50,7 @@ fn test_timed_wait_timeout(clock_id: i32) {
4950
let invalid_timeout_1 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: 1_000_000_000 };
5051
assert_eq!(
5152
libc::pthread_cond_timedwait(
52-
&mut cond as *mut _,
53+
cond.as_mut_ptr(),
5354
&mut mutex as *mut _,
5455
&invalid_timeout_1
5556
),
@@ -58,7 +59,7 @@ fn test_timed_wait_timeout(clock_id: i32) {
5859
let invalid_timeout_2 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: -1 };
5960
assert_eq!(
6061
libc::pthread_cond_timedwait(
61-
&mut cond as *mut _,
62+
cond.as_mut_ptr(),
6263
&mut mutex as *mut _,
6364
&invalid_timeout_2
6465
),
@@ -68,7 +69,7 @@ fn test_timed_wait_timeout(clock_id: i32) {
6869
let invalid_timeout_3 = libc::timespec { tv_sec: -1, tv_nsec: 0 };
6970
assert_eq!(
7071
libc::pthread_cond_timedwait(
71-
&mut cond as *mut _,
72+
cond.as_mut_ptr(),
7273
&mut mutex as *mut _,
7374
&invalid_timeout_3
7475
),
@@ -77,7 +78,7 @@ fn test_timed_wait_timeout(clock_id: i32) {
7778

7879
assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0);
7980
assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0);
80-
assert_eq!(libc::pthread_cond_destroy(&mut cond as *mut _), 0);
81+
assert_eq!(libc::pthread_cond_destroy(cond.as_mut_ptr()), 0);
8182
}
8283
}
8384

tests/run-pass/concurrency/sync.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ignore-windows: Concurrency on Windows is not supported yet.
2-
// compile-flags: -Zmiri-disable-isolation
2+
// compile-flags: -Zmiri-disable-isolation -Zmiri-check-number-validity
33

44
use std::sync::mpsc::{channel, sync_channel};
55
use std::sync::{Arc, Barrier, Condvar, Mutex, Once, RwLock};
@@ -340,6 +340,10 @@ fn park_unpark() {
340340
assert!((200..1000).contains(&start.elapsed().as_millis()));
341341
}
342342

343+
fn check_condvar() {
344+
let _ = std::sync::Condvar::new();
345+
}
346+
343347
fn main() {
344348
check_barriers();
345349
check_conditional_variables_notify_one();
@@ -357,4 +361,5 @@ fn main() {
357361
check_rwlock_unlock_bug2();
358362
park_timeout();
359363
park_unpark();
364+
check_condvar();
360365
}

0 commit comments

Comments
 (0)