-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rust allows impl Fn(T<'a>) -> T<'b>
to be : 'static
, which is unsound
#112905
Comments
I definitely think this is a dupe of #84366 you're constraining a hidden type to be a closure that thinks its |
Regression: found 10 bors merge commits in the specified range |
WG-prioritization assigning priority (Zulip discussion). @rustbot label -I-prioritize +P-high |
We dove into this in the @rust-lang/types meetup and uncovered a few interesting observations. Most importantly, we believe this is a dup of #25860. The reason why is not super obvious. The key point is that for this type F<'a, 'b> = impl 'static + Fn(T<'a>) -> T<'b>; ...the hidden type is a closure that (implicitly) relies on fn helper<'a, 'b>(_: [&'b &'a (); 0]) -> F<'a, 'b> {
// Why does this compile? Because of an implied bound
// `where `'a: 'b` (which could well have been explicit);
// that relationship is required to make the hidden type
// well-formed.
|x: T<'a>| -> T<'b> { x } // this should *not* be `: 'static`
} We ordinarily require that the hidden type is WF under the where-clauses on the type alias (example). But in this case, because of the bugs in how we handle implied bounds, these where-clauses are "implied" and are not present in the predicates list for the closure. If we make them explicit, by adding As a side note, we dug into the "dyn-transmute" mechanism here, which is an important interaction I was not aware of. The key point seems to be that if you have "semantic |
@nikomatsakis What about the second example that doesn't use |
@workingjubilee Hmm, which example is that? I'm having trouble finding it :) |
This one /// Note: this is sound! It's the "type witness" pattern (here, lt witness).
mod some_lib {
use super::T;
/// Invariant in `'a` and `'b` for soundness.
pub struct LtEq<'a, 'b>(::core::marker::PhantomData<*mut Self>);
impl<'a, 'b> LtEq<'a, 'b> {
pub fn new() -> LtEq<'a, 'a> {
LtEq(<_>::default())
}
pub fn eq(&self) -> impl 'static + Fn(T<'a>) -> T<'b> {
|a| unsafe { ::core::mem::transmute::<T<'a>, T<'b>>(a) }
}
}
}
use core::{any::Any, cell::Cell};
use some_lib::LtEq;
/// Feel free to choose whatever you want, here.
type T<'lt> = Cell<&'lt str>;
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>;
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)
};
dbg!(r.get());
} |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
…illot Add tests for rust-lang#112905 This is a part of rust-lang#105107. Adds the tests from the OP in rust-lang#112905.
This is similar to #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
ofFnOnce
at all), just the mere approach of:type F<'a, 'b> = impl Fn(T<'a>) -> T<'b> : 'static;
dyn Any
-erase it.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 haveimpl '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:
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 ofEqWitness<T, U>
orEqWitness<'a, 'b>
, with such instances only being constructible for<T, T>
or<'a, 'a>
.With this tool/library at our disposal, we can then exploit it:
This happens since
1.66.0
.@rustbot modify labels: +I-unsound +regression-from-stable-to-stable
The text was updated successfully, but these errors were encountered: