Skip to content

Commit e5597b2

Browse files
committed
sanity-check the pthread offsets against the static initializers
1 parent ba1a00a commit e5597b2

File tree

2 files changed

+123
-26
lines changed

2 files changed

+123
-26
lines changed

src/tools/miri/src/helpers.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,14 +255,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
255255
}
256256

257257
/// Evaluates the scalar at the specified path.
258-
fn eval_path_scalar(&self, path: &[&str]) -> Scalar<Provenance> {
258+
fn eval_path(&self, path: &[&str]) -> OpTy<'tcx, Provenance> {
259259
let this = self.eval_context_ref();
260260
let instance = this.resolve_path(path, Namespace::ValueNS);
261261
// We don't give a span -- this isn't actually used directly by the program anyway.
262262
let const_val = this.eval_global(instance).unwrap_or_else(|err| {
263263
panic!("failed to evaluate required Rust item: {path:?}\n{err:?}")
264264
});
265-
this.read_scalar(&const_val)
265+
const_val.into()
266+
}
267+
fn eval_path_scalar(&self, path: &[&str]) -> Scalar<Provenance> {
268+
let this = self.eval_context_ref();
269+
let val = this.eval_path(path);
270+
this.read_scalar(&val)
266271
.unwrap_or_else(|err| panic!("failed to read required Rust item: {path:?}\n{err:?}"))
267272
}
268273

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

Lines changed: 116 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
use std::sync::atomic::{AtomicBool, Ordering};
12
use std::time::SystemTime;
23

4+
use rustc_target::abi::Size;
5+
36
use crate::concurrency::thread::MachineCallback;
47
use crate::*;
58

@@ -71,23 +74,54 @@ fn is_mutex_kind_normal<'mir, 'tcx: 'mir>(
7174
// - id: u32
7275
// - kind: i32
7376

74-
#[inline]
7577
fn mutex_id_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, u64> {
76-
// When adding a new OS, make sure its PTHREAD_MUTEX_INITIALIZER is 0 for this offset
77-
// (and the `mutex_kind_offset` below).
78-
Ok(match &*ecx.tcx.sess.target.os {
78+
let offset = match &*ecx.tcx.sess.target.os {
7979
"linux" | "illumos" | "solaris" => 0,
8080
// macOS stores a signature in the first bytes, so we have to move to offset 4.
8181
"macos" => 4,
8282
os => throw_unsup_format!("`pthread_mutex` is not supported on {os}"),
83-
})
83+
};
84+
85+
// Sanity-check this against PTHREAD_MUTEX_INITIALIZER (but only once):
86+
// the id must start out as 0.
87+
static SANITY: AtomicBool = AtomicBool::new(false);
88+
if !SANITY.swap(true, Ordering::Relaxed) {
89+
let static_initializer = ecx.eval_path(&["libc", "PTHREAD_MUTEX_INITIALIZER"]);
90+
let id_field = static_initializer
91+
.offset(Size::from_bytes(offset), ecx.machine.layouts.u32, ecx)
92+
.unwrap();
93+
let id = ecx.read_scalar(&id_field).unwrap().to_u32().unwrap();
94+
assert_eq!(
95+
id, 0,
96+
"PTHREAD_MUTEX_INITIALIZER is incompatible with our pthread_mutex layout: id is not 0"
97+
);
98+
}
99+
100+
Ok(offset)
84101
}
85102

86-
#[inline]
87103
fn mutex_kind_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> u64 {
88104
// These offsets are picked for compatibility with Linux's static initializer
89105
// macros, e.g. PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP.)
90-
if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }
106+
let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 };
107+
108+
// Sanity-check this against PTHREAD_MUTEX_INITIALIZER (but only once):
109+
// the kind must start out as PTHREAD_MUTEX_DEFAULT.
110+
static SANITY: AtomicBool = AtomicBool::new(false);
111+
if !SANITY.swap(true, Ordering::Relaxed) {
112+
let static_initializer = ecx.eval_path(&["libc", "PTHREAD_MUTEX_INITIALIZER"]);
113+
let kind_field = static_initializer
114+
.offset(Size::from_bytes(mutex_kind_offset(ecx)), ecx.machine.layouts.i32, ecx)
115+
.unwrap();
116+
let kind = ecx.read_scalar(&kind_field).unwrap().to_i32().unwrap();
117+
assert_eq!(
118+
kind,
119+
ecx.eval_libc_i32("PTHREAD_MUTEX_DEFAULT"),
120+
"PTHREAD_MUTEX_INITIALIZER is incompatible with our pthread_mutex layout: kind is not PTHREAD_MUTEX_DEFAULT"
121+
);
122+
}
123+
124+
offset
91125
}
92126

