-
-
Notifications
You must be signed in to change notification settings - Fork 14.6k
Description
Related to #153425
Edit: See #153438 (comment) for an easier exploit
The following code causes UB. In my testing, it prints a random-looking number.
use std::ops::{Deref, DerefMut};
use std::pin::{Pin, pin};
use std::task::{Context, Poll, Waker};
struct Thing<'a, T> {
data: Option<T>,
out: &'a mut Option<T>,
}
impl<T> Deref for Thing<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.data.as_ref().unwrap()
}
}
impl<T> DerefMut for Thing<'_, T> {
fn deref_mut(&mut self) -> &mut T {
self.data.as_mut().unwrap()
}
}
impl<T> Drop for Thing<'_, T> {
fn drop(&mut self) {
*self.out = self.data.take();
}
}
fn wrong_pin<T>(data: T, callback: impl FnOnce(Pin<&mut T>)) -> T {
let mut storage = None::<T>;
{
let thing = Thing {
data: Some(data),
out: &mut storage,
};
let pinned: Pin<&mut T> = pin!(thing);
callback(pinned);
}
storage.unwrap()
}
// The above function violates the Pin guarantee.
// Code below exploits this to actually cause UB.
struct PendingOnce(bool);
impl Future for PendingOnce {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll<()> {
if self.0 {
Poll::Ready(())
} else {
self.0 = true;
Poll::Pending
}
}
}
fn main() {
let mut ctx = Context::from_waker(Waker::noop());
let future = async {
let x = Box::new(1);
let y = &x;
PendingOnce(false).await;
println!("{y}");
};
let future = wrong_pin(future, |pinned_future| {
let _ = pinned_future.poll(&mut ctx);
});
let _ = pin!(future).poll(&mut ctx);
}The issue is that the pin!(thing) macro call moves the entire Thing into an inaccessible variable. Then, instead of pinning Thing, it deref-coerces the &mut Thing<T> into a &mut T and pins that instead. Then, the Drop impl can later access the &mut T, breaking the Pin invariant.
This presumably regressed in #139114, which changed pin!() to use super let. This uses a block expression in the syntax, which then presumably runs into the bug in #23014, causing the strange coercion behavior.
The code compiled since 1.88.0. It didn't compile in 1.87.0.
Meta
Reproducible on the playground with version 1.96.0-nightly (2026-03-04 b90dc1e597db0bbc0cab)