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

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

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-coercionsArea: implicit and explicit `expr as Type` coercionsA-lifetimesArea: Lifetimes / regionsA-trait-objectsArea: trait objects, vtable layoutC-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.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions