-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
arbitrary_self_types + derive_coerce_pointee allows calling methods whose where clauses are violated #136702
Comments
cc @adetaylor @rust-lang/lang |
I've had the same feeling about this and then I did try for a while and didn't find a way to make this UB. Which proves nothing, but changed my gut feeling to “this might be fine”. |
I think this boils down essentially to the question: Can you use an impossible non-higher-ranked type-outlives where clause to do UB if you don't have a value of the type? That's because for you to do something sneaky, for example, you'd need to have a value of the type to do something with that type-outlives where clause (e.g. move it into a thread), and you can't do that without that type showing up in a function signature, and you can't do that (in an dyn-compatible way) unless it shows up in the args of a So I'm semi-reasonably convinced this is not possible yet. However, it does still fill me with a bit of worry that we don't change the way implied bounds work or something that could make this UB in the future... One way we could prevent this becoming a problem is by strengthening the |
The primary motivating use-case for |
The other option would be to disallow changing that lifetime in |
My guess is that disallowing lifetime casts in general would be too breaking, but perhaps we could do it only for traits with a function that has a trait bound that implies |
The other option here is to make |
I just had an idea: What about we disallow all raw pointer casts of raw dyn pointers (both lifetime and auto trait case), but only when the trait has any methods with a |
That seems pretty arbitrary, and also interacts poorly with adding certain kinds of trivial where clauses to methods which today which is not a breaking change. I'd really rather we don't try to design some fix for this that's cookie-cutter designed just for this problem. |
…o_to_object-hard-error, r=<try> Make `ptr_cast_add_auto_to_object` lint into hard error In Rust 1.81, we added a FCW lint (including linting in dependencies) against pointer casts that add an auto trait to dyn bounds. This was part of work making casts of pointers involving trait objects stricter, and was part of the work needed to restabilize trait upcasting. We considered just making this a hard error, but opted against it at that time due to breakage found by crater. This breakage was mostly due to the `anymap` crate which has been a persistent problem for us. It's now a year later, and the fact that this is not yet a hard error is giving us pause about stabilizing arbitrary self types and `derive(CoercePointee)`. So let's see about making a hard error of this. r? ghost cc `@adetaylor` `@Darksonn` `@BoxyUwU` `@RalfJung` `@compiler-errors` `@oli-obk` `@WaffleLapkin` Related: - rust-lang#135881 - rust-lang#136702 Tracking: - rust-lang#127323 - rust-lang#44874 - rust-lang#123430
For the record we already forbid casting |
…, r=<try> [WIP] Forbid object lifetime changing pointer casts Fixes rust-lang#136702 r? `@ghost`
@BoxyUwU is this even possible as a FCW? Realistically, it’ll create borrow-check errors, I’d be surprised if the borrow checker supports conditions like “I want |
I've seen the cast been used a fair number of times with I guess the real rule here is that if you cast from So the precise check we need is to go through each method on the trait and verify that if it's callable on |
@Darksonn are you proposing that the check you describe is done at the time of the cast, or at method call time? I've been digging into what a "valid vtable pointer" might mean, and this example surprises me: use std::any::TypeId;
use std::marker::PhantomData;
#[allow(dead_code)]
trait GetTypeId {
fn foo(&self);
fn get_type_id(&self) -> TypeId
where
Self: 'static;
}
impl<T: ?Sized> GetTypeId for PhantomData<T> {
fn foo(&self) {}
fn get_type_id(&self) -> TypeId
where
Self: 'static,
{
TypeId::of::<T>()
}
}
fn make_bad_get_type_id<T: ?Sized>(_: &T) -> *const (dyn GetTypeId + 'static) {
&PhantomData::<T> as *const (dyn GetTypeId + '_) as *const (dyn GetTypeId + 'static)
}
fn make_good_get_type_id<'a, T: ?Sized>(_: &'a T) -> *const (dyn GetTypeId + 'a) {
&PhantomData::<T> as *const dyn GetTypeId
}
fn main() {
let mut x = 1234;
let reference = &mut x;
let ok_tid_of_ref = make_good_get_type_id(reference);
let rf = unsafe { &*ok_tid_of_ref };
// let ok_type_id = rf.get_type_id(); // does not compile
let bad_tid_of_ref = make_bad_get_type_id(reference);
let rf = unsafe { &*bad_tid_of_ref };
let bad_type_id = rf.get_type_id();
dbg!(bad_type_id);
} The surprises are:
I'm assuming your intention is that the checks you propose are done at cast-time, whereas to be consistent with the above behavior, maybe it's OK to allow |
My intent is that the checks I propose are done at cast time. This reinforces that even raw pointers must always have a valid vtable for the type when they are wide. This is consistent with #101336. As for your examples, |
Please be aware that type- and lifetime-outlives where clauses may never be used to disqualify a trait from a vtable, since we erase lifetime information post-borrowck we literally do not (and will never) have the information to know whether it may hold or not. So no, today |
The entire crux of this issue is that calling the Or do you mean to say that it's okay to call the |
Yes, for some substitutions of The notion that lifetime generics in general are not treated on the same footing as type and const generics in the type system is pervasive, and type- and lifetime-outlives where clauses are never practically going to be checkable in codegen. All that we can do with lifetimes is gather their side-effects during borrowck and validate them; this is why what Boxy's checking in #136776 is likely going to be the only way forward here. |
I'm trying to understand exactly what this example is showing me and in what way it truly requires arbitrary-self-types. I guess the wacky thing we are now able to do is to combine (1) the pre-existing rules that allow you to cast a |
Basically by combining the two features, you can call a function pointer in the vtable of a raw pointer safely. |
Yeah. I regret permitting the cast between raw pointers in quite the way we do, but I feel like this is the "heart of the problem" |
I've been thinking a bit about this problem. I think the root of what is going is something like this:
I presume the reason that @steffahn had trouble creating UB is that to actually access the raw pointer, you still have to use But we can see the risk from those predicates, in that it can allow access to safe abstractions that are gated on So I guess I can see the answer to "why is this a problem NOW when it wasn't before". I still feel like the root problem is indeed casting raw pointers in safe code, but migrating away from that will be tricky. Does this sound like an accurate summary, @RalfJung or others? |
That sounds right.
The fallout from forbidding these casts seems completely acceptable based on the crater run, so I think we have a viable plan: fcw against such casts, and eventually make that a hard error. (Though making borrowck emit an fcw could become tricky? I do not know the implementation well enough to judge this.)
|
To be clear, that accessing the pointer’s target would require
|
I wrote down my thoughts on arbitrary self types from a soundness pov which involved writing down stuff about this issue: here. Relevant part would be: Something that is interesting to think about is why raw pointers allowing safe casting of object types hasn't caused problems before For example while you can:
The If it's going to be safe to make vtable calls, and safe to construct types that can make vtable calls, then it just has to be unsafe to have an object type that allows methods to be called on its vtable that shouldnt be called1. In some sense raw pointers and vtable calls have never been sound, but as the unsoundness only affects implementors of I need to spend some time looking at the results of the crater run myself I know @RalfJung has looked over them already though. Hopefully we can just forbid safe pointer casting of object lifetimes though if we can't there are probably other paths forwards Footnotes
|
I also went through the same motions of trying to do this using any of the existing std types, and reached the same conclusion. (The other experiment I was doing was trying to determine if the sheer existence of |
If we focus on the case of parameter-less trait bounds (which also looks like it’s the most common use-case) [and perhaps additionally traits with type parameters that are
If all of the above is true / unproblematic, one could even expose this via a trait, I suppose trait RemoveFreeLifetimes {
type WithoutFreeLifetimes: ?Sized + 'static;
}
// automatically implemented by the compiler
// for every type `T: ?Sized` Then for any type (With such an API, you still don’t really express the “T: TraitBound”-implies- One can furthermore think of I’m not sure if the future-compatibility concerns @compiler-errors mentioned would also apply to this case of parameter-less (or lifetime-free-only parameter) trait bounds. Though it’s also not clear to me whether it’s technically possible to begin with2 to introduce an error only for cases outside of this more limited scope. Footnotes
|
nevermind that… months ago: https://rust-lang.zulipchat.com/#narrow/channel/136281-t-opsem/topic/Can.20.60*mut.20dyn.20T.20.2B.20'a.20-.3E.20*mut.20dyn.20T.20.2B.20'b.60.20cast.20be.20exploited.3F/near/424767839 Edit: Actually, I think both might be true, I do believe that when I came up with the |
Here's an example by @steffahn:
The double
as *const dyn GetTypeId as *const dyn GetTypeId
is necessary; the secondas
changes the lifetime something short-lived to'static
. That means we callget_type_id
on a type that does not satisfySelf: 'static
.@BoxyUwU has a similar example:
Boxy's example is mitigated by a FCW lint. However, the lifetime example doesn't trigger a lint, and while the code above is "fine", we should at least have a good idea for why this cannot cause UB in other ways. (My gut feeling is that this can cause UB but I haven't tried.)
Cc @rust-lang/types
The text was updated successfully, but these errors were encountered: