Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync: fix Notify to clone the waker before locking its waiter list #4129

Merged
merged 1 commit into from Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion tokio/src/sync/notify.rs
Expand Up @@ -551,6 +551,10 @@ impl Future for Notified<'_> {
return Poll::Ready(());
}

// Clone the waker before locking, a waker clone can be
// triggering arbitrary code.
let waker = cx.waker().clone();

// Acquire the lock and attempt to transition to the waiting
// state.
let mut waiters = notify.waiters.lock();
Expand Down Expand Up @@ -612,7 +616,7 @@ impl Future for Notified<'_> {

// Safety: called while locked.
unsafe {
(*waiter.get()).waker = Some(cx.waker().clone());
(*waiter.get()).waker = Some(waker);
}

// Insert the waiter into the linked list
Expand Down
1 change: 1 addition & 0 deletions tokio/src/sync/tests/mod.rs
@@ -1,5 +1,6 @@
cfg_not_loom! {
mod atomic_waker;
mod notify;
mod semaphore_batch;
}

Expand Down
44 changes: 44 additions & 0 deletions tokio/src/sync/tests/notify.rs
@@ -0,0 +1,44 @@
use crate::sync::Notify;
use std::future::Future;
use std::mem::ManuallyDrop;
use std::sync::Arc;
use std::task::{Context, RawWaker, RawWakerVTable, Waker};

#[test]
fn notify_clones_waker_before_lock() {
const VTABLE: &RawWakerVTable = &RawWakerVTable::new(clone_w, wake, wake_by_ref, drop_w);

unsafe fn clone_w(data: *const ()) -> RawWaker {
let arc = ManuallyDrop::new(Arc::<Notify>::from_raw(data as *const Notify));
// Or some other arbitrary code that shouldn't be executed while the
// Notify wait list is locked.
arc.notify_one();
let _arc_clone: ManuallyDrop<_> = arc.clone();
RawWaker::new(data, VTABLE)
}

unsafe fn drop_w(data: *const ()) {
let _ = Arc::<Notify>::from_raw(data as *const Notify);
}

unsafe fn wake(_data: *const ()) {
unreachable!()
}

unsafe fn wake_by_ref(_data: *const ()) {
unreachable!()
}

let notify = Arc::new(Notify::new());
let notify2 = notify.clone();

let waker =
unsafe { Waker::from_raw(RawWaker::new(Arc::into_raw(notify2) as *const _, VTABLE)) };
let mut cx = Context::from_waker(&waker);

let future = notify.notified();
pin!(future);

// The result doesn't matter, we're just testing that we don't deadlock.
let _ = future.poll(&mut cx);
}