rust-lang / miri

An interpreter for Rust's mid-level intermediate representation
Apache License 2.0
4.13k stars 318 forks source link

Miri does not detect invalid uses of alchemy (std::ptr::from_raw_parts w/ invalid vtable pointer) #3664

Closed LunarLambda closed 2 weeks ago

LunarLambda commented 2 weeks ago

I apologize for the exceedingly silly example code, I wasn't exactly planning on filing this issue.

#![feature(ptr_metadata)]

use std::any::Any;

#[derive(Debug)]
struct Lead;
#[derive(Debug)]
struct Gold;

fn philosophers_stone(lead: &Lead) -> &dyn Any {
    // extract the quintessence (vtable pointer for Any) of Gold.
    let the_essence_of_gold = std::ptr::metadata(&Gold as &dyn Any as *const dyn Any);

    // infuse Lead with the essence of Gold
    // using the philosopher's stone.
    // (this should obviously be UB)
    unsafe { &*std::ptr::from_raw_parts(lead as *const Lead as *const (), the_essence_of_gold) }
}

fn main() {
    let lead = &Lead;

    let magic = philosophers_stone(lead);

    println!("{:?}", magic.downcast_ref::<Gold>());
}

Running this in the playground (Miri version 0.1.0 (2024-06-08 f21554f)) prints Some(Gold) and reports no UB.

Assuming the ptr_metadata feature will eventually become stable and people will use ptr::from_raw_parts for various horrible unsafe shenanigans, Miri should try to detect constructing trait objects with the wrong vtable, similarly to how it catches e.g. ptr::slice_from_raw_parts with an invalid length.

RalfJung commented 2 weeks ago

Miri detects when the vtable does not match the trait given in the type. There's not a lot more that can be detected - Rust does not have typed memory so there is no sense in which "an object of type Gold" exists or "an object of type Lead" does not exist.

Your code is no worse than code that does something like "&(&Gold as const Gold as *const Lead)". That kind of code is pretty common and should definitely be allowed (if the two types have sufficiently compatible layout). The same applies to your code.

LunarLambda commented 2 weeks ago

I thought Miri already tracks the "true type" of each pointed-to object/"alloc" for other purposes?

Also, I know ZST-transmutes are fine, but I don't see how creating a mismatched trait object is equivalent?

Unless you're suggesting that dispatching via the wrong vtable for the erased type is okay as long as an equivalent transmute between the two recipient types would be valid?

Or put another way, my code is the same as casting &Leas to &Gold and then that to &dyn Any? That seems like a pretty strong thing to allow, but I guess I don't have a counterargument, since the size and alignment fields in the vtable have to match, but that's also true for transmuting &T to &U in general.

RalfJung commented 2 weeks ago

I thought Miri already tracks the "true type" of each pointed-to object/"alloc" for other purposes?

There is no such thing as the "true type" of an object. In memory, there are just bytes. The bytes are a bit more fancy than you may be used to, but they are still fundamentally untyped.

RalfJung commented 2 weeks ago

Unless you're suggesting that dispatching via the wrong vtable for the erased type is okay as long as an equivalent transmute between the two recipient types would be valid?

Yes, that is a good way to put it -- that's what Miri currently implements. (Note that the wrong vtable still has to be for the same trait as the original vtable, otherwise Miri will immediately complain.) I don't think @rust-lang/opsem has made an official decision on this, but this seems most consistent with our general stance on memory being untyped.

Closing as not-a-bug.

LunarLambda commented 2 weeks ago

got it, thank you! very interesting.