detect when pthread_rwlock_t is moved

For some implementations of pthreads, the address of pthread_rwlock_t
(or its fields) is used to identify the lock. That means that if the
contents of a pthread_rwlock_t are moved in memory, effectively a new
lock object is created, which is completely independted from the
original. Thus we want to detect when when such objects are moved and
show an error.
This commit is contained in:
Konstantinos Andrikopoulos 2024-09-09 02:32:19 +02:00
parent 59cb24dc76
commit ff28977c06
5 changed files with 68 additions and 5 deletions

View file

@ -105,6 +105,13 @@ struct Mutex {
declare_id!(RwLockId);
#[derive(Debug)]
/// Additional data that may be used by shim implementations.
pub struct AdditionalRwLockData {
/// The address of the rwlock.
pub address: u64,
}
/// The read-write lock state.
#[derive(Default, Debug)]
struct RwLock {
@ -137,6 +144,9 @@ struct RwLock {
/// locks.
/// This is only relevant when there is an active reader.
clock_current_readers: VClock,
/// Additional data that can be set by shim implementations.
data: Option<AdditionalRwLockData>,
}
declare_id!(CondvarId);
@ -343,6 +353,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
lock_op: &OpTy<'tcx>,
lock_layout: TyAndLayout<'tcx>,
offset: u64,
initialize_data: impl for<'a> FnOnce(
&'a mut MiriInterpCx<'tcx>,
)
-> InterpResult<'tcx, Option<AdditionalRwLockData>>,
) -> InterpResult<'tcx, RwLockId> {
let this = self.eval_context_mut();
this.get_or_create_id(
@ -350,11 +364,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
lock_layout,
offset,
|ecx| &mut ecx.machine.sync.rwlocks,
|_| Ok(Default::default()),
|ecx| initialize_data(ecx).map(|data| RwLock { data, ..Default::default() }),
)?
.ok_or_else(|| err_ub_format!("rwlock has invalid ID").into())
}
/// Retrieve the additional data stored for a rwlock.
fn rwlock_get_data<'a>(&'a mut self, id: RwLockId) -> Option<&'a AdditionalRwLockData>
where
'tcx: 'a,
{
let this = self.eval_context_ref();
this.machine.sync.rwlocks[id].data.as_ref()
}
fn condvar_get_or_create_id(
&mut self,
lock_op: &OpTy<'tcx>,

View file

@ -134,8 +134,8 @@ pub use crate::concurrency::{
data_race::{AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _},
init_once::{EvalContextExt as _, InitOnceId},
sync::{
AdditionalMutexData, CondvarId, EvalContextExt as _, MutexId, MutexKind, RwLockId,
SynchronizationObjects,
AdditionalMutexData, AdditionalRwLockData, CondvarId, EvalContextExt as _, MutexId,
MutexKind, RwLockId, SynchronizationObjects,
},
thread::{
BlockReason, EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager,

View file

@ -213,11 +213,22 @@ fn rwlock_get_id<'tcx>(
ecx: &mut MiriInterpCx<'tcx>,
rwlock_op: &OpTy<'tcx>,
) -> InterpResult<'tcx, RwLockId> {
ecx.rwlock_get_or_create_id(
let address = ecx.read_pointer(rwlock_op)?.addr().bytes();
let id = ecx.rwlock_get_or_create_id(
rwlock_op,
ecx.libc_ty_layout("pthread_rwlock_t"),
rwlock_id_offset(ecx)?,
)
|_| Ok(Some(AdditionalRwLockData { address })),
)?;
// Check that the rwlock has not been moved since last use.
let data = ecx.rwlock_get_data(id).expect("data should be always exist for pthreads");
if data.address != address {
throw_ub_format!("pthread_rwlock_t can't be moved after first use")
}
Ok(id)
}
// pthread_condattr_t.

View file

@ -0,0 +1,14 @@
//@ignore-target-windows: No pthreads on Windows
fn main() {
unsafe {
let mut rw = libc::PTHREAD_RWLOCK_INITIALIZER;
libc::pthread_rwlock_rdlock(&mut rw as *mut _);
// Move rwlock
let mut rw2 = rw;
libc::pthread_rwlock_unlock(&mut rw2 as *mut _); //~ ERROR: pthread_rwlock_t can't be moved after first use
}
}

View file

@ -0,0 +1,15 @@
error: Undefined Behavior: pthread_rwlock_t can't be moved after first use
--> $DIR/libx_pthread_rwlock_moved.rs:LL:CC
|
LL | libc::pthread_rwlock_unlock(&mut rw2 as *mut _);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pthread_rwlock_t can't be moved after first use
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at $DIR/libx_pthread_rwlock_moved.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error