Skip to content

Condvar and Mutex in std::sys::pal::unix::sync violate aliasing rules #160815

Description

@maxdexh

Initially found by @RalfJung while discussing code surrounding #160219 on zulip, and expanded on in this issue.

Note that the text of this issue is overly focused on Parker::new_in_place's usage of Condvar. It turns out (see comments) that due to the Drop impl of Condvar (and Mutex), it is impossible to use these APIs correctly in their current state.


To construct a pinned Parker in-place, Thread::new does so behind MaybeUninit by using Arc::get_mut_unchecked and deriving a pointer from the mutable reference:

let inner = unsafe {
let mut arc = Arc::<Inner, _>::new_uninit_in(System);
let ptr = Arc::get_mut_unchecked(&mut arc).as_mut_ptr();
(&raw mut (*ptr).name).write(name);
(&raw mut (*ptr).id).write(id);
Parker::new_in_place(&raw mut (*ptr).parker);
Pin::new_unchecked(arc.assume_init())
};

This is needed to initialize a pinned Condvar on platforms where Parker uses one, since it cannot be moved after being initialized:

pub unsafe fn new_in_place(parker: *mut Parker) {
parker.write(Parker {
state: AtomicUsize::new(EMPTY),
lock: Mutex::new(),
cvar: Condvar::new(),
});
Pin::new_unchecked(&mut (*parker).cvar).init();
}

Then Condvar::init ends up calling phread_cond_init:

pub unsafe fn init(self: Pin<&mut Self>) {
use crate::mem::MaybeUninit;
struct AttrGuard<'a>(pub &'a mut MaybeUninit<libc::pthread_condattr_t>);
impl Drop for AttrGuard<'_> {
fn drop(&mut self) {
unsafe {
let result = libc::pthread_condattr_destroy(self.0.as_mut_ptr());
assert_eq!(result, 0);
}
}
}
unsafe {
let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
let r = libc::pthread_condattr_init(attr.as_mut_ptr());
assert_eq!(r, 0);
let attr = AttrGuard(&mut attr);
let r = libc::pthread_condattr_setclock(attr.0.as_mut_ptr(), Self::CLOCK);
assert_eq!(r, 0);
let r = libc::pthread_cond_init(self.raw(), attr.0.as_ptr());
assert_eq!(r, 0);
}
}

As discussed on zulip, pthread_cond_init is allowed to store the pointer for later use (this is the reason they need to be pinned). For example, on IBM AIX (which this code is run on), condvars are documented as being held in an intrusive linked list.

Now, this means that the condvar's stored pointer (which was derived from a mutable reference) is invalidated when the Arc inside Thread is borrowed from the next time, e.g. through Thread::id. Any access to it from libc later on will violate the aliasing rules.

@rustbot label T-opsem T-libs I-unsound A-thread -T-bootstrap A-pin

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-pinArea: PinA-threadArea: `std::thread`C-bugCategory: This is a bug.I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-libsRelevant to the library team, which will review and decide on the PR/issue.T-opsemRelevant to the opsem teamneeds-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions