Repository: rust
Issue: Rust allows `impl Fn(T<'a>) -> T<'b>` to be `: 'static`, which is unsound
Labels: P-high,regression-from-stable-to-stable,I-unsound,A-impl-trait,C-bug,A-variance,S-bug-has-test,T-types

This is similar to https://github.com/rust-lang/rust/issues/84366, but I don't know if I would say it's exactly the same. For instance, the exploit involves no associated types (no `Output` of `FnOnce` at all), just the mere approach of:
 1. `type F<'a, 'b> = impl Fn(T<'a>) -> T<'b> : 'static;`
 1. `dyn Any`-erase it.
 1. downcast to a different `F<'c, 'd>` (_e.g._, `'a = 'b = 'c`, and `'d = 'whatever_you_want`).

Mainly, the `-> T<'b>` return could always become an `&mut Option<T<'b>>` out parameter (so as to have a `-> ()` returning closure), so the _return_ type of the closure not being `: 'static` is not really the issue; it's really about the closure being allowed to be `: 'static` despite _any part of the closure API_ being non-`'static`.

In fact, **before `1.66.0`**, we did have `impl 'static + Fn(&'a ())` being `'a`-infected (and thus non `: 'static`). While a very surprising property, it seems to be a more sound one that what we currently have.

The simplest possible exploit, with no `unsafe`(-but-sound) helper API (replaced by an implicit bound trick) requires:
  - `T<'lt>` to be covariant;
  - `type_alias_impl_trait`.

I'll start with that snippet nonetheless to get people familiarized with the context:

```rs
#![forbid(unsafe_code)] // No `unsafe!`
#![feature(type_alias_impl_trait)]

use core::any::Any;

/// Anything covariant will do, for this demo.
type T<'lt> = &'lt str;

type F<'a, 'b> = impl 'static + Fn(T<'a>) -> T<'b>;

fn helper<'a, 'b>(_: [&'b &'a (); 0]) -> F<'a, 'b> {
    |x: T<'a>| -> T<'b> { x } // this should *not* be `: 'static`
}

fn exploit<'a, 'b>(a: T<'a>) -> T<'b> {
    let f: F<'a, 'a> = helper([]);
    let any = Box::new(f) as Box<dyn Any>;

    let f: F<'a, 'static> = *any.downcast().unwrap_or_else(|_| unreachable!());

    f(a)
}

fn main() {
    let r: T<'static> = {
        let local = String::from("...");
        exploit(&local)
    };
    // Since `r` now dangles, we can easily make the use-after-free
    // point to newly allocated memory!
    let _unrelated = String::from("UAF");
    dbg!(r); // may print `UAF`! Run with `miri` to see the UB.
}
```

 - [Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=95404c193e0fa2e023608543ba3c723d)

___

Now, to avoid blaming implicit bounds and/or `type_alias_impl_trait`, here is a snippet not using either (which thus works independently of variance or lack thereof).

It does require `unsafe` to offer a sound API (it's the "witness types" / "witness lifetimes" pattern, wherein you can be dealing with a generic API with two potentially distinct generic parameters, but you have an instance of `EqWitness<T, U>` or `EqWitness<'a, 'b>`, with such instances only being constructible for `<T, T>` or `<'a, 'a>`.

```rs
/// Note: this is sound (modulo the `impl 'static`):
/// it's the "type witness" pattern (here, lifetime witness).
mod some_lib {
    use super::T; // e.g., `type T<'a> = Cell<&'a str>;`

    /// Invariant in `'a` and `'b` for soundness.
    pub struct LtEq<'a, 'b>(::core::marker::PhantomData<*mut Self>);

    impl<'a, 'b> LtEq<'a, 'b> {
        /// Invariant: these are the only actual instances of `LtEq` code may witness.
        pub fn new() -> LtEq<'a, 'a> {
            LtEq(<_>::default())
        }
    
        pub fn eq(&self) -> impl 'static + Fn(T<'a>) -> T<'b> {
            // this `impl Fn(T<'a>) -> T<'b>` is sound;
            let f = |a| unsafe { ::core::mem::transmute::<T<'a>, T<'b>>(a) };
            // what is *not* sound, is it being allowed to be `: 'static`
            f
        }
    }
}
```

With this tool/library at our disposal, we can then exploit it:

```rs
use core::{any::Any, cell::Cell};
use some_lib::LtEq;

/// Feel free to choose whatever you want, here.
type T<'lt> = Cell<&'lt str>;

/// I've explicitly lifetime annotated everything to make it clearer.
fn exploit<'a, 'b>(a: T<'a>) -> T<'b> {
    let f = LtEq::<'a, 'a>::new().eq();
    let any = Box::new(f) as Box<dyn Any>; // this should not compile: `T<'a> -> T<'a>` ought not to be `'static`

    let new_f = None.map(LtEq::<'a, 'b>::eq);

    fn downcast_a_to_type_of_new_f<F: 'static>(
        any: Box<dyn Any>,
        _: Option<F>,
    ) -> F {
        *any.downcast().unwrap_or_else(|_| unreachable!())
    }

    let f = downcast_a_to_type_of_new_f(any, new_f);

    f(a)
}

fn main() {
    let r: T<'static> = {
        let local = String::from("…");
        let a: T<'_> = Cell::new(&local[..]);
        exploit(a)
    };
    // Since `r` now dangles, we can easily make the use-after-free
    // point to newly allocated memory!
    let _unrelated = String::from("UAF");
    dbg!(r.get()); // may print `UAF`! Run with `miri` to see the UB.
}
```

  - [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6a16a2feaa272325326dbe0dcc499791)

___

[This happens since `1.66.0`](https://rust.godbolt.org/z/rTaaKoMc9).

@rustbot modify labels: +I-unsound +regression-from-stable-to-stable