Skip to content

Commit

Permalink
Miri: detect wrong vtables in wide pointers
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Apr 21, 2024
1 parent 655f421 commit 338de69
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 30 deletions.
5 changes: 4 additions & 1 deletion tests/fail/dyn-call-trait-mismatch.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Validation stops this too early.
//@compile-flags: -Zmiri-disable-validation

trait T1 {
#[allow(dead_code)]
fn method1(self: Box<Self>);
Expand All @@ -13,5 +16,5 @@ impl T1 for i32 {
fn main() {
let r = Box::new(0) as Box<dyn T1>;
let r2: Box<dyn T2> = unsafe { std::mem::transmute(r) };
r2.method2(); //~ERROR: call on a pointer whose vtable does not match its type
r2.method2(); //~ERROR: using vtable for trait `T1` but trait `T2` was expected
}
4 changes: 2 additions & 2 deletions tests/fail/dyn-call-trait-mismatch.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: `dyn` call on a pointer whose vtable does not match its type
error: Undefined Behavior: using vtable for trait `T1` but trait `T2` was expected
--> $DIR/dyn-call-trait-mismatch.rs:LL:CC
|
LL | r2.method2();
| ^^^^^^^^^^^^ `dyn` call on a pointer whose vtable does not match its type
| ^^^^^^^^^^^^ using vtable for trait `T1` but trait `T2` was expected
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
15 changes: 15 additions & 0 deletions tests/fail/dyn-upcast-nop-wrong-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This upcast is currently forbidden because it involves an invalid value.
// However, if in the future we relax the validity requirements for raw pointer vtables,
// we could consider allowing this again -- the cast itself isn't doing anything wrong,
// only the transmutes needed to set up the testcase are wrong.

use std::fmt;

fn main() {
// vtable_mismatch_nop_cast
let ptr: &dyn fmt::Display = &0;
let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) }; //~ERROR: wrong trait
// Even though the vtable is for the wrong trait, this cast doesn't actually change the needed
// vtable so it should still be allowed -- if we ever allow the line above.
let _ptr2 = ptr as *const dyn fmt::Debug;
}
15 changes: 15 additions & 0 deletions tests/fail/dyn-upcast-nop-wrong-trait.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug + std::marker::Send + std::marker::Sync`, but encountered `std::fmt::Display`
--> $DIR/dyn-upcast-nop-wrong-trait.rs:LL:CC
|
LL | let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) };
| ^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug + std::marker::Send + std::marker::Sync`, but encountered `std::fmt::Display`
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at $DIR/dyn-upcast-nop-wrong-trait.rs:LL:CC

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

9 changes: 6 additions & 3 deletions tests/fail/dyn-upcast-trait-mismatch.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Validation stops this too early.
//@compile-flags: -Zmiri-disable-validation

#![feature(trait_upcasting)]
#![allow(incomplete_features)]

Expand Down Expand Up @@ -57,7 +60,7 @@ impl Baz for i32 {

fn main() {
let baz: &dyn Baz = &1;
let baz_fake: &dyn Bar = unsafe { std::mem::transmute(baz) };
let _err = baz_fake as &dyn Foo;
//~^ERROR: upcast on a pointer whose vtable does not match its type
let baz_fake: *const dyn Bar = unsafe { std::mem::transmute(baz) };
let _err = baz_fake as *const dyn Foo;
//~^ERROR: using vtable for trait `Baz` but trait `Bar` was expected
}
6 changes: 3 additions & 3 deletions tests/fail/dyn-upcast-trait-mismatch.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: upcast on a pointer whose vtable does not match its type
error: Undefined Behavior: using vtable for trait `Baz` but trait `Bar` was expected
--> $DIR/dyn-upcast-trait-mismatch.rs:LL:CC
|
LL | let _err = baz_fake as &dyn Foo;
| ^^^^^^^^ upcast on a pointer whose vtable does not match its type
LL | let _err = baz_fake as *const dyn Foo;
| ^^^^^^^^ using vtable for trait `Baz` but trait `Bar` was expected
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
12 changes: 12 additions & 0 deletions tests/fail/validity/wrong-dyn-trait-generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use std::mem;

// Make sure we notice the mismatch also if the difference is "only" in the generic
// parameters of the trait.

trait Trait<T> {}
impl<T> Trait<T> for T {}

fn main() {
let x: &dyn Trait<i32> = &0;
let _y: *const dyn Trait<u32> = unsafe { mem::transmute(x) }; //~ERROR: wrong trait
}
15 changes: 15 additions & 0 deletions tests/fail/validity/wrong-dyn-trait-generic.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `Trait<u32>`, but encountered `Trait<i32>`
--> $DIR/wrong-dyn-trait-generic.rs:LL:CC
|
LL | let _y: *const dyn Trait<u32> = unsafe { mem::transmute(x) };
| ^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `Trait<u32>`, but encountered `Trait<i32>`
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at $DIR/wrong-dyn-trait-generic.rs:LL:CC

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

6 changes: 6 additions & 0 deletions tests/fail/validity/wrong-dyn-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use std::{fmt, mem};

fn main() {
let x: &dyn Send = &0;
let _y: *const dyn fmt::Debug = unsafe { mem::transmute(x) }; //~ERROR: wrong trait
}
15 changes: 15 additions & 0 deletions tests/fail/validity/wrong-dyn-trait.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: Undefined Behavior: constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `<trivial>`
--> $DIR/wrong-dyn-trait.rs:LL:CC
|
LL | let _y: *const dyn fmt::Debug = unsafe { mem::transmute(x) };
| ^^^^^^^^^^^^^^^^^ constructing invalid value: wrong trait in wide pointer vtable: expected `std::fmt::Debug`, but encountered `<trivial>`
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at $DIR/wrong-dyn-trait.rs:LL:CC

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

2 changes: 1 addition & 1 deletion tests/pass/cast-rfc0401-vtable-kinds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl Foo<u32> for u32 {
impl Bar for () {}

unsafe fn round_trip_and_call<'a>(t: *const (dyn Foo<u32> + 'a)) -> u32 {
let foo_e: *const dyn Foo<u16> = t as *const _;
let foo_e: *const dyn Foo<u32> = t as *const _;
let r_1 = foo_e as *mut dyn Foo<u32>;

(&*r_1).foo(0)
Expand Down
42 changes: 22 additions & 20 deletions tests/pass/dyn-upcast.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
#![feature(trait_upcasting)]
#![allow(incomplete_features)]

use std::fmt;

fn main() {
basic();
diamond();
struct_();
replace_vptr();
vtable_mismatch_nop_cast();
vtable_nop_cast();
}

fn vtable_mismatch_nop_cast() {
let ptr: &dyn std::fmt::Display = &0;
// Even though the vtable is for the wrong trait, this cast doesn't actually change the needed
// vtable so it should still be allowed.
let ptr: *const (dyn std::fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) };
let _ptr2 = ptr as *const dyn std::fmt::Debug;
fn vtable_nop_cast() {
let ptr: &dyn fmt::Debug = &0;
// We transmute things around, but the principal trait does not change, so this is allowed.
let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) };
// This cast is a NOP and should be allowed.
let _ptr2 = ptr as *const dyn fmt::Debug;
}

fn basic() {
trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync {
trait Foo: PartialEq<i32> + fmt::Debug + Send + Sync {
fn a(&self) -> i32 {
10
}
Expand Down Expand Up @@ -67,7 +69,7 @@ fn basic() {
}

let baz: &dyn Baz = &1;
let _: &dyn std::fmt::Debug = baz;
let _: &dyn fmt::Debug = baz;
assert_eq!(*baz, 1);
assert_eq!(baz.a(), 100);
assert_eq!(baz.b(), 200);
Expand All @@ -77,7 +79,7 @@ fn basic() {
assert_eq!(baz.w(), 21);

let bar: &dyn Bar = baz;
let _: &dyn std::fmt::Debug = bar;
let _: &dyn fmt::Debug = bar;
assert_eq!(*bar, 1);
assert_eq!(bar.a(), 100);
assert_eq!(bar.b(), 200);
Expand All @@ -86,22 +88,22 @@ fn basic() {
assert_eq!(bar.w(), 21);

let foo: &dyn Foo = baz;
let _: &dyn std::fmt::Debug = foo;
let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);
assert_eq!(foo.z(), 11);
assert_eq!(foo.y(), 12);

let foo: &dyn Foo = bar;
let _: &dyn std::fmt::Debug = foo;
let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);
assert_eq!(foo.z(), 11);
assert_eq!(foo.y(), 12);
}

fn diamond() {
trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync {
trait Foo: PartialEq<i32> + fmt::Debug + Send + Sync {
fn a(&self) -> i32 {
10
}
Expand Down Expand Up @@ -166,7 +168,7 @@ fn diamond() {
}

let baz: &dyn Baz = &1;
let _: &dyn std::fmt::Debug = baz;
let _: &dyn fmt::Debug = baz;
assert_eq!(*baz, 1);
assert_eq!(baz.a(), 100);
assert_eq!(baz.b(), 200);
Expand All @@ -178,7 +180,7 @@ fn diamond() {
assert_eq!(baz.v(), 31);

let bar1: &dyn Bar1 = baz;
let _: &dyn std::fmt::Debug = bar1;
let _: &dyn fmt::Debug = bar1;
assert_eq!(*bar1, 1);
assert_eq!(bar1.a(), 100);
assert_eq!(bar1.b(), 200);
Expand All @@ -187,7 +189,7 @@ fn diamond() {
assert_eq!(bar1.w(), 21);

let bar2: &dyn Bar2 = baz;
let _: &dyn std::fmt::Debug = bar2;
let _: &dyn fmt::Debug = bar2;
assert_eq!(*bar2, 1);
assert_eq!(bar2.a(), 100);
assert_eq!(bar2.c(), 300);
Expand All @@ -196,17 +198,17 @@ fn diamond() {
assert_eq!(bar2.v(), 31);

let foo: &dyn Foo = baz;
let _: &dyn std::fmt::Debug = foo;
let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);

let foo: &dyn Foo = bar1;
let _: &dyn std::fmt::Debug = foo;
let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);

let foo: &dyn Foo = bar2;
let _: &dyn std::fmt::Debug = foo;
let _: &dyn fmt::Debug = foo;
assert_eq!(*foo, 1);
assert_eq!(foo.a(), 100);
}
Expand All @@ -215,7 +217,7 @@ fn struct_() {
use std::rc::Rc;
use std::sync::Arc;

trait Foo: PartialEq<i32> + std::fmt::Debug + Send + Sync {
trait Foo: PartialEq<i32> + fmt::Debug + Send + Sync {
fn a(&self) -> i32 {
10
}
Expand Down

0 comments on commit 338de69

Please sign in to comment.