93127
fn mutex_get_id<'mir, 'tcx: 'mir>(
@@ -108,7 +142,7 @@ fn mutex_reset_id<'mir, 'tcx: 'mir>(
108142
ecx.deref_pointer_and_write(
109143
mutex_op,
110144
mutex_id_offset(ecx)?,
111-
Scalar::from_i32(0),
145+
Scalar::from_u32(0),
112146
ecx.libc_ty_layout("pthread_mutex_t"),
113147
ecx.machine.layouts.u32,
114148
)
@@ -145,15 +179,30 @@ fn mutex_set_kind<'mir, 'tcx: 'mir>(
145179
// We ignore the platform layout and store our own fields:
146180
// - id: u32
147181

148-
#[inline]
149182
fn rwlock_id_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, u64> {
150-
// When adding a new OS, make sure its PTHREAD_RWLOCK_INITIALIZER is 0 for this offset.
151-
Ok(match &*ecx.tcx.sess.target.os {
183+
let offset = match &*ecx.tcx.sess.target.os {
152184
"linux" | "illumos" | "solaris" => 0,
153185
// macOS stores a signature in the first bytes, so we have to move to offset 4.
154186
"macos" => 4,
155187
os => throw_unsup_format!("`pthread_rwlock` is not supported on {os}"),
156-
})
188+
};
189+
190+
// Sanity-check this against PTHREAD_RWLOCK_INITIALIZER (but only once):
191+
// the id must start out as 0.
192+
static SANITY: AtomicBool = AtomicBool::new(false);
193+
if !SANITY.swap(true, Ordering::Relaxed) {
194+
let static_initializer = ecx.eval_path(&["libc", "PTHREAD_RWLOCK_INITIALIZER"]);
195+
let id_field = static_initializer
196+
.offset(Size::from_bytes(offset), ecx.machine.layouts.u32, ecx)
197+
.unwrap();
198+
let id = ecx.read_scalar(&id_field).unwrap().to_u32().unwrap();
199+
assert_eq!(
200+
id, 0,
201+
"PTHREAD_RWLOCK_INITIALIZER is incompatible with our pthread_rwlock layout: id is not 0"
202+
);
203+
}
204+
205+
Ok(offset)
157206
}
158207

159208
fn rwlock_get_id<'mir, 'tcx: 'mir>(
@@ -214,21 +263,64 @@ fn condattr_set_clock_id<'mir, 'tcx: 'mir>(
214263
// - id: u32
215264
// - clock: i32
216265

217-
#[inline]
218266
fn cond_id_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, u64> {
219-
// When adding a new OS, make sure its PTHREAD_COND_INITIALIZER is 0 for this offset
220-
// (and the `COND_CLOCK_OFFSET` below is initialized to `CLOCK_REALTIME`).
221-
Ok(match &*ecx.tcx.sess.target.os {
267+
let offset = match &*ecx.tcx.sess.target.os {
222268
"linux" | "illumos" | "solaris" => 0,
223269
// macOS stores a signature in the first bytes, so we have to move to offset 4.
224270
"macos" => 4,
225271
os => throw_unsup_format!("`pthread_cond` is not supported on {os}"),
226-
})
272+
};
273+
274+
// Sanity-check this against PTHREAD_COND_INITIALIZER (but only once):
275+
// the id must start out as 0.
276+
static SANITY: AtomicBool = AtomicBool::new(false);
277+
if !SANITY.swap(true, Ordering::Relaxed) {
278+
let static_initializer = ecx.eval_path(&["libc", "PTHREAD_COND_INITIALIZER"]);
279+
let id_field = static_initializer
280+
.offset(Size::from_bytes(offset), ecx.machine.layouts.u32, ecx)
281+
.unwrap();
282+
let id = ecx.read_scalar(&id_field).unwrap().to_u32().unwrap();
283+
assert_eq!(
284+
id, 0,
285+
"PTHREAD_COND_INITIALIZER is incompatible with our pthread_cond layout: id is not 0"
286+
);
287+
}
288+
289+
Ok(offset)
290+
}
291+
292+
/// Determines whether this clock represents the real-time clock, CLOCK_REALTIME.
293+
fn is_cond_clock_realtime<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>, clock_id: i32) -> bool {
294+
// To ensure compatibility with PTHREAD_COND_INITIALIZER on all platforms,
295+
// we can't just compare with CLOCK_REALTIME: on Solarish, PTHREAD_COND_INITIALIZER
296+
// makes the clock 0 but CLOCK_REALTIME is 3.
297+
// However, we need to always be able to distinguish this from CLOCK_MONOTONIC.
298+
clock_id == ecx.eval_libc_i32("CLOCK_REALTIME")
299+
|| (clock_id == 0 && clock_id != ecx.eval_libc_i32("CLOCK_MONOTONIC"))
227300
}
228301

