Open
Description
Haven’t got the time to do any more explaining right now, but here’s the code (Edit: I added a few inline comments anyways):
use std::{
cell::{RefCell, RefMut},
future::Future,
mem,
ops::DerefMut,
pin::Pin,
sync::Arc,
task::{Context, Poll, Wake},
};
struct SomeLocalStruct<'a, Fut>(&'a RefCell<Fut>);
trait SomeTrait<'a, Fut> {
#[allow(clippy::mut_from_ref)]
fn deref_helper(&self) -> &mut (dyn SomeTrait<'a, Fut> + 'a) {
unimplemented!()
}
fn downcast(self: Pin<&mut Self>) -> Pin<&mut Fut> {
unimplemented!()
}
}
impl<'a, Fut: Future<Output = ()>> SomeTrait<'a, Fut> for SomeLocalStruct<'a, Fut> {
fn deref_helper(&self) -> &mut (dyn SomeTrait<'a, Fut> + 'a) {
let x = Box::new(self.0.borrow_mut());
let x: &'a mut RefMut<'a, Fut> = Box::leak(x);
&mut **x
}
}
impl<'a, Fut: Future<Output = ()>> SomeTrait<'a, Fut> for Fut {
fn downcast(self: Pin<&mut Self>) -> Pin<&mut Fut> {
self
}
}
impl<'b, 'a, Fut> DerefMut for Pin<&'b dyn SomeTrait<'a, Fut>> {
fn deref_mut<'c>(
self: &'c mut Pin<&'b dyn SomeTrait<'a, Fut>>,
) -> &'c mut (dyn SomeTrait<'a, Fut> + 'b) {
self.deref_helper()
}
}
// obviously a “working” function with this signature is problematic
pub fn unsound_pin<Fut: Future<Output = ()>>(
fut: Fut,
callback: impl FnOnce(Pin<&mut Fut>),
) -> Fut {
let cell = RefCell::new(fut);
let s: &SomeLocalStruct<'_, Fut> = &SomeLocalStruct(&cell);
let p: Pin<Pin<&SomeLocalStruct<'_, Fut>>> = Pin::new(Pin::new(s));
let mut p: Pin<Pin<&dyn SomeTrait<'_, Fut>>> = p;
let r: Pin<&mut dyn SomeTrait<'_, Fut>> = p.as_mut();
let f: Pin<&mut Fut> = r.downcast();
callback(f);
cell.into_inner()
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// everything below here just exploitation of `unsound_pin` to get a segfault
fn yield_now() -> impl Future<Output = ()> {
struct Yield(bool);
impl Future for Yield {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
if matches!(mem::replace(&mut *self, Yield(true)), Yield(true)) {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
Yield(false)
}
fn main() {
let fut = async {
let x = &&0;
let reference = &x;
yield_now().await; // future will be moved here
dbg!(reference);
};
struct Waker;
impl Wake for Waker {
fn wake(self: std::sync::Arc<Self>) {}
}
let waker = Arc::new(Waker).into();
let mut cx = Context::from_waker(&waker);
let fut = unsound_pin(fut, |fut| {
let _ = fut.poll(&mut cx);
});
// moving `fut` vvv after the first poll above, then polling again
let _ = Box::pin(fut).as_mut().poll(&mut cx);
}
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 1.10s
Running `target/debug/playground`
[src/main.rs:84] reference = timeout: the monitored command dumped core
/playground/tools/entrypoint.sh: line 11: 8 Segmentation fault timeout --signal=KILL ${timeout} "$@"
segfaults in debug build due to an async
-block future being moved after the first .poll()
call
@rustbot label C-bug, A-pin, T-compiler, T-libs, T-lang
and someone please add I-unsound 💥
Metadata
Metadata
Assignees
Labels
Area: PinCategory: This is a bug.Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessHigh priorityStatus: This bug is tracked inside the repo by a `known-bug` test.Relevant to the compiler team, which will review and decide on the PR/issue.Relevant to the language team, which will review and decide on the PR/issue.Relevant to the library API team, which will review and decide on the PR/issue.Relevant to the types team, which will review and decide on the PR/issue.
Type
Projects
Status
unknown