Skip to content

A Pin unsoundness involving an impl DerefMut for Pin<&dyn LocalTrait> #85099

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} "$@"

(playground)

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 💥

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    A-pinArea: PinC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityS-bug-has-testStatus: This bug is tracked inside the repo by a `known-bug` test.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team, which will review and decide on the PR/issue.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions