Skip to content

Commit 0e07c42

Browse files
committed
Auto merge of #54762 - RalfJung:miri-validate, r=oli-obk
Prepare miri engine for enforcing validity invariant during execution In particular, make recursive checking of references optional, and add a `const_mode` parameter that says whether `usize` is allowed to contain a pointer. Also refactor validation a bit to be type-driven at the "leafs" (primitive types), and separately validate scalar layout to catch `NonNull` violations (which it did not properly validate before). Fixes #53826 Also fixes #54751 r? @oli-obk
2 parents e1643a8 + fe96f82 commit 0e07c42

31 files changed

+634
-351
lines changed

src/librustc/ich/impls_ty.rs

+4
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,10 @@ for ::mir::interpret::EvalErrorKind<'gcx, O> {
560560
a.hash_stable(hcx, hasher);
561561
b.hash_stable(hcx, hasher)
562562
},
563+
FunctionRetMismatch(a, b) => {
564+
a.hash_stable(hcx, hasher);
565+
b.hash_stable(hcx, hasher)
566+
},
563567
NoMirFor(ref s) => s.hash_stable(hcx, hasher),
564568
UnterminatedCString(ptr) => ptr.hash_stable(hcx, hasher),
565569
PointerOutOfBounds {

src/librustc/mir/interpret/error.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ pub enum EvalErrorKind<'tcx, O> {
186186

187187
FunctionAbiMismatch(Abi, Abi),
188188
FunctionArgMismatch(Ty<'tcx>, Ty<'tcx>),
189+
FunctionRetMismatch(Ty<'tcx>, Ty<'tcx>),
189190
FunctionArgCountMismatch,
190191
NoMirFor(String),
191192
UnterminatedCString(Pointer),
@@ -294,7 +295,8 @@ impl<'tcx, O> EvalErrorKind<'tcx, O> {
294295
use self::EvalErrorKind::*;
295296
match *self {
296297
MachineError(ref inner) => inner,
297-
FunctionAbiMismatch(..) | FunctionArgMismatch(..) | FunctionArgCountMismatch =>
298+
FunctionAbiMismatch(..) | FunctionArgMismatch(..) | FunctionRetMismatch(..)
299+
| FunctionArgCountMismatch =>
298300
"tried to call a function through a function pointer of incompatible type",
299301
InvalidMemoryAccess =>
300302
"tried to access memory through an invalid pointer",
@@ -470,6 +472,10 @@ impl<'tcx, O: fmt::Debug> fmt::Debug for EvalErrorKind<'tcx, O> {
470472
write!(f, "tried to call a function with argument of type {:?} \
471473
passing data of type {:?}",
472474
callee_ty, caller_ty),
475+
FunctionRetMismatch(caller_ty, callee_ty) =>
476+
write!(f, "tried to call a function with return type {:?} \
477+
passing return place of type {:?}",
478+
callee_ty, caller_ty),
473479
FunctionArgCountMismatch =>
474480
write!(f, "tried to call a function with incorrect number of arguments"),
475481
BoundsCheck { ref len, ref index } =>

src/librustc/ty/structural_impls.rs

+4
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,10 @@ impl<'a, 'tcx, O: Lift<'tcx>> Lift<'tcx> for interpret::EvalErrorKind<'a, O> {
492492
tcx.lift(&a)?,
493493
tcx.lift(&b)?,
494494
),
495+
FunctionRetMismatch(a, b) => FunctionRetMismatch(
496+
tcx.lift(&a)?,
497+
tcx.lift(&b)?,
498+
),
495499
FunctionArgCountMismatch => FunctionArgCountMismatch,
496500
NoMirFor(ref s) => NoMirFor(s.clone()),
497501
UnterminatedCString(ptr) => UnterminatedCString(ptr),

src/librustc_lint/builtin.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -1558,15 +1558,13 @@ fn validate_const<'a, 'tcx>(
15581558
let ecx = ::rustc_mir::const_eval::mk_eval_cx(tcx, gid.instance, param_env).unwrap();
15591559
let result = (|| {
15601560
let op = ecx.const_to_op(constant)?;
1561-
let mut todo = vec![(op, Vec::new())];
1562-
let mut seen = FxHashSet();
1563-
seen.insert(op);
1564-
while let Some((op, mut path)) = todo.pop() {
1561+
let mut ref_tracking = ::rustc_mir::interpret::RefTracking::new(op);
1562+
while let Some((op, mut path)) = ref_tracking.todo.pop() {
15651563
ecx.validate_operand(
15661564
op,
15671565
&mut path,
1568-
&mut seen,
1569-
&mut todo,
1566+
Some(&mut ref_tracking),
1567+
/* const_mode */ true,
15701568
)?;
15711569
}
15721570
Ok(())

src/librustc_mir/const_eval.rs

+1
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx>
274274
type MemoryKinds = !;
275275

276276
const MUT_STATIC_KIND: Option<!> = None; // no mutating of statics allowed
277+
const ENFORCE_VALIDITY: bool = false; // for now, we don't
277278

278279
fn find_fn(
279280
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,

src/librustc_mir/interpret/eval_context.rs

+5
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
231231
/// Mark a storage as live, killing the previous content and returning it.
232232
/// Remember to deallocate that!
233233
pub fn storage_live(&mut self, local: mir::Local) -> EvalResult<'tcx, LocalValue> {
234+
assert!(local != mir::RETURN_PLACE, "Cannot make return place live");
234235
trace!("{:?} is now live", local);
235236

236237
let layout = self.layout_of_local(self.cur_frame(), local)?;
@@ -242,6 +243,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
242243
/// Returns the old value of the local.
243244
/// Remember to deallocate that!
244245
pub fn storage_dead(&mut self, local: mir::Local) -> LocalValue {
246+
assert!(local != mir::RETURN_PLACE, "Cannot make return place dead");
245247
trace!("{:?} is now dead", local);
246248

247249
mem::replace(&mut self.frame_mut().locals[local], LocalValue::Dead)
@@ -446,6 +448,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
446448
let dummy =
447449
LocalValue::Live(Operand::Immediate(Value::Scalar(ScalarMaybeUndef::Undef)));
448450
let mut locals = IndexVec::from_elem(dummy, &mir.local_decls);
451+
// Return place is handled specially by the `eval_place` functions, and the
452+
// entry in `locals` should never be used. Make it dead, to be sure.
453+
locals[mir::RETURN_PLACE] = LocalValue::Dead;
449454
// Now mark those locals as dead that we do not want to initialize
450455
match self.tcx.describe_def(instance.def_id()) {
451456
// statics and constants don't have `Storage*` statements, no need to look for them

src/librustc_mir/interpret/machine.rs

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
3333
/// The memory kind to use for mutated statics -- or None if those are not supported.
3434
const MUT_STATIC_KIND: Option<Self::MemoryKinds>;
3535

36+
/// Whether to enforce the validity invariant
37+
const ENFORCE_VALIDITY: bool;
38+
3639
/// Called before a basic block terminator is executed.
3740
/// You can use this to detect endlessly running programs.
3841
fn before_terminator(ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>) -> EvalResult<'tcx>;

src/librustc_mir/interpret/memory.rs

+33-19
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use std::collections::VecDeque;
2020
use std::ptr;
2121

22-
use rustc::ty::{self, Instance, query::TyCtxtAt};
22+
use rustc::ty::{self, Instance, ParamEnv, query::TyCtxtAt};
2323
use rustc::ty::layout::{self, Align, TargetDataLayout, Size, HasDataLayout};
2424
use rustc::mir::interpret::{Pointer, AllocId, Allocation, ConstValue, GlobalId,
2525
EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic,
@@ -235,7 +235,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
235235
// Check non-NULL/Undef, extract offset
236236
let (offset, alloc_align) = match ptr {
237237
Scalar::Ptr(ptr) => {
238-
let (size, align) = self.get_size_and_align(ptr.alloc_id)?;
238+
let (size, align) = self.get_size_and_align(ptr.alloc_id);
239239
// check this is not NULL -- which we can ensure only if this is in-bounds
240240
// of some (potentially dead) allocation.
241241
if ptr.offset > size {
@@ -284,7 +284,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
284284
/// If you want to check bounds before doing a memory access, be sure to
285285
/// check the pointer one past the end of your access, then everything will
286286
/// work out exactly.
287-
pub fn check_bounds(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> {
287+
pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> {
288288
let alloc = self.get(ptr.alloc_id)?;
289289
let allocation_size = alloc.bytes.len() as u64;
290290
if ptr.offset.bytes() > allocation_size {
@@ -296,6 +296,13 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
296296
}
297297
Ok(())
298298
}
299+
300+
/// Check if the memory range beginning at `ptr` and of size `Size` is "in-bounds".
301+
#[inline(always)]
302+
pub fn check_bounds(&self, ptr: Pointer, size: Size, access: bool) -> EvalResult<'tcx> {
303+
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
304+
self.check_bounds_ptr(ptr.offset(size, &*self)?, access)
305+
}
299306
}
300307

301308
/// Allocation accessors
@@ -352,19 +359,28 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
352359
}
353360
}
354361

355-
pub fn get_size_and_align(&self, id: AllocId) -> EvalResult<'tcx, (Size, Align)> {
356-
Ok(match self.get(id) {
357-
Ok(alloc) => (Size::from_bytes(alloc.bytes.len() as u64), alloc.align),
358-
Err(err) => match err.kind {
359-
EvalErrorKind::DanglingPointerDeref =>
360-
// This should be in the dead allocation map
361-
*self.dead_alloc_map.get(&id).expect(
362-
"allocation missing in dead_alloc_map"
363-
),
364-
// E.g. a function ptr allocation
365-
_ => return Err(err)
362+
pub fn get_size_and_align(&self, id: AllocId) -> (Size, Align) {
363+
if let Ok(alloc) = self.get(id) {
364+
return (Size::from_bytes(alloc.bytes.len() as u64), alloc.align);
365+
}
366+
// Could also be a fn ptr or extern static
367+
match self.tcx.alloc_map.lock().get(id) {
368+
Some(AllocType::Function(..)) => (Size::ZERO, Align::from_bytes(1, 1).unwrap()),
369+
Some(AllocType::Static(did)) => {
370+
// The only way `get` couldnÄt have worked here is if this is an extern static
371+
assert!(self.tcx.is_foreign_item(did));
372+
// Use size and align of the type
373+
let ty = self.tcx.type_of(did);
374+
let layout = self.tcx.layout_of(ParamEnv::empty().and(ty)).unwrap();
375+
(layout.size, layout.align)
366376
}
367-
})
377+
_ => {
378+
// Must be a deallocated pointer
379+
*self.dead_alloc_map.get(&id).expect(
380+
"allocation missing in dead_alloc_map"
381+
)
382+
}
383+
}
368384
}
369385

370386
pub fn get_mut(
@@ -524,8 +540,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
524540
) -> EvalResult<'tcx, &[u8]> {
525541
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
526542
self.check_align(ptr.into(), align)?;
527-
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
528-
self.check_bounds(ptr.offset(size, &*self)?, true)?;
543+
self.check_bounds(ptr, size, true)?;
529544

530545
if check_defined_and_ptr {
531546
self.check_defined(ptr, size)?;
@@ -569,8 +584,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
569584
) -> EvalResult<'tcx, &mut [u8]> {
570585
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
571586
self.check_align(ptr.into(), align)?;
572-
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
573-
self.check_bounds(ptr.offset(size, &self)?, true)?;
587+
self.check_bounds(ptr, size, true)?;
574588

575589
self.mark_definedness(ptr, size, true)?;
576590
self.clear_relocations(ptr, size)?;

src/librustc_mir/interpret/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ pub use self::memory::{Memory, MemoryKind};
3535
pub use self::machine::Machine;
3636

3737
pub use self::operand::{ScalarMaybeUndef, Value, ValTy, Operand, OpTy};
38+
39+
pub use self::validity::RefTracking;

src/librustc_mir/interpret/place.rs

+31-3
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ impl MemPlace {
131131
}
132132

133133
impl<'tcx> MPlaceTy<'tcx> {
134+
/// Produces a MemPlace that works for ZST but nothing else
135+
#[inline]
136+
pub fn dangling(layout: TyLayout<'tcx>, cx: impl HasDataLayout) -> Self {
137+
MPlaceTy {
138+
mplace: MemPlace::from_scalar_ptr(
139+
Scalar::from_uint(layout.align.abi(), cx.pointer_size()),
140+
layout.align
141+
),
142+
layout
143+
}
144+
}
145+
134146
#[inline]
135147
fn from_aligned_ptr(ptr: Pointer, layout: TyLayout<'tcx>) -> Self {
136148
MPlaceTy { mplace: MemPlace::from_ptr(ptr, layout.align), layout }
@@ -555,6 +567,13 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
555567
dest: PlaceTy<'tcx>,
556568
) -> EvalResult<'tcx> {
557569
trace!("write_value: {:?} <- {:?}", *dest, src_val);
570+
// Check that the value actually is okay for that type
571+
if M::ENFORCE_VALIDITY {
572+
// Something changed somewhere, better make sure it matches the type!
573+
let op = OpTy { op: Operand::Immediate(src_val), layout: dest.layout };
574+
self.validate_operand(op, &mut vec![], None, /*const_mode*/false)?;
575+
}
576+
558577
// See if we can avoid an allocation. This is the counterpart to `try_read_value`,
559578
// but not factored as a separate function.
560579
let mplace = match dest.place {
@@ -576,7 +595,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
576595
self.write_value_to_mplace(src_val, dest)
577596
}
578597

579-
/// Write a value to memory
598+
/// Write a value to memory. This does NOT do validation, so you better had already
599+
/// done that before calling this!
580600
fn write_value_to_mplace(
581601
&mut self,
582602
value: Value,
@@ -640,12 +660,18 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
640660
};
641661
// Slow path, this does not fit into an immediate. Just memcpy.
642662
trace!("copy_op: {:?} <- {:?}", *dest, *src);
643-
let (dest_ptr, dest_align) = self.force_allocation(dest)?.to_scalar_ptr_align();
663+
let dest = self.force_allocation(dest)?;
664+
let (dest_ptr, dest_align) = dest.to_scalar_ptr_align();
644665
self.memory.copy(
645666
src_ptr, src_align,
646667
dest_ptr, dest_align,
647668
src.layout.size, false
648-
)
669+
)?;
670+
if M::ENFORCE_VALIDITY {
671+
// Something changed somewhere, better make sure it matches the type!
672+
self.validate_operand(dest.into(), &mut vec![], None, /*const_mode*/false)?;
673+
}
674+
Ok(())
649675
}
650676

651677
/// Make sure that a place is in memory, and return where it is.
@@ -668,6 +694,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
668694
// that has different alignment than the outer field.
669695
let local_layout = self.layout_of_local(frame, local)?;
670696
let ptr = self.allocate(local_layout, MemoryKind::Stack)?;
697+
// We don't have to validate as we can assume the local
698+
// was already valid for its type.
671699
self.write_value_to_mplace(value, ptr)?;
672700
let mplace = ptr.mplace;
673701
// Update the local

src/librustc_mir/interpret/terminator.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
287287

288288
let return_place = match dest {
289289
Some(place) => *place,
290-
None => Place::null(&self),
290+
None => Place::null(&self), // any access will error. good!
291291
};
292292
self.push_stack_frame(
293293
instance,
@@ -373,6 +373,20 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
373373
trace!("Caller has too many args over");
374374
return err!(FunctionArgCountMismatch);
375375
}
376+
// Don't forget to check the return type!
377+
if let Some(caller_ret) = dest {
378+
let callee_ret = self.eval_place(&mir::Place::Local(mir::RETURN_PLACE))?;
379+
if !Self::check_argument_compat(caller_ret.layout, callee_ret.layout) {
380+
return err!(FunctionRetMismatch(
381+
caller_ret.layout.ty, callee_ret.layout.ty
382+
));
383+
}
384+
} else {
385+
// FIXME: The caller thinks this function cannot return. How do
386+
// we verify that the callee agrees?
387+
// On the plus side, the the callee ever writes to its return place,
388+
// that will be detected as UB (because we set that to NULL above).
389+
}
376390
Ok(())
377391
})();
378392
match res {

0 commit comments

Comments
 (0)