Skip to content

Incoherent (?) Lifetime HRTB on associated type results in unsoundness in stable, safe code #141713

Open
@maxdexh

Description

@maxdexh

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.

Activity

added
needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.
on May 29, 2025
theemathas

theemathas commented on May 29, 2025

@theemathas
added
I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
I-prioritizeIssue: Indicates that prioritization has been requested for this issue.
on May 29, 2025
theemathas

theemathas commented on May 29, 2025

@theemathas
theemathas

theemathas commented on May 29, 2025

@theemathas
maxdexh

maxdexh commented on May 29, 2025

@maxdexh
Author
lcnr

lcnr commented on May 29, 2025

@lcnr
Contributor

Fun ✨

trait WriteWhereClause<T>: FnOnce() -> T {}
impl<F: FnOnce() -> T, T> WriteWhereClause<T> for F {}
fn takes_any_requires_static<F>(_: F, any_lt: F::Output) -> &'static str
where
    //     Where-clause rejected by HIR lowering. This must not be soundness-critical,
    //     as elaboration can give us the exact same where-clause regardless. It should
    //     simply not be possible to prove it.
    // F: for<'a> FnOnce() -> &'a String,
    //     Elaborating the below where-clause does work. This is #136547.
    F: for<'a> WriteWhereClause<&'a str>,
{
    any_lt
}

fn dangle() -> &'static str {
    let temp = String::from("temporary");
    takes_any_requires_static(|| "whatever", temp.as_str())
}

fn main() {
    println!("{:?}", dangle());
}

The type of || "whatever" is Closure<for<'b> fn() -> &'b str>. Its builtin impl is not well-formed as the impl signature does not constrain 'a.

impl<'a, T> FnOnce<()> for Closure<for<'b> fn() -> &'b str> {
     type Output = &'a T;
}

added
A-closuresArea: Closures (`|…| { … }`)
A-associated-itemsArea: Associated items (types, constants & functions)
P-highHigh priority
T-typesRelevant to the types team, which will review and decide on the PR/issue.
and removed
I-prioritizeIssue: Indicates that prioritization has been requested for this issue.
on May 29, 2025

14 remaining items

Loading
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-associated-itemsArea: Associated items (types, constants & functions)A-closuresArea: Closures (`|…| { … }`)A-type-systemArea: Type systemC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-typesRelevant to the types team, which will review and decide on the PR/issue.regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.

    Type

    No type

    Projects

    Status

    open/unblocked

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @apiraino@theemathas@lcnr@rustbot@maxdexh

        Issue actions

          Incoherent (?) Lifetime HRTB on associated type results in unsoundness in stable, safe code · Issue #141713 · rust-lang/rust