Rollup merge of #124220 - RalfJung:interpret-wrong-vtable, r=oli-obk
Miri: detect wrong vtables in wide pointers Fixes https://github.com/rust-lang/miri/issues/3497. Needed to catch the UB that https://github.com/rust-lang/rust/pull/123572 will start exploiting. r? `@oli-obk`
This commit is contained in:
@@ -393,6 +393,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
let val = self.read_immediate(src)?;
|
||||
if data_a.principal() == data_b.principal() {
|
||||
// A NOP cast that doesn't actually change anything, should be allowed even with mismatching vtables.
|
||||
// (But currently mismatching vtables violate the validity invariant so UB is triggered anyway.)
|
||||
return self.write_immediate(*val, dest);
|
||||
}
|
||||
let (old_data, old_vptr) = val.to_scalar_pair();
|
||||
@@ -400,7 +401,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
let old_vptr = old_vptr.to_pointer(self)?;
|
||||
let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?;
|
||||
if old_trait != data_a.principal() {
|
||||
throw_ub_custom!(fluent::const_eval_upcast_mismatch);
|
||||
throw_ub!(InvalidVTableTrait {
|
||||
expected_trait: data_a,
|
||||
vtable_trait: old_trait,
|
||||
});
|
||||
}
|
||||
let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
|
||||
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
|
||||
|
||||
@@ -1020,16 +1020,20 @@ where
|
||||
pub(super) fn unpack_dyn_trait(
|
||||
&self,
|
||||
mplace: &MPlaceTy<'tcx, M::Provenance>,
|
||||
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, Pointer<Option<M::Provenance>>)> {
|
||||
assert!(
|
||||
matches!(mplace.layout.ty.kind(), ty::Dynamic(_, _, ty::Dyn)),
|
||||
"`unpack_dyn_trait` only makes sense on `dyn*` types"
|
||||
);
|
||||
let vtable = mplace.meta().unwrap_meta().to_pointer(self)?;
|
||||
let (ty, _) = self.get_ptr_vtable(vtable)?;
|
||||
let layout = self.layout_of(ty)?;
|
||||
let (ty, vtable_trait) = self.get_ptr_vtable(vtable)?;
|
||||
if expected_trait.principal() != vtable_trait {
|
||||
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
|
||||
}
|
||||
// This is a kind of transmute, from a place with unsized type and metadata to
|
||||
// a place with sized type and no metadata.
|
||||
let layout = self.layout_of(ty)?;
|
||||
let mplace =
|
||||
MPlaceTy { mplace: MemPlace { meta: MemPlaceMeta::None, ..mplace.mplace }, layout };
|
||||
Ok((mplace, vtable))
|
||||
@@ -1040,6 +1044,7 @@ where
|
||||
pub(super) fn unpack_dyn_star<P: Projectable<'tcx, M::Provenance>>(
|
||||
&self,
|
||||
val: &P,
|
||||
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
|
||||
) -> InterpResult<'tcx, (P, Pointer<Option<M::Provenance>>)> {
|
||||
assert!(
|
||||
matches!(val.layout().ty.kind(), ty::Dynamic(_, _, ty::DynStar)),
|
||||
@@ -1048,10 +1053,12 @@ where
|
||||
let data = self.project_field(val, 0)?;
|
||||
let vtable = self.project_field(val, 1)?;
|
||||
let vtable = self.read_pointer(&vtable.to_op(self)?)?;
|
||||
let (ty, _) = self.get_ptr_vtable(vtable)?;
|
||||
let (ty, vtable_trait) = self.get_ptr_vtable(vtable)?;
|
||||
if expected_trait.principal() != vtable_trait {
|
||||
throw_ub!(InvalidVTableTrait { expected_trait, vtable_trait });
|
||||
}
|
||||
// `data` is already the right thing but has the wrong type. So we transmute it.
|
||||
let layout = self.layout_of(ty)?;
|
||||
// `data` is already the right thing but has the wrong type. So we transmute it, by
|
||||
// projecting with offset 0.
|
||||
let data = data.transmute(layout, self)?;
|
||||
Ok((data, vtable))
|
||||
}
|
||||
|
||||
@@ -803,11 +803,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
let (vptr, dyn_ty, adjusted_receiver) = if let ty::Dynamic(data, _, ty::DynStar) =
|
||||
receiver_place.layout.ty.kind()
|
||||
{
|
||||
let (recv, vptr) = self.unpack_dyn_star(&receiver_place)?;
|
||||
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
|
||||
if dyn_trait != data.principal() {
|
||||
throw_ub_custom!(fluent::const_eval_dyn_star_call_vtable_mismatch);
|
||||
}
|
||||
let (recv, vptr) = self.unpack_dyn_star(&receiver_place, data)?;
|
||||
let (dyn_ty, _dyn_trait) = self.get_ptr_vtable(vptr)?;
|
||||
|
||||
(vptr, dyn_ty, recv.ptr())
|
||||
} else {
|
||||
@@ -829,7 +826,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
let vptr = receiver_place.meta().unwrap_meta().to_pointer(self)?;
|
||||
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
|
||||
if dyn_trait != data.principal() {
|
||||
throw_ub_custom!(fluent::const_eval_dyn_call_vtable_mismatch);
|
||||
throw_ub!(InvalidVTableTrait {
|
||||
expected_trait: data,
|
||||
vtable_trait: dyn_trait,
|
||||
});
|
||||
}
|
||||
|
||||
// It might be surprising that we use a pointer as the receiver even if this
|
||||
@@ -939,13 +939,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
let place = self.force_allocation(place)?;
|
||||
|
||||
let place = match place.layout.ty.kind() {
|
||||
ty::Dynamic(_, _, ty::Dyn) => {
|
||||
ty::Dynamic(data, _, ty::Dyn) => {
|
||||
// Dropping a trait object. Need to find actual drop fn.
|
||||
self.unpack_dyn_trait(&place)?.0
|
||||
self.unpack_dyn_trait(&place, data)?.0
|
||||
}
|
||||
ty::Dynamic(_, _, ty::DynStar) => {
|
||||
ty::Dynamic(data, _, ty::DynStar) => {
|
||||
// Dropping a `dyn*`. Need to find actual drop fn.
|
||||
self.unpack_dyn_star(&place)?.0
|
||||
self.unpack_dyn_star(&place, data)?.0
|
||||
}
|
||||
_ => {
|
||||
debug_assert_eq!(
|
||||
|
||||
@@ -339,16 +339,22 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
||||
) -> InterpResult<'tcx> {
|
||||
let tail = self.ecx.tcx.struct_tail_erasing_lifetimes(pointee.ty, self.ecx.param_env);
|
||||
match tail.kind() {
|
||||
ty::Dynamic(_, _, ty::Dyn) => {
|
||||
ty::Dynamic(data, _, ty::Dyn) => {
|
||||
let vtable = meta.unwrap_meta().to_pointer(self.ecx)?;
|
||||
// Make sure it is a genuine vtable pointer.
|
||||
let (_ty, _trait) = try_validation!(
|
||||
let (_dyn_ty, dyn_trait) = try_validation!(
|
||||
self.ecx.get_ptr_vtable(vtable),
|
||||
self.path,
|
||||
Ub(DanglingIntPointer(..) | InvalidVTablePointer(..)) =>
|
||||
InvalidVTablePtr { value: format!("{vtable}") }
|
||||
);
|
||||
// FIXME: check if the type/trait match what ty::Dynamic says?
|
||||
// Make sure it is for the right trait.
|
||||
if dyn_trait != data.principal() {
|
||||
throw_validation_failure!(
|
||||
self.path,
|
||||
InvalidMetaWrongTrait { expected_trait: data, vtable_trait: dyn_trait }
|
||||
);
|
||||
}
|
||||
}
|
||||
ty::Slice(..) | ty::Str => {
|
||||
let _len = meta.unwrap_meta().to_target_usize(self.ecx)?;
|
||||
@@ -933,7 +939,16 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.walk_value(op)?; // default handler
|
||||
// default handler
|
||||
try_validation!(
|
||||
self.walk_value(op),
|
||||
self.path,
|
||||
// It's not great to catch errors here, since we can't give a very good path,
|
||||
// but it's better than ICEing.
|
||||
Ub(InvalidVTableTrait { expected_trait, vtable_trait }) => {
|
||||
InvalidMetaWrongTrait { expected_trait, vtable_trait: *vtable_trait }
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,22 +88,22 @@ pub trait ValueVisitor<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>>: Sized {
|
||||
// Special treatment for special types, where the (static) layout is not sufficient.
|
||||
match *ty.kind() {
|
||||
// If it is a trait object, switch to the real type that was used to create it.
|
||||
ty::Dynamic(_, _, ty::Dyn) => {
|
||||
ty::Dynamic(data, _, ty::Dyn) => {
|
||||
// Dyn types. This is unsized, and the actual dynamic type of the data is given by the
|
||||
// vtable stored in the place metadata.
|
||||
// unsized values are never immediate, so we can assert_mem_place
|
||||
let op = v.to_op(self.ecx())?;
|
||||
let dest = op.assert_mem_place();
|
||||
let inner_mplace = self.ecx().unpack_dyn_trait(&dest)?.0;
|
||||
let inner_mplace = self.ecx().unpack_dyn_trait(&dest, data)?.0;
|
||||
trace!("walk_value: dyn object layout: {:#?}", inner_mplace.layout);
|
||||
// recurse with the inner type
|
||||
return self.visit_field(v, 0, &inner_mplace.into());
|
||||
}
|
||||
ty::Dynamic(_, _, ty::DynStar) => {
|
||||
ty::Dynamic(data, _, ty::DynStar) => {
|
||||
// DynStar types. Very different from a dyn type (but strangely part of the
|
||||
// same variant in `TyKind`): These are pairs where the 2nd component is the
|
||||
// vtable, and the first component is the data (which must be ptr-sized).
|
||||
let data = self.ecx().unpack_dyn_star(v)?.0;
|
||||
let data = self.ecx().unpack_dyn_star(v, data)?.0;
|
||||
return self.visit_field(v, 0, &data);
|
||||
}
|
||||
// Slices do not need special handling here: they have `Array` field
|
||||
|
||||
Reference in New Issue
Block a user