229-
// macOS doesn't have a clock attribute, but to keep the code uniform we store
230-
// a clock ID in the pthread_cond_t anyway. There's enough space.
231-
const COND_CLOCK_OFFSET: u64 = 8;
302+
fn cond_clock_offset<'mir, 'tcx: 'mir>(ecx: &MiriInterpCx<'mir, 'tcx>) -> u64 {
303+
// macOS doesn't have a clock attribute, but to keep the code uniform we store
304+
// a clock ID in the pthread_cond_t anyway. There's enough space.
305+
let offset = 8;
306+
307+
// Sanity-check this against PTHREAD_COND_INITIALIZER (but only once):
308+
// the clock must start out as CLOCK_REALTIME.
309+
static SANITY: AtomicBool = AtomicBool::new(false);
310+
if !SANITY.swap(true, Ordering::Relaxed) {
311+
let static_initializer = ecx.eval_path(&["libc", "PTHREAD_COND_INITIALIZER"]);
312+
let id_field = static_initializer
313+
.offset(Size::from_bytes(offset), ecx.machine.layouts.i32, ecx)
314+
.unwrap();
315+
let id = ecx.read_scalar(&id_field).unwrap().to_i32().unwrap();
316+
assert!(
317+
is_cond_clock_realtime(ecx, id),
318+
"PTHREAD_COND_INITIALIZER is incompatible with our pthread_cond layout: clock is not CLOCK_REALTIME"
319+
);
320+
}
321+
322+
offset
323+
}
232324

233325
fn cond_get_id<'mir, 'tcx: 'mir>(
234326
ecx: &mut MiriInterpCx<'mir, 'tcx>,
@@ -260,7 +352,7 @@ fn cond_get_clock_id<'mir, 'tcx: 'mir>(
260352
) -> InterpResult<'tcx, i32> {
261353
ecx.deref_pointer_and_read(
262354
cond_op,
263-
COND_CLOCK_OFFSET,
355+
cond_clock_offset(ecx),
264356
ecx.libc_ty_layout("pthread_cond_t"),
265357
ecx.machine.layouts.i32,
266358
)?
@@ -274,7 +366,7 @@ fn cond_set_clock_id<'mir, 'tcx: 'mir>(
274366
) -> InterpResult<'tcx, ()> {
275367
ecx.deref_pointer_and_write(
276368
cond_op,
277-
COND_CLOCK_OFFSET,
369+
cond_clock_offset(ecx),
278370
Scalar::from_i32(clock_id),
279371
ecx.libc_ty_layout("pthread_cond_t"),
280372
ecx.machine.layouts.i32,
@@ -776,7 +868,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
776868
let this = self.eval_context_mut();
777869

778870
let attr = this.read_pointer(attr_op)?;
779-
// Default clock if att is null, and on macOS where there is no clock attribute.
871+
// Default clock if `attr` is null, and on macOS where there is no clock attribute.
780872
let clock_id = if this.ptr_is_null(attr)? || this.tcx.sess.target.os == "macos" {
781873
this.eval_libc_i32("CLOCK_REALTIME")
782874
} else {
@@ -858,7 +950,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
858950
}
859951
};
860952

861-
let timeout_time = if clock_id == this.eval_libc_i32("CLOCK_REALTIME") {
953+
let timeout_time = if is_cond_clock_realtime(this, clock_id) {
862954
this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
863955
CallbackTime::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
864956
} else if clock_id == this.eval_libc_i32("CLOCK_MONOTONIC") {

0 commit comments

Comments
 (0)