Skip to content

Commit d73f5e6

Browse files
committed
Fix undefined behavior when re-locking a mutex from the same thread
The only applies to pthread mutexes. We solve this by creating the mutex with the PTHREAD_MUTEX_NORMAL type, which guarantees that re-locking from the same thread will deadlock.
1 parent eea4f0c commit d73f5e6

File tree

4 files changed

+46
-1
lines changed

4 files changed

+46
-1
lines changed

src/libstd/sync/mutex.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,14 @@ impl<T> Mutex<T> {
190190
/// Creates a new mutex in an unlocked state ready for use.
191191
#[stable(feature = "rust1", since = "1.0.0")]
192192
pub fn new(t: T) -> Mutex<T> {
193-
Mutex {
193+
let mut m = Mutex {
194194
inner: box StaticMutex::new(),
195195
data: UnsafeCell::new(t),
196+
};
197+
unsafe {
198+
m.inner.lock.init();
196199
}
200+
m
197201
}
198202
}
199203

src/libstd/sys/common/mutex.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ impl Mutex {
2727
/// first used with any of the functions below.
2828
pub const fn new() -> Mutex { Mutex(imp::Mutex::new()) }
2929

30+
/// Prepare the mutex for use.
31+
///
32+
/// This should be called once the mutex is at a stable memory address.
33+
#[inline]
34+
pub unsafe fn init(&mut self) { self.0.init() }
35+
3036
/// Locks the mutex blocking the current thread until it is available.
3137
///
3238
/// Behavior is undefined if the mutex has been moved between this and any

src/libstd/sys/unix/mutex.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,39 @@ impl Mutex {
3030
Mutex { inner: UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER) }
3131
}
3232
#[inline]
33+
pub unsafe fn init(&mut self) {
34+
// Issue #33770
35+
//
36+
// A pthread mutex initialized with PTHREAD_MUTEX_INITIALIZER will have
37+
// a type of PTHREAD_MUTEX_DEFAULT, which has undefined behavior if you
38+
// try to re-lock it from the same thread when you already hold a lock.
39+
//
40+
// In practice, glibc takes advantage of this undefined behavior to
41+
// implement hardware lock elision, which uses hardware transactional
42+
// memory to avoid acquiring the lock. While a transaction is in
43+
// progress, the lock appears to be unlocked. This isn't a problem for
44+
// other threads since the transactional memory will abort if a conflict
45+
// is detected, however no abort is generated if re-locking from the
46+
// same thread.
47+
//
48+
// Since locking the same mutex twice will result in two aliasing &mut
49+
// references, we instead create the mutex with type
50+
// PTHREAD_MUTEX_NORMAL which is guaranteed to deadlock if we try to
51+
// re-lock it from the same thread, thus avoiding undefined behavior.
52+
//
53+
// We can't do anything for StaticMutex, but that type is deprecated
54+
// anyways.
55+
let mut attr: libc::pthread_mutexattr_t = mem::uninitialized();
56+
let r = libc::pthread_mutexattr_init(&mut attr);
57+
debug_assert_eq!(r, 0);
58+
let r = libc::pthread_mutexattr_settype(&mut attr, libc::PTHREAD_MUTEX_NORMAL);
59+
debug_assert_eq!(r, 0);
60+
let r = libc::pthread_mutex_init(self.inner.get(), &attr);
61+
debug_assert_eq!(r, 0);
62+
let r = libc::pthread_mutexattr_destroy(&mut attr);
63+
debug_assert_eq!(r, 0);
64+
}
65+
#[inline]
3366
pub unsafe fn lock(&self) {
3467
let r = libc::pthread_mutex_lock(self.inner.get());
3568
debug_assert_eq!(r, 0);

src/libstd/sys/windows/mutex.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ impl Mutex {
6464
held: UnsafeCell::new(false),
6565
}
6666
}
67+
#[inline]
68+
pub unsafe fn init(&mut self) {}
6769
pub unsafe fn lock(&self) {
6870
match kind() {
6971
Kind::SRWLock => c::AcquireSRWLockExclusive(raw(self)),

0 commit comments

Comments
 (0)