-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Associated type constraints on super traits allowing for unsound transmutation to trait objects #114389
Comments
A trivial change allows a segfault in safe code: trait Dog: TypeEq<This = dyn Cat> {
fn bark(&self) {
println!("bark");
}
fn extra(&self) {
panic!("extra");
}
}
// ...
oh_no_my_vtable.extra(); // segfault |
Note that that code does not segfault with rustc 1.71; it simply does nothing. 1.70 compiles it to a segfault. |
Here is the compiler panic example (presumably caused by the transmutation of a thin pointer to a fat pointer) trait TypeEq {
type This: ?Sized;
}
impl<T: ?Sized> TypeEq for T {
type This = Self;
}
fn identity<T: ?Sized>(t: &<T as TypeEq>::This) -> &T {
t
}
struct SomeType;
// compiler crashes if called
fn unsound(x: &SomeType) -> &dyn TypeEq<This = SomeType> {
identity(x)
}
fn main() {
let thing = SomeType;
unsound(&thing); // crash!
} Here is the example that causes a libLLVM segfault when using -O trait TypeEq {
type This: ?Sized;
}
impl<T: ?Sized> TypeEq for T {
type This = Self;
}
fn identity<T: ?Sized>(t: &<T as TypeEq>::This) -> &T {
t
}
trait Dog: TypeEq<This = [u8]> {
fn woof(&self) {
println!("woof");
}
}
fn main() {
let arr = vec![1; 0];
let arr: &[u8] = &arr; // fat pointer (ptr, len) with len being 0
let dog: &dyn Dog = identity(arr); // (data_ptr, vtable_ptr) with vtable_ptr having address 0
// (at least this is what i assume, idk how DSTs are actually
// represented when turned into trait objects)
dog.woof(); // llvm does not like this.
} Replacing |
@rustbot label -needs-triage T-compiler T-types I-unsound A-trait-objects A-associated-items A-coercions |
This is very similar to #57893, in that an incorrect associated type bound is assumed correct. |
This can be exploited without involving super traits. The trick is to construct the impossible type trait TypeEq {
type This: ?Sized;
fn bark(&self) {
println!("bark");
}
}
impl<T: ?Sized> TypeEq for T {
type This = T;
}
fn identity<T: ?Sized>(t: &<T as TypeEq>::This) -> &T {
t // T::This is deduced to be T
}
trait Cat {
fn meow(&self) {
println!("meow");
}
}
struct SomeType;
impl Cat for SomeType {}
pub fn main() {
let normal_coercion: &dyn Cat = &SomeType;
let oh_no_my_vtable: &dyn TypeEq<This = dyn Cat> = identity(normal_coercion);
oh_no_my_vtable.bark(); // meow :3
} |
Hmm, indeed. I've quickly looked at open I-unsound issues earlier but didn't find anything related but this issue could actually be a duplicate of the one you linked. Could somebody else take a look and confirm or refute that this is indeed a duplicate? |
Well, the fundamental issue at hand seems to be that if we say that
Coercing The underlying issue is that by inferring the associated type from the blanket implementation we assume that it applies to all types, when it simply does not because If we want to infer types from fn identity<T: ?Sized>(t: &<T as TypeEq>::This) -> &T {
t
} I don't think this function should compile, since there are types that satisfy the trait bounds of In my eyes, #57893 and this have the same root cause, but I do not think that normalization as mentioned over there is enough to fully fix this issue. |
Alternatively, one could require something like that to be valid (for a given equality bound), a trait object type like This also fixes the example from the earlier issue. I have no idea if this can be implemented though, maybe only in simple cases. Edit: after writing this, i have found that someone has already made that suggestion here. I also think my issue should be closed as duplicate, but I'd like to hear another opinion. |
WG-prioritization assigning priority (Zulip discussion). I'm going to nominate this issue for the next T-compiler meeting to get more eyeballs on this (and the related old issue mentioned). I'd like to understand better what's the current status of this unsoundness, how we can proceed and in which timeframe. @rustbot label +I-compiler-nominated |
I would also like to remind that more complex bounds might make restricting object safety without breaking sound code complicated. trait Tr {
type T: ?Sized;
}
trait ExtraBound {}
impl<T: ?Sized+ExtraBound> Tr for T {
type T=T;
}
trait TrExtra: Tr+ExtraBound {}
impl<T: Tr+ExtraBound+?Sized> TrExtra for T {}
// this is sound since the blanket impl requires ExtraBound.
type Sound = dyn Tr<T=i32>;
// this is not
type Unsound = dyn TrExtra<T=i32>; Though you could argue that the lack of object safety for This gets more interesting with trait Tr {
type T: ?Sized;
}
impl<T: ?Sized+Send> Tr for T {
type T=T;
}
// no contradiction with blanket because no Send
type Sound = dyn Tr<T=i32>;
// but this is not sound even though both of its components are
type Unsound = dyn Tr<T=i32>+Send; To handle this it would seem that more special treatment of auto traits is needed? Also there's trait Tr {
type T: ?Sized;
}
impl<T: ?Sized+'static> Tr for T {
type T=T;
}
type MightBeSound = dyn Tr<T=i32>;
type Unsound = dyn Tr<T=i32>+'static; I do not even want to think about how something like trait Tr<'a>: 'a {} would play out here. Or maybe I'm just pessimistic. I haven't thought that much about the exact details of which cases are actually problematic. |
Discussed in T-compiler meeting on Zulip. T-types will ignite a follow-up discussion on Zulip to evaluate the overlap with #57893 and also make the point about this perhaps being more feasible to accidentally trigger. Removing the nomination for T-compiler for now. @rustbot label -I-compiler-nominated |
I saw some of the discussion on zulip, #57893 is still completely broken. I have tried most of the examples there and all I've tried still worked. |
In today's t-types meeting, we decided to close this issue as a duplicate of #57893 -- we are going to place further comments in that issue. |
This code allows for unsound transmutation to trait objects of trait with impossible constraints.
I'm not sure if this is a special case of one of the numerous similar known bugs, so I thought I should share it just in case. Simpler cases where the types' memory layouts do not match cause the compiler to panic or llvm to segfault during optimization.
The text was updated successfully, but these errors were encountered: