Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pointer casts allow switching trait parameters for trait objects, which can be unsound with raw pointers as receiver types under feature(arbitrary_self_types) #120217

Closed
steffahn opened this issue Jan 22, 2024 · 12 comments
Labels
A-coercions Area: implicit and explicit `expr as Type` coercions A-lifetimes Area: Lifetimes / regions A-trait-objects Area: trait objects, vtable layout C-bug Category: This is a bug. F-arbitrary_self_types `#![feature(arbitrary_self_types)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-medium Medium priority requires-nightly This issue requires a nightly compiler in some way. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@steffahn
Copy link
Member

steffahn commented Jan 22, 2024

This particular exploitation is possible since #113262. I’m not certain if there wasn’t any way to convince the compiler to do such casts before that; if there wasn’t, then that PR was definitely a lot more than “a bugfix”.

Edit: Turns out, there is a way, see my first answer below. (Does this mean the regression label should be removed, since the regression only extended the scope of the issue a bit? So this becomes an issue for F-arbitrary_self_types in general then? Should we also add requires-nightly label?)


So apparently, the compiler doesn’t care about lifetimes for trait object metadata when checking casts between pointers. This means I can coerce *const dyn Foo<'a> into *const dyn Foo<'b> without restrictions. Since vtables logically contain function pointers, like for example a vtable for trait Foo<'a> { fn foo(&self) -> &'a str } logically contains something like unsafe fn(*const ()) -> &'a str this results in casting the types of these function pointers.

Now one could argue “it’s fine, they are raw pointers, you can’t do anything with *const dyn NotQuiteTheRightTrait”, but as far as I’m aware, the story on that is that you can, vtables must be valid (at least as a soundness invariant, not necessarily promising instant UB), and we should not break arbitrary_self_types’s soundness this way, for now.

And with arbitrary_self_types, unsoundness there is!

#![feature(arbitrary_self_types)]

trait Static<'a> {
    fn proof(self: *const Self, s: &'a str) -> &'static str;
}

fn bad_cast<'a>(x: *const dyn Static<'static>) -> *const dyn Static<'a> {
    x as _
}

impl Static<'static> for () {
    fn proof(self: *const Self, s: &'static str) -> &'static str {
        s
    }
}

fn extend_lifetime(s: &str) -> &'static str {
    bad_cast(&()).proof(s)
}

fn main() {
    let s = String::from("Hello World");
    let slice = extend_lifetime(&s);
    println!("Now it exists: {slice}");
    drop(s);
    println!("Now it’s gone: {slice}");
}
Now it exists: Hello World
Now it’s gone: �@7

(playground)

@rustbot label +I-unsound +F-arbitrary_self_types +A-lifetimes +A-coercions +A-trait-objects +requires-nightly

@steffahn steffahn added C-bug Category: This is a bug. regression-untriaged Untriaged performance or correctness regression. labels Jan 22, 2024
@rustbot rustbot added I-prioritize Issue: Indicates that prioritization has been requested for this issue. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. A-coercions Area: implicit and explicit `expr as Type` coercions A-lifetimes Area: Lifetimes / regions A-trait-objects Area: trait objects, vtable layout F-arbitrary_self_types `#![feature(arbitrary_self_types)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness regression-from-stable-to-stable Performance or correctness regression from one stable version to another. and removed regression-untriaged Untriaged performance or correctness regression. labels Jan 22, 2024
@compiler-errors compiler-errors added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Jan 22, 2024
@steffahn
Copy link
Member Author

steffahn commented Jan 22, 2024

Uhm… perhaps nevermind on that recent regression point, I guess? Apparently we can also do the following just fine™ since around Rust 1.2

// edition 2015

#![allow(bare_trait_objects)]

trait Trait<T> {}

fn foo(x: *const Trait<u8>) -> *const Trait<u16> {
    x as *const Trait<u16>
}

(Let me work on a better code example for unsoundness from this.)


Edit: For example:

#![feature(arbitrary_self_types)]

trait Trait<S, T> {
    fn convert(self: *const Self, x: S) -> T;
}

impl<T> Trait<T, T> for [T; 0] {
    fn convert(self: *const Self, x: T) -> T {
        x
    }
}

fn transmute<S, T>(x: S) -> T {
    (&[] as *const dyn Trait<S, S> as *const dyn Trait<S, T>).convert(x)
}

fn main() {
    let n = 1;
    println!(
        "much data, such joy: {:?}",
        transmute::<&u8, &[u8; u32::MAX as _]>(&n)
    );
}

(playground)

@steffahn steffahn changed the title Pointer casts allow switching lifetimes of trait objects, which can be unsound Pointer casts allow switching trait parameters for trait objects, which can be unsound Jan 22, 2024
@rustbot rustbot added requires-nightly This issue requires a nightly compiler in some way. and removed regression-from-stable-to-stable Performance or correctness regression from one stable version to another. labels Jan 22, 2024
@steffahn steffahn changed the title Pointer casts allow switching trait parameters for trait objects, which can be unsound Pointer casts allow switching trait parameters for trait objects, which can be unsound with raw pointers as receiver types under feature(arbitrary_self_types) Jan 22, 2024
@saethlin saethlin removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jan 22, 2024
@Noratrieb
Copy link
Member

Requiring pointers to have valid vtables while allowing these casts seems pretty conflicting and impossible. cc @rust-lang/opsem @rust-lang/lang who have previously been discussing whether raw pointers should have valid vtables.

@RalfJung
Copy link
Member

This wasn't just discussed, it was decided that raw ptrs have valid vtables as a safety invariant as part of dyn trait upcasting.

However, this being about safety invariants, it's more a @rust-lang/types question than an opsem one.

@Noratrieb
Copy link
Member

If a safety invariant can be broken by safe code that's uh, not good.

@adetaylor
Copy link
Contributor

Agreed - IMO if arbitrary self types supports calls into *[const|mut] dyn Trait then these should need to be unsafe since it involves dereferencing some data that can be arbitrarily manipulated.

@RalfJung
Copy link
Member

Ideally we can fix things so that safe code can't arbitrarily manipulate this.

#120222 is another issue caused by vtable manipulation. Let's see how that one gets resolved (it's a lot more critical since the feature it affects almost got stabilized in 2 weeks), then we can use that to decide what to do about arbitrary-self.

bors added a commit to rust-lang-ci/rust that referenced this issue Jan 22, 2024
…r=<try>

Make casts of pointers to trait objects stricter

This is an attempt to `fix` rust-lang#120222 and rust-lang#120217.

cc `@oli-obk` `@compiler-errors` `@lcnr`
@QuineDot
Copy link

This wasn't just discussed, it was decided that raw ptrs have valid vtables as a safety invariant as part of dyn trait upcasting.

Issue #101336 for anyone looking.

@apiraino
Copy link
Contributor

apiraino commented Feb 5, 2024

@steffahn can you help me getting a bit more context (maybe I missed some comments and don't have the global picture): how does (if at all) the revert of #118133 (in PR #120233) affect this? Does that "fix" it, at least partially? Thanks :)

@lukas-code
Copy link
Member

@apiraino Both feature(arbitrary_self_types) and feature(trait_upcasting) are individually unsound, because we allow casts like *const dyn Trait<u8> -> *const dyn Trait<u16>. But the features are otherwise completely unrelated, so the revert of trait_upcasting does not affect the soundness of arbitrary_self_types at all.

The unsoundness of arbitrary_self_types is tracked in this issue and the unsoundness of trait_upcasting is tracked in #120222.

Both issues could be fixed by disallowing casts like *const dyn Trait<u8> -> *const dyn Trait<u16> (#120222 (comment) / #120248), but that would regress stable code.

An alternative set of fixes is to change the vtable layout to fix trait_upcasting (#120222 (comment)) and to make dispatch on *const dyn Trait unsafe to fix arbitrary_self_types (#120217 (comment)).

@steffahn
Copy link
Member Author

steffahn commented Feb 5, 2024

@apiraino The summary from @lukas-code above is already good. As for what the revert of stabilization of trait_upcasting in its current form achieved: all it did was making #120222 from a regression-from-stable-to-beta issue into a requires-nightly issue (which has already been reflected in the labels).

@apiraino
Copy link
Contributor

WG-prioritization assigning priority (Zulip discussion).

@rustbot label -I-prioritize +P-medium

@rustbot rustbot added P-medium Medium priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Feb 12, 2024
bors added a commit to rust-lang-ci/rust that referenced this issue Feb 13, 2024
…r=<try>

Make casts of pointers to trait objects stricter

This is an attempt to `fix` rust-lang#120222 and rust-lang#120217.

This is done by adding restrictions on casting pointers to trait objects. Currently the restriction is
> When casting `*const X<dyn A>` -> `*const Y<dyn B>`, if `B` has a principal trait, `dyn A + 'erased: Unsize<dyn B + 'erased>` must hold

This ensures that
1. Principal trait's generic arguments match (no `*const dyn Tr<A>` -> `*const dyn Tr<B>` casts, which are a problem for [rust-lang#120222](rust-lang#120222))
2. Principal trait's lifetime arguments match (no `*const dyn Tr<'a>` -> `*const dyn Tr<'b>` casts, which are a problem for [rust-lang#120217](rust-lang#120217))
3. No auto traits can be _added_ (this is a problem for arbitrary self types, see [this comment](rust-lang#120248 (comment)))

Some notes:
 - We only care about the metadata/last field, so you can still cast `*const dyn T` to `*const WithHeader<dyn T>`, etc
- The lifetime of the trait object itself (`dyn A + 'lt`) is not checked, so you can still cast `*mut FnOnce() + '_` to `*mut FnOnce() + 'static`, etc
  - This feels fishy, but I couldn't come up with a reason it must be checked
- The new checks are only done if `B` has a principal, so you can still do any kinds of cast, if the target only has auto traits
  - This is because auto traits are not enough to get unsoundness issues that this PR fixes
  - ...and so it makes sense to minimize breakage

The plan is to, ~~once the checks are properly implemented~~, run crate to determine how much damage does this do.

The diagnostics are currently not great, to say the least, but as far as I can tell this correctly fixes the issues.

cc `@oli-obk` `@compiler-errors` `@lcnr`
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jul 8, 2024
…, r=compiler-errors,oli-obk,lnicola

Make casts of pointers to trait objects stricter

This is an attempt to `fix` rust-lang#120222 and rust-lang#120217.

This is done by adding restrictions on casting pointers to trait objects.

Before this PR the rules were as follows:

> When casting `*const X<dyn A>` -> `*const Y<dyn B>`, principal traits in `A` and `B` must refer to the same trait definition (or no trait).

With this PR the rules are changed to

> When casting `*const X<dyn Src>` -> `*const Y<dyn Dst>`
> - if `Dst` has a principal trait `DstP`,
>   - `Src` must have a principal trait `SrcP`
>   - `dyn SrcP` and `dyn DstP` must be the same type (modulo the trait object lifetime, `dyn T+'a` -> `dyn T+'b` is allowed)
>   - Auto traits in `Dst` must be a subset of auto traits in `Src`
>     - Not adhering to this is currently a FCW (warn-by-default + `FutureReleaseErrorReportInDeps`), instead of an error
> - if `Src` has a principal trait `Dst` must as well
>   - this restriction will be removed in a follow up PR

This ensures that
1. Principal trait's generic arguments match (no `*const dyn Tr<A>` -> `*const dyn Tr<B>` casts, which are a problem for [rust-lang#120222](rust-lang#120222))
2. Principal trait's lifetime arguments match (no `*const dyn Tr<'a>` -> `*const dyn Tr<'b>` casts, which are a problem for [rust-lang#120217](rust-lang#120217))
3. No auto traits can be _added_ (this is a problem for arbitrary self types, see [this comment](rust-lang#120248 (comment)))

Some notes:
 - We only care about the metadata/last field, so you can still cast `*const dyn T` to `*const WithHeader<dyn T>`, etc
- The lifetime of the trait object itself (`dyn A + 'lt`) is not checked, so you can still cast `*mut FnOnce() + '_` to `*mut FnOnce() + 'static`, etc
  - This feels fishy, but I couldn't come up with a reason it must be checked

The diagnostics are currently not great, to say the least, but as far as I can tell this correctly fixes the issues.

cc `@oli-obk` `@compiler-errors` `@lcnr`
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Jul 8, 2024
Rollup merge of rust-lang#120248 - WaffleLapkin:bonk-ptr-object-casts, r=compiler-errors,oli-obk,lnicola

Make casts of pointers to trait objects stricter

This is an attempt to `fix` rust-lang#120222 and rust-lang#120217.

This is done by adding restrictions on casting pointers to trait objects.

Before this PR the rules were as follows:

> When casting `*const X<dyn A>` -> `*const Y<dyn B>`, principal traits in `A` and `B` must refer to the same trait definition (or no trait).

With this PR the rules are changed to

> When casting `*const X<dyn Src>` -> `*const Y<dyn Dst>`
> - if `Dst` has a principal trait `DstP`,
>   - `Src` must have a principal trait `SrcP`
>   - `dyn SrcP` and `dyn DstP` must be the same type (modulo the trait object lifetime, `dyn T+'a` -> `dyn T+'b` is allowed)
>   - Auto traits in `Dst` must be a subset of auto traits in `Src`
>     - Not adhering to this is currently a FCW (warn-by-default + `FutureReleaseErrorReportInDeps`), instead of an error
> - if `Src` has a principal trait `Dst` must as well
>   - this restriction will be removed in a follow up PR

This ensures that
1. Principal trait's generic arguments match (no `*const dyn Tr<A>` -> `*const dyn Tr<B>` casts, which are a problem for [rust-lang#120222](rust-lang#120222))
2. Principal trait's lifetime arguments match (no `*const dyn Tr<'a>` -> `*const dyn Tr<'b>` casts, which are a problem for [rust-lang#120217](rust-lang#120217))
3. No auto traits can be _added_ (this is a problem for arbitrary self types, see [this comment](rust-lang#120248 (comment)))

Some notes:
 - We only care about the metadata/last field, so you can still cast `*const dyn T` to `*const WithHeader<dyn T>`, etc
- The lifetime of the trait object itself (`dyn A + 'lt`) is not checked, so you can still cast `*mut FnOnce() + '_` to `*mut FnOnce() + 'static`, etc
  - This feels fishy, but I couldn't come up with a reason it must be checked

The diagnostics are currently not great, to say the least, but as far as I can tell this correctly fixes the issues.

cc `@oli-obk` `@compiler-errors` `@lcnr`
@traviscross
Copy link
Contributor

This is believed to be fixed by:

github-actions bot pushed a commit to rust-lang/miri that referenced this issue Jul 9, 2024
…ler-errors,oli-obk,lnicola

Make casts of pointers to trait objects stricter

This is an attempt to `fix` rust-lang/rust#120222 and rust-lang/rust#120217.

This is done by adding restrictions on casting pointers to trait objects.

Before this PR the rules were as follows:

> When casting `*const X<dyn A>` -> `*const Y<dyn B>`, principal traits in `A` and `B` must refer to the same trait definition (or no trait).

With this PR the rules are changed to

> When casting `*const X<dyn Src>` -> `*const Y<dyn Dst>`
> - if `Dst` has a principal trait `DstP`,
>   - `Src` must have a principal trait `SrcP`
>   - `dyn SrcP` and `dyn DstP` must be the same type (modulo the trait object lifetime, `dyn T+'a` -> `dyn T+'b` is allowed)
>   - Auto traits in `Dst` must be a subset of auto traits in `Src`
>     - Not adhering to this is currently a FCW (warn-by-default + `FutureReleaseErrorReportInDeps`), instead of an error
> - if `Src` has a principal trait `Dst` must as well
>   - this restriction will be removed in a follow up PR

This ensures that
1. Principal trait's generic arguments match (no `*const dyn Tr<A>` -> `*const dyn Tr<B>` casts, which are a problem for [#120222](rust-lang/rust#120222))
2. Principal trait's lifetime arguments match (no `*const dyn Tr<'a>` -> `*const dyn Tr<'b>` casts, which are a problem for [#120217](rust-lang/rust#120217))
3. No auto traits can be _added_ (this is a problem for arbitrary self types, see [this comment](rust-lang/rust#120248 (comment)))

Some notes:
 - We only care about the metadata/last field, so you can still cast `*const dyn T` to `*const WithHeader<dyn T>`, etc
- The lifetime of the trait object itself (`dyn A + 'lt`) is not checked, so you can still cast `*mut FnOnce() + '_` to `*mut FnOnce() + 'static`, etc
  - This feels fishy, but I couldn't come up with a reason it must be checked

The diagnostics are currently not great, to say the least, but as far as I can tell this correctly fixes the issues.

cc `@oli-obk` `@compiler-errors` `@lcnr`
lnicola pushed a commit to lnicola/rust-analyzer that referenced this issue Jul 11, 2024
…ler-errors,oli-obk,lnicola

Make casts of pointers to trait objects stricter

This is an attempt to `fix` rust-lang/rust#120222 and rust-lang/rust#120217.

This is done by adding restrictions on casting pointers to trait objects.

Before this PR the rules were as follows:

> When casting `*const X<dyn A>` -> `*const Y<dyn B>`, principal traits in `A` and `B` must refer to the same trait definition (or no trait).

With this PR the rules are changed to

> When casting `*const X<dyn Src>` -> `*const Y<dyn Dst>`
> - if `Dst` has a principal trait `DstP`,
>   - `Src` must have a principal trait `SrcP`
>   - `dyn SrcP` and `dyn DstP` must be the same type (modulo the trait object lifetime, `dyn T+'a` -> `dyn T+'b` is allowed)
>   - Auto traits in `Dst` must be a subset of auto traits in `Src`
>     - Not adhering to this is currently a FCW (warn-by-default + `FutureReleaseErrorReportInDeps`), instead of an error
> - if `Src` has a principal trait `Dst` must as well
>   - this restriction will be removed in a follow up PR

This ensures that
1. Principal trait's generic arguments match (no `*const dyn Tr<A>` -> `*const dyn Tr<B>` casts, which are a problem for [#120222](rust-lang/rust#120222))
2. Principal trait's lifetime arguments match (no `*const dyn Tr<'a>` -> `*const dyn Tr<'b>` casts, which are a problem for [#120217](rust-lang/rust#120217))
3. No auto traits can be _added_ (this is a problem for arbitrary self types, see [this comment](rust-lang/rust#120248 (comment)))

Some notes:
 - We only care about the metadata/last field, so you can still cast `*const dyn T` to `*const WithHeader<dyn T>`, etc
- The lifetime of the trait object itself (`dyn A + 'lt`) is not checked, so you can still cast `*mut FnOnce() + '_` to `*mut FnOnce() + 'static`, etc
  - This feels fishy, but I couldn't come up with a reason it must be checked

The diagnostics are currently not great, to say the least, but as far as I can tell this correctly fixes the issues.

cc `@oli-obk` `@compiler-errors` `@lcnr`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-coercions Area: implicit and explicit `expr as Type` coercions A-lifetimes Area: Lifetimes / regions A-trait-objects Area: trait objects, vtable layout C-bug Category: This is a bug. F-arbitrary_self_types `#![feature(arbitrary_self_types)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-medium Medium priority requires-nightly This issue requires a nightly compiler in some way. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests