Pin soundness design meeting 2020.01.06

Links


Background and summary

  • Core Pin invariant:
  • If you have a Pin<P> for some pointer type P, then P::Target must never move
  • Pin offers an API, as_mut:
  • Pin<P>Pin<&mut P::Target>
  • if P: DerefMut
  • This invokes DerefMut::deref_mut, which we expect to be “pure”
  • Correctness of this API (currently) relies on some conditions being enforced when a Pin is constructed
  • Pin<P> can be safely constructed via Pin::new, but only if P::Target is Unpin (let’s put a pin in this for the moment)
  • Pin<P> can also be unsafely constructed via Pin::new_unchecked, which requires proving various rather ill-defined conditions
  • But we have some Into wrappers for Box<T>, so given a Box<T> you can create a Pin<Box<T>>
  • You can create a Pin<&P::Target> and Pin<&mut P::Target> via Pin::as_ref and Pin::as_mut
  • Assumption: constructing the Pin<P> ensured that P::Target would not move, so this is reasonable
  • Further assumption (false):
  • as_ref creates a Pin<&T>, so it must 
  • Box<T> => Pin<Box<T>>
  • There were actually a few unsoundnesses at play
  • One, for PartialEq, which we resolved in PR #67039
  • Another set of attacks that are more pernicious:
  • via DerefMut (stable)
  • via Clone (stable)
  • via CoerceUnsized (unstable)
  • Ultimate goal of these attacks:
  • to permit construction of a Pin<&mut P::Target 

// A !Unpin type to reference
struct MyType<'a>(Cell<Option<&'a mut MyType<'a>>>, PhantomPinned);

impl<'a> DerefMut for &'a MyType<'a> {
    fn deref_mut<'this>(&'this mut self) -> &'this mut MyType<'a> {
        self.0.replace(None).unwrap()
    }
}

fn main() {
    let mut unpinned = Box::new(MyType(Cell::new(None), PhantomPinned));
    let pinned = Box::pin(MyType(Cell::new(Some(&mut unpinned)),
                          PhantomPinned);
    let mut pinned_ref: Pin<&MyType<'_>> = pinned.as_ref();

    // call the unsound DerefMut impl
    let pinned_mut: Pin<&mut MyType<'_>> = pinned_ref.as_mut();
    
    // pinned_mut points to the address at `unpinned`, which
    // can be moved in the future. UNSOUND!