Skip to content

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

Description

@steffahn

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

Activity

  1. added
    C-bugCategory: This is a bug.
    regression-untriagedUntriaged performance or correctness regression.
    on Jan 22, 2024
  2. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    A-coercionsArea: implicit and explicit `expr as Type` coercions
    A-lifetimesArea: Lifetimes / regions
    A-dyn-traitArea: trait objects, vtable layout
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.
    and removed
    regression-untriagedUntriaged performance or correctness regression.
    on Jan 22, 2024
  3. added
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    on Jan 22, 2024
  4. steffahn commented on Jan 22, 2024

    @steffahn
    MemberAuthor

    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)

  5. 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[/+] on Jan 22, 2024
  6. added
    requires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.
    on Jan 22, 2024
  7. 12 remaining items

  8. steffahn commented on Feb 5, 2024

    @steffahn
    MemberAuthor

    @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).

  9. apiraino commented on Feb 12, 2024

    @apiraino
    Contributor

    WG-prioritization assigning priority (Zulip discussion).

    @rustbot label -I-prioritize +P-medium

  10. added and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Feb 12, 2024
  11. added a commit that references this issue on Feb 13, 2024
  12. added a commit that references this issue on Jul 8, 2024
  13. added a commit that references this issue on Jul 8, 2024
  14. traviscross commented on Jul 8, 2024

    @traviscross
    Contributor
  15. added a commit that references this issue on Feb 19, 2025
  16. added a commit that references this issue on Feb 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-coercionsArea: implicit and explicit `expr as Type` coercionsA-dyn-traitArea: trait objects, vtable layoutA-lifetimesArea: Lifetimes / regionsC-bugCategory: This is a bug.F-arbitrary_self_types`#![feature(arbitrary_self_types)]`I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-mediumMedium priorityT-typesRelevant to the types team, which will review and decide on the PR/issue.requires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions