-
Notifications
You must be signed in to change notification settings - Fork 13.1k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # 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: