kyren / gc-arena

Incremental garbage collection from safe Rust
Creative Commons Zero v1.0 Universal
436 stars 36 forks source link

Workaround for a rustc bug that can currently lead to UB #63

Closed kyren closed 1 year ago

kyren commented 1 year ago

Credit goes to me for stumbling across it and 100% to @moulins for actually finding the fix.

I do not have a link to an open rust issue for the specific bug, I can try and find a link later if @moulins doesn't already know which one it is.

moulins commented 1 year ago

As extra evidence that this is an actual rustc bug and not an issue with gc-arena's abstractions, here's a piece of code using a similar trait setup and Any to demonstrate UB with no unsafe code (playground link):

use std::any::Any;
use std::sync::OnceLock;

trait Visit {
    fn visit(&self);
}

trait Visitable<'a> {
    type Visit: Visit + 'a;
}

static HOLDER: OnceLock<&'static String> = OnceLock::new();

impl<T> Visit for &'static T {
    fn visit(&self) {
        let any = (*self) as &dyn Any;
        if let Some(i) = any.downcast_ref::<&'static String>() {
            HOLDER.get_or_init(|| i);
        }
    }
}

fn call_and_visit<V, F>(value: String, f: F)
where
    V: for<'a> Visitable<'a> + ?Sized,
    F: for<'a> Fn(&'a String) -> <V as Visitable<'a>>::Visit,
{
    f(&value).visit();
}

type MyVisitable = dyn for<'a> Visitable<'a, Visit = &'static &'a String>;

fn main() {
    call_and_visit::<MyVisitable, _>("hello".into(), |i| {
        Box::leak(Box::new(i))
    });

    // oops, use-after-free
    dbg!(HOLDER.get());
}

I believe this is an instance of https://github.com/rust-lang/rust/issues/84533, as it also involves "broken" dyn Trait types allowing the bypass of well-formed checks.