-
Notifications
You must be signed in to change notification settings - Fork 352
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
Miri does not report UB when transmuting vtables between instances of the same trait with different associated types #3905
Comments
We check that the principal trait still matches. But I seem to recall there was some problem with dyn types also affecting the type checker, where two dyn trait references were considered equal even when they should not? I don't know if that has been fixed already and Miri needs to copy that fix (i.e., the fix doesn't automatically apply everywhere), or whether that work is still ongoing, or whether it is a different issue. Cc @lcnr @compiler-errors |
@RalfJung: The type checker specifically checks that all projections are equal, but miri only checks the principal. I could extend the vtable compatibility check to implement this check, too, if you'd like. Side-note: We probably also need to check that the auto traits match... I feel like there may be some sketchy stuff going on when we transmute |
This is a totally unrelated issue to the recent strengthening of checks for pointer conversions allowed for a dyn trait (i.e. disallowing conversion like |
Codegen has a fast-path where if the principal does not change in a cast, it makes the cast a NOP. So if the vtable is affected by anything not captured in the principal -- projections or auto traits -- isn't that completely unsound? In fact that fast-path only checks the DefId of the principal trait, it doesn't even check the generic parameters. How can that possibly be correct, given what you said above? |
I'm pretty sure the compiler cannot exploit this since codegen fully identifies a vtable using the principal trait ref. So I am fairly sure that what Miri does (not erroring in your example) is correct wrt current codegen. But if we might want to change codegen in the future to have the vtable depend on more than just the principal trait, then we should change this in Miri first. |
@RalfJung: How is what correct? I am not certain what you're asking. Remember that that optimization during codegen is only being performed on well-formed unsize casts. Codegen is sound because we check during typeck that much stronger goals of the Unsize trait hold, which enforces equality of all of the parts of the dyn type, and we only permit certain kinds of casts to even make it to codegen. Then during codegen, we can optimize a subset of those casts because they must correspond to cases where the whole dyn trait was equal (modulo auto traits). Like, if the principal def ids match during an unsize cast, then we must have previously proven that the whole trait ref and all the projections were equal. This issue is unrelated to any soundness fixes recently fixed because miri is not validating an unsize cast right now; these are the source and target type of the transmute operator, which has no well-formedness checks performed on it except for the operand size. Am I misunderstanding something? 🤔 |
If there is a non-local invariant on unsize casts here, then that should be documented in
I am also curious what exactly the compiler does that would cause issues here. |
Apparently it's "hard to check" :) |
That doesn't even say what is hard to check though, so I am not sure it is referring to the same things as what you have been talking about. It's also no explanation for a lack of comment in the MIR docs and in codegen. ;) |
Well, I hope it's obvious that I didn't implement that codegen optimization or the lack of MIR validation, either. I'm just trying to explain why what is sound in codegen is sound today, according to the way the type system implements the (currently internal) I can probably get around to documenting the subtleties of how
As it stands, the way that the This is difficult to check because it requires peeling a complex pair of type arguments to an unsize cast like |
To be clear I wasn't blaming you or anyone. :) I just expressed that the current situation led me to (incorrectly) conclude that the checks Miri does are sufficient, it is all quite non obvious and how that could be improved.
If soundness of this is actually partially handled by the *library*, it seems even more important to write all this down, so yeah if you could do that that would be much appreciated. :)
|
Check vtable projections for validity in miri Currently, miri does not catch when we transmute `dyn Trait<Assoc = A>` to `dyn Trait<Assoc = B>`. This PR implements such a check, and fixes rust-lang/miri#3905. To do this, we modify `GlobalAlloc::VTable` to contain the *whole* list of `PolyExistentialPredicate`, and then modify `check_vtable_for_type` to validate the `PolyExistentialProjection`s of the vtable, along with the principal trait that was already being validated. cc `@RalfJung` r? `@lcnr` or types I also tweaked the diagnostics a bit. --- **Open question:** We don't validate the auto traits. You can transmute `dyn Foo` into `dyn Foo + Send`. Should we check that? We currently have a test that *exercises* this as not being UB: https://github.com/rust-lang/rust/blob/6c6d210089e4589afee37271862b9f88ba1d7755/src/tools/miri/tests/pass/dyn-upcast.rs#L14-L20 I'm not actually sure if we ever decided that's actually UB or not 🤔 We could perhaps still check that the underlying type of the object (i.e. the concrete type that was unsized) implements the auto traits, to catch UB like: ```rust fn main() { let x: &dyn Trait = &std::ptr::null_mut::<()>(); let _: &(dyn Trait + Send) = std::mem::transmute(x); //~^ this vtable is not allocated for a type that is `Send`! } ```
Rollup merge of rust-lang#130727 - compiler-errors:objects, r=RalfJung Check vtable projections for validity in miri Currently, miri does not catch when we transmute `dyn Trait<Assoc = A>` to `dyn Trait<Assoc = B>`. This PR implements such a check, and fixes rust-lang/miri#3905. To do this, we modify `GlobalAlloc::VTable` to contain the *whole* list of `PolyExistentialPredicate`, and then modify `check_vtable_for_type` to validate the `PolyExistentialProjection`s of the vtable, along with the principal trait that was already being validated. cc ``@RalfJung`` r? ``@lcnr`` or types I also tweaked the diagnostics a bit. --- **Open question:** We don't validate the auto traits. You can transmute `dyn Foo` into `dyn Foo + Send`. Should we check that? We currently have a test that *exercises* this as not being UB: https://github.com/rust-lang/rust/blob/6c6d210089e4589afee37271862b9f88ba1d7755/src/tools/miri/tests/pass/dyn-upcast.rs#L14-L20 I'm not actually sure if we ever decided that's actually UB or not 🤔 We could perhaps still check that the underlying type of the object (i.e. the concrete type that was unsized) implements the auto traits, to catch UB like: ```rust fn main() { let x: &dyn Trait = &std::ptr::null_mut::<()>(); let _: &(dyn Trait + Send) = std::mem::transmute(x); //~^ this vtable is not allocated for a type that is `Send`! } ```
Check vtable projections for validity in miri Currently, miri does not catch when we transmute `dyn Trait<Assoc = A>` to `dyn Trait<Assoc = B>`. This PR implements such a check, and fixes #3905. To do this, we modify `GlobalAlloc::VTable` to contain the *whole* list of `PolyExistentialPredicate`, and then modify `check_vtable_for_type` to validate the `PolyExistentialProjection`s of the vtable, along with the principal trait that was already being validated. cc ``@RalfJung`` r? ``@lcnr`` or types I also tweaked the diagnostics a bit. --- **Open question:** We don't validate the auto traits. You can transmute `dyn Foo` into `dyn Foo + Send`. Should we check that? We currently have a test that *exercises* this as not being UB: https://github.com/rust-lang/rust/blob/6c6d210089e4589afee37271862b9f88ba1d7755/src/tools/miri/tests/pass/dyn-upcast.rs#L14-L20 I'm not actually sure if we ever decided that's actually UB or not 🤔 We could perhaps still check that the underlying type of the object (i.e. the concrete type that was unsized) implements the auto traits, to catch UB like: ```rust fn main() { let x: &dyn Trait = &std::ptr::null_mut::<()>(); let _: &(dyn Trait + Send) = std::mem::transmute(x); //~^ this vtable is not allocated for a type that is `Send`! } ```
Take the following code for example (https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f1fc9b16c4d0b7cc512fcd33ee2097b6):
I included an empty function in the trait so its vtable won't be empty which may affect things somehow. AFAIK, changing principal trait (aka. not removing or adding auto traits only) is immediate UB, because the vtable has a validity invariant of being valid, and the compiler will exploit this (e.g. to hoist method loads).
However, Miri does not report this as UB.
The text was updated successfully, but these errors were encountered: