Lifetime HRTB on associated type results in unsoundness
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
This code results in undefined behavior in safe code.
use std::any::Any;
// `for<'a> Producer<&'a T>` is equivalent to the (non-existent) `for<'a> FnOnce() -> &'a T`
pub trait Producer<T>: FnOnce() -> T {}
impl<T, F: FnOnce() -> T> Producer<T> for F {}
// What does `P::Output` even mean in this context? If this was `FnOnce(&()) -> &T`, using `Output`
// would result in "Cannot use the assiciated type of a trait with uninferred generic parameters",
// even when hiding behind an extra trait
fn write_incoherent_p2<T, P: for<'a> Producer<&'a T>>(
weird: P::Output,
out: &mut &'static dyn Any,
) {
// `T` is not even `'static`, but `P::Output` seems to kind of
// resemble `for<'a> &'a T` (if that even means anything)
*out = weird;
}
fn write_incoherent_p1<T, P: for<'a> Producer<&'a T>>(p: P, out: &mut &'static dyn Any) {
// Producing and writing p() in one function doesn't work. Doing so requires T: 'static.
// Adding T: 'static to all functions also makes this not work.
// This fragility is why every line is its own function.
write_incoherent_p2::<T, P>(p(), out)
}
// Now we can trigger unsoundness by finding something that is `FnOnce() -> &'a T` for any `'a`
// `for<'a> FnOnce() -> &'a T` is basically just the signature of Box::leak
fn construct_implementor<T>(not_static: T, out: &mut &'static dyn Any) {
write_incoherent_p1::<T, _>(|| Box::leak(Box::new(not_static)), out);
}
fn make_static_and_drop<T: 'static>(t: T) -> &'static T {
let mut out: &'static dyn Any = &();
construct_implementor::<&T>(&t, &mut out);
*out.downcast_ref::<&T>().unwrap()
}
fn main() {
println!("{:?}", make_static_and_drop(vec![vec![1]])); // use after free
}
The earliest affected version is 1.72 1.67.0. Many All earlier versions reject construct_implementor, but most just 1.70.0-1.72.0 ICE. Current nightly is also affected.
I am not entirely sure what is going on in this code; I was trying to create a function type that returns a reference of any requested lifetime (don't ask why), i.e. for<'a> Fn() -> &'a T. For some reason it works when hiding behind a blanket implementation, though it is very fragile. Taking Fn::Output of such a function type yields a type that seems to kind of resemble "for<'a> &'a T" and allows assignment to &'static dyn Any, even though T is not 'static.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by compiling the standalone reproducer with Rust 1.67.0 through 1.72.0 and current nightly, comparing acceptance, ICEs, and runtime behavior. Trace the associated-type and higher-ranked trait bound handling implicated by the example; the issue is done when the safe-code path no longer permits undefined behavior and regression coverage protects the affected cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100