Skip to content

Commit 17b4d18

Browse files
committed
Propose specific solution for Pin unsoundness issue
1 parent 1eec5b3 commit 17b4d18

File tree

1 file changed

+180
-151
lines changed

1 file changed

+180
-151
lines changed

text/3621-derive-smart-pointer.md

Lines changed: 180 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,19 @@ struct MySmartPointer<#[pointee] T: ?Sized, U> {
173173
Specifying `#[pointee]` when the struct has only one type parameter is allowed,
174174
but not required.
175175

176+
## Pinned pointers
177+
178+
The `#[derive(SmartPointer)]` macro is not sufficient to coerce the smart
179+
pointer when it is wrapped in `Pin`. That is, even if `MySmartPointer<MyStruct>`
180+
coerces to `MySmartPointer<dyn MyTrait>`, you will not be able to coerce
181+
`Pin<MySmartPointer<MyStruct>>` to `Pin<MySmartPointer<dyn MyTrait>>`.
182+
Similarly, traits with self types of `Pin<MySmartPointer<Self>>` are not object
183+
safe.
184+
185+
If you implement the unstable unsafe trait called `PinCoerceUnsized` for
186+
`MySmartPointer`, then the smart pointer will gain the ability to be coerced
187+
when wrapped in `Pin`. The trait is not being stabilized by this RFC.
188+
176189
## Example of a custom Rc
177190
[custom-rc]: #example-of-a-custom-rc
178191

@@ -230,7 +243,6 @@ impl<T: ?Sized> Drop for Rc<T> {
230243
In this example, `#[derive(SmartPointer)]` makes it possible to use `Rc<dyn
231244
MyTrait>`.
232245

233-
234246
# Reference-level explanation
235247
[reference-level-explanation]: #reference-level-explanation
236248

@@ -337,6 +349,47 @@ you can use them for dynamic dispatch.
337349
As seen in the `Rc` example, the macro needs to be usable even if the pointer
338350
is `NonNull<RcInner<T>>` (as opposed to `NonNull<T>`).
339351

352+
## `PinCoerceUnsized`
353+
354+
The standard library defines the following unstable trait:
355+
```rust
356+
/// Trait that indicates that this is a pointer or a wrapper for one, where
357+
/// unsizing can be performed on the pointee when it is pinned.
358+
///
359+
/// # Safety
360+
///
361+
/// If this type implements `Deref`, then the concrete type returned by `deref`
362+
/// and `deref_mut` must not change without a modification. The following
363+
/// operations are not considered modifications:
364+
///
365+
/// * Moving the pointer.
366+
/// * Performing unsizing coercions on the pointer.
367+
/// * Performing dynamic dispatch with the pointer.
368+
/// * Calling `deref` or `deref_mut` on the pointer.
369+
///
370+
/// The concrete type of a trait object is the type that the vtable corresponds
371+
/// to. The concrete type of a slice is an array of the same element type and
372+
/// the length specified in the metadata. The concrete type of a sized type
373+
/// is the type itself.
374+
pub unsafe trait PinCoerceUnsized<U>: CoerceUnsized<U> {}
375+
376+
impl<T, U> CoerceUnsized<Pin<U>> for Pin<T>
377+
where
378+
T: PinCoerceUnsized<U>,
379+
{}
380+
381+
impl<T, U> DispatchFromDyn<Pin<U>> for Pin<T>
382+
where
383+
T: PinCoerceUnsized<U> + DispatchFromDyn<U>,
384+
{}
385+
```
386+
The trait is implemented for all standard library types that implement
387+
`CoerceUnsized`.
388+
389+
Although this RFC proposes to add the `PinCoerceUnsized` trait to ensure that
390+
unsizing coercions of pinned pointers cannot be used to cause unsoundness, the
391+
RFC does not propose to stabilize the trait.
392+
340393
# Drawbacks
341394
[drawbacks]: #drawbacks
342395

@@ -537,6 +590,131 @@ implementations of these traits for `Pin` with stricter trait bounds than what
537590
is specified on the struct. That will get much more complicated if we use a
538591
mechanism other than traits to specify this logic.
539592

593+
## `PinCoerceUnsized`
594+
595+
Beyond the addition of the `#[derive(SmartPointer)]` macro, this RFC also
596+
proposes to add a new unstable trait called `PinCoerceUnsized`. This trait is
597+
necessary because the API proposed by this RFC would otherwise by unsound:
598+
599+
> You could use `Pin::new` to create a `Pin<SmartPtr<MyUnpinFuture>>` and coerce
600+
> that to `Pin<SmartPtr<dyn Future>>`. Then, if `SmartPtr` has a malicious
601+
> implementation of the `Deref` trait, then `deref` could return a `&mut dyn
602+
> Future` whose concrete type is not `MyUnpinFuture`, but instead some other
603+
> future type that *does* need to be pinned. Since no unsafe code is involved in
604+
> any of these steps, this means that we are able to safely create a pinned
605+
> pointer to a value that has not been pinned.
606+
607+
Adding the unsafe `PinCoerceUnsized` trait ensures that the user cannot coerce
608+
`Pin<SmartPtr<MyUnpinFuture>>` to `Pin<SmartPtr<dyn Future>>` without using
609+
unsafe to promise that the concrete type returned when calling `deref` on the
610+
resulting `Pin<SmartPtr<dyn Future>>` is `MyUnpinFuture`.
611+
612+
This RFC does not propose to stabilize `PinCoerceUnsized` because of naming
613+
issues. If we do not know whether `CoerceUnsized` will still use that name when
614+
we stabilize it, then we can't stabilize a trait called `PinCoerceUnsized`.
615+
Furthermore, the Linux kernel (which forms the motivation for this RFC) does not
616+
currently need it to be stabilized.
617+
618+
There are some alternatives to `PinCoerceUnsized`. The primary contender for an
619+
alternative solution is `DerefPure`. However, that solution involves a minor
620+
breaking change, and we can always decide to switch to `DerefPure` later even if
621+
we adopt `PinCoerceUnsized` now.
622+
623+
### `StableDeref`
624+
625+
A previous version of this RFC proposed to instead add a trait called
626+
`StableDeref` that pretty much had the same requirements as `PinCoerceUnsized`,
627+
except that it also required the address returned by `deref` to be stable.
628+
629+
The motivation behind adding a `StableDeref` trait instead of `PinCoerceUnsized`
630+
is that `StableDeref` would also be useful for other things, and that both
631+
traits essentially just say that the `Deref` implementation doesn't do anything
632+
unreasonable. The requirement that the address is stable is not strictly
633+
required to keep the API sound, but semantically it is incoherent to have a
634+
pinned pointer whose address can change, so it is not overly burdensome to
635+
require it.
636+
637+
However, this suggestion was abandoned due to an inconsistency with the
638+
`StableDeref` trait defined by the ecosystem. That trait requires that raw
639+
pointers to the contents of the pointer stay valid even if the smart pointer is
640+
moved, but this is not satisfied by `Box` or `&mut T` because moving these
641+
pointers asserts that they are unique. This is a problem because whichever trait
642+
we use for pinned unsizing coercions, it *must* be implemented by `Box` and
643+
`&mut T`.
644+
645+
### `DerefPure`
646+
647+
In a similar manner to the `StableDeref` option, we can use the existing
648+
`DerefPure` trait. This option is a reasonable way forward, but this RFC does
649+
not propose it because it would be a breaking change. (Note that `StableDeref`
650+
is also a breaking change for the same reason.)
651+
652+
Basically, the problem is that `Deref` is a supertrait of `DerefPure`, but there
653+
are a few types that can be coerced when pinned that do not implement `Deref`.
654+
For example, this code compiles today:
655+
```rust
656+
trait MyTrait {}
657+
impl MyTrait for String {}
658+
659+
fn pin_cell_map(p: Pin<Cell<Box<String>>>) -> Pin<Cell<Box<dyn MyTrait>>> {
660+
p
661+
}
662+
```
663+
The `Cell` type does not implement `Deref`, but the above code still compiles.
664+
Note that since all methods on `Pin` _do_ require `Deref`, such pinned pointers
665+
are useless and impossible to construct. But it is a breaking change
666+
nonetheless.
667+
668+
If this breakage is considered acceptable, then using `DerefPure` instead of a
669+
new `PinCoerceUnsized` would be a reasonable way forward.
670+
671+
### Make the derive macro unsafe
672+
673+
We could just make the macro unsafe in a similar vein to [the unsafe attributes
674+
RFC][unsafe-attribute].
675+
```rust
676+
// SAFETY: The Deref impl is not malicious.
677+
#[unsafe(derive(SmartPointer))]
678+
pub struct Rc<T: ?Sized> {
679+
inner: NonNull<RcInner<T>>,
680+
}
681+
```
682+
This would solve the unsoundness, but this RFC does not propose it because it
683+
raises forwards compatibility hazards. We might start out with an unsafe derive
684+
macro, and then in the future we might decide to instead use the
685+
`PinCoerceUnsized` solution. Then, `#[unsafe(derive(SmartPointer))]` would have
686+
to generate an implementation of `PinCoerceUnsized` trait too, because otherwise
687+
`#[unsafe(derive(SmartPointer))] Pin<Rc<MyStruct>>` would lose the ability to be
688+
unsize coerced, which would be a breaking change. This means that
689+
`#[unsafe(derive(SmartPointer))]` and `#[derive(SmartPointer)]` could end up
690+
expanding to _different_ things.
691+
692+
### Negative trait bounds
693+
694+
There are also various solutions that involve negative trait bounds. For
695+
example, you might instead modify `CoerceUnsized` like this:
696+
```rust
697+
// Permit going from `Pin<impl Unpin>` to` Pin<impl Unpin>`
698+
impl<P, U> CoerceUnsized<Pin<U>> for Pin<P>
699+
where
700+
P: CoerceUnsized<U>,
701+
P: Deref<Target: Unpin>,
702+
U: Deref<Target: Unpin>,
703+
{ }
704+
705+
// Permit going from `Pin<impl !Unpin>` to `Pin<impl !Unpin>`
706+
impl<P, U> CoerceUnsized<Pin<U>> for Pin<P>
707+
where
708+
P: CoerceUnsized<U>,
709+
P: core::ops::Deref<Target: !Unpin>,
710+
U: core::ops::Deref<Target: !Unpin>,
711+
{ }
712+
```
713+
This RFC does not propose it because it is a breaking change and the
714+
`PinCoerceUnsized` or `DerefPure` solutions are simpler. This solution is
715+
discussed in more details in [the pre-RFC for stabilizing the underlying
716+
traits][pre-rfc].
717+
540718
# Prior art
541719
[prior-art]: #prior-art
542720

@@ -574,156 +752,7 @@ feature, though it does so for a different reason than
574752
# Unresolved questions
575753
[unresolved-questions]: #unresolved-questions
576754

577-
Unfortunately, the API proposed by this RFC is unsound. :(
578-
579-
Basically, the issue is that if `MyStruct` is `Unpin`, then you can create a
580-
`Pin<SmartPointer<MyStruct>>` safely, even though you can coerce that to
581-
`Pin<SmartPointer<dyn MyTrait>>` (and `dyn MyTrait` may be `!Unpin`). If
582-
`SmartPointer` has a malicious implementation of `Deref`, then this can lead to
583-
unsoundness. Since `Deref` is a safe trait, we cannot outlaw malicious
584-
implementations of `Deref`.
585-
586-
Intuitively, the way that `Deref` can be malicious is by not always derefing to
587-
the same value.
588-
589-
Some solution idea is outlined below, but the authors need your input on what
590-
to do about this problem.
591-
592-
## Unsafe macro
593-
594-
The easiest solution is probably to just make the macro unsafe. We could do this
595-
in a similar vein to [the unsafe attributes RFC][unsafe-attribute].
596-
```rust
597-
// SAFETY: The Deref impl always returns the same value.
598-
#[unsafe(derive(SmartPointer))]
599-
pub struct Rc<T: ?Sized> {
600-
inner: NonNull<RcInner<T>>,
601-
}
602-
```
603-
This way, if you coerce an `Pin<Rc<MyStruct>>` to `Pin<Rc<dyn MyTrait>>` and
604-
this is unsound due to a weird `Deref` impl, then it's your fault because you
605-
unsafely asserted that you have a reasonable `Deref` implementation.
606-
607-
That said, there are some possible forwards compatibility hazards with this
608-
solution. We might start out with an unsafe derive macro, and then in the future
609-
we might decide to instead use the `StableDeref` solution mentioned below. Then,
610-
`#[unsafe(derive(SmartPointer))]` would have to generate an implementation of
611-
the `StableDeref` trait too, because otherwise `Pin<Rc<MyStruct>>` would lose
612-
the ability to be unsize coerced, which would be a breaking change. This means
613-
that `#[unsafe(derive(SmartPointer))]` and `#[derive(SmartPointer)]` could end
614-
up expanding to _different_ things.
615-
616-
[unsafe-attribute]: https://github.com/rust-lang/rfcs/pull/3325
617-
618-
## StableDeref
619-
620-
We are quite limited in how we can work around this issue due to backwards
621-
compatibility concerns with `Pin`. We cannot prevent you from using `Pin::new`
622-
with structs that have malicious `Deref` implementations. However, one possible
623-
place we can intervene is the coercion from `Pin<SmartPointer<MyStruct>>` to
624-
`Pin<SmartPointer<dyn MyTrait>>`. If you need to use unsafe before those
625-
coercions are possible, then the problem is solved. For example, we might
626-
introduce a `StableDeref` trait:
627-
```rs
628-
/// # Safety
629-
///
630-
/// Any two calls to `deref` must return the same value at the same address unless
631-
/// `self` has been modified in the meantime. Moves and unsizing coercions of `self`
632-
/// are not considered modifications.
633-
///
634-
/// Here, "same value" means that if `deref` returns a trait object, then the actual
635-
/// type behind that trait object must not change. Additionally, when you unsize
636-
/// coerce from `Self` to `Unsized`, then if you call `deref` on `Unsized` and get a
637-
/// trait object, then the underlying type of that trait object must be `<Self as
638-
/// Deref>::Target`.
639-
///
640-
/// Analogous requirements apply to other unsized types. E.g., if `deref` returns
641-
/// `[T]`, then the length must not change. (The underlying type must not change
642-
/// from `[T; N]` to `[T; M]`.)
643-
///
644-
/// If this type implements `DerefMut`, then the same restrictions apply to calls
645-
/// to `deref_mut`.
646-
unsafe trait StableDeref: Deref { }
647-
```
648-
Then we make it so that you can only coerce pinned pointers when they implement
649-
`StableDeref`. We can do that by modifying its trait implementations to this:
650-
```rs
651-
impl<T, U> CoerceUnsized<Pin<U>> for Pin<T>
652-
where
653-
T: CoerceUnsized<U>,
654-
T: StableDeref,
655-
U: StableDeref,
656-
{}
657-
658-
impl<T, U> DispatchFromDyn<Pin<U>> for Pin<T>
659-
where
660-
T: CoerceUnsized<U>,
661-
T: StableDeref,
662-
U: StableDeref,
663-
{}
664-
```
665-
This way, the user must implement the unsafe trait before they can coerce
666-
pinned versions of the pointer. Since the trait is unsafe, it is not our fault
667-
if that leads to unsoundness.
668-
669-
This should not be a breaking change as long as we implement `StableDeref` for
670-
all standard library types that can be coerced when wrapped in `Pin`.
671-
672-
The proposed trait is called `StableDeref` because the way that `Deref`
673-
implementations can be malicious is essentially by having
674-
`SmartPointer<MyStruct>` and `SmartPointer<dyn MyTrait>` deref to two different
675-
values. There may be some opportunity to reuse the `DerefPure` trait from [the
676-
deref patterns feature][deref-patterns], and there is also some prior art with
677-
the [`stable_deref_trait`](https://docs.rs/stable_deref_trait) crate,
678-
679-
[deref-patterns]: https://github.com/rust-lang/rust/issues/87121/
680-
681-
## Don't allow Pin coercions with custom smart pointers
682-
683-
This solution is essentially the `StableDeref` solution except that we don't
684-
stabilize `StableDeref`. This way, there's no stable way to use
685-
`#[derive(SmartPointer)]` types with `Pin` coercions.
686-
687-
This solutions isn't a problem for the Linux Kernel right now because our custom
688-
`Arc` happens to be implicitly pinned for convenience reasons, but if it wasn't,
689-
then we would need coercions of `Pin`-wrapped values.
690-
691-
## Negative trait bounds?
692-
693-
There are also various solutions that involve negative trait bounds. For
694-
example, you might instead modify `CoerceUnsized` like this:
695-
```rust
696-
// Permit going from `Pin<impl Unpin>` to` Pin<impl Unpin>`
697-
impl<P, U> CoerceUnsized<Pin<U>> for Pin<P>
698-
where
699-
P: CoerceUnsized<U>,
700-
P: Deref<Target: Unpin>,
701-
U: Deref<Target: Unpin>,
702-
{ }
703-
704-
// Permit going from `Pin<impl !Unpin>` to `Pin<impl !Unpin>`
705-
impl<P, U> CoerceUnsized<Pin<U>> for Pin<P>
706-
where
707-
P: CoerceUnsized<U>,
708-
P: core::ops::Deref<Target: !Unpin>,
709-
U: core::ops::Deref<Target: !Unpin>,
710-
{ }
711-
```
712-
This way, you can only coerce pinned pointers when this doesn't change whether
713-
the target type is `Unpin`.
714-
715-
It would solve the unsoundness, but it does have the disadvantage of having no
716-
path to making these pinned coercions possible for smart pointers that don't
717-
have malicious `Deref` implementations. Another downside of this solution is
718-
that it's a breaking change, because it disallows these pinned coercions even
719-
with the standard library pointers, which allows them today.
720-
721-
There are also other variations on the negative trait bounds, which become
722-
different implementations of the "Don't allow Pin coercions with custom smart
723-
pointers" solution.
724-
725-
This solution is discussed in more details in [the pre-RFC for stabilizing the
726-
underlying traits][pre-rfc].
755+
No unresolved questions.
727756

728757
# Future possibilities
729758
[future-possibilities]: #future-possibilities

0 commit comments

Comments
 (0)