diff --git a/README.md b/README.md index 1de1c2e4c1..42ea9b8448 100644 --- a/README.md +++ b/README.md @@ -230,13 +230,20 @@ environment variable: * `-Zmiri-track-alloc-id=` shows a backtrace when the given allocation is being allocated or freed. This helps in debugging memory leaks and use after free bugs. +* `-Zmiri-track-call-id=` shows a backtrace when the given call id is + assigned to a stack frame. This helps in debugging UB related to Stacked + Borrows "protectors". * `-Zmiri-track-pointer-tag=` shows a backtrace when the given pointer tag is popped from a borrow stack (which is where the tag becomes invalid and any future use of it will error). This helps you in finding out why UB is happening and where in your code would be a good place to look for it. -* `-Zmiri-track-call-id=` shows a backtrace when the given call id is - assigned to a stack frame. This helps in debugging UB related to Stacked - Borrows "protectors". +* `-Zmiri-track-raw-pointers` makes Stacked Borrows track a pointer tag even for + raw pointers. This can make valid code fail to pass the checks, but also can + help identify latent aliasing issues in code that Miri accepts by default. You + can recognize false positives by "" occurring in the message -- this + indicates a pointer that was cast from an integer, so Miri was unable to track + this pointer. Make sure to use a non-Windows target with this flag, as the + Windows runtime makes use of integer-pointer casts. Some native rustc `-Z` flags are also very relevant for Miri: diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 5769590ad0..ef1429a350 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -207,6 +207,9 @@ fn main() { "-Zmiri-ignore-leaks" => { miri_config.ignore_leaks = true; } + "-Zmiri-track-raw-pointers" => { + miri_config.track_raw = true; + } "--" => { after_dashdash = true; } diff --git a/src/eval.rs b/src/eval.rs index e36a0019cd..54d06feec3 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -3,8 +3,6 @@ use std::convert::TryFrom; use std::ffi::OsStr; -use rand::rngs::StdRng; -use rand::SeedableRng; use log::info; use rustc_hir::def_id::DefId; @@ -48,6 +46,8 @@ pub struct MiriConfig { pub tracked_call_id: Option, /// The allocation id to report about. pub tracked_alloc_id: Option, + /// Whether to track raw pointers in stacked borrows. + pub track_raw: bool, } impl Default for MiriConfig { @@ -64,6 +64,7 @@ impl Default for MiriConfig { tracked_pointer_tag: None, tracked_call_id: None, tracked_alloc_id: None, + track_raw: false, } } } @@ -84,14 +85,7 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( rustc_span::source_map::DUMMY_SP, param_env, Evaluator::new(config.communicate, config.validate, layout_cx), - MemoryExtra::new( - StdRng::seed_from_u64(config.seed.unwrap_or(0)), - config.stacked_borrows, - config.tracked_pointer_tag, - config.tracked_call_id, - config.tracked_alloc_id, - config.check_alignment, - ), + MemoryExtra::new(&config), ); // Complete initialization. EnvVars::init(&mut ecx, config.excluded_env_vars)?; diff --git a/src/machine.rs b/src/machine.rs index 544f4667e8..e9f9298e56 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -10,6 +10,7 @@ use std::fmt; use log::trace; use rand::rngs::StdRng; +use rand::SeedableRng; use rustc_data_structures::fx::FxHashMap; use rustc_middle::{ @@ -132,16 +133,14 @@ pub struct MemoryExtra { } impl MemoryExtra { - pub fn new( - rng: StdRng, - stacked_borrows: bool, - tracked_pointer_tag: Option, - tracked_call_id: Option, - tracked_alloc_id: Option, - check_alignment: AlignmentCheck, - ) -> Self { - let stacked_borrows = if stacked_borrows { - Some(Rc::new(RefCell::new(stacked_borrows::GlobalState::new(tracked_pointer_tag, tracked_call_id)))) + pub fn new(config: &MiriConfig) -> Self { + let rng = StdRng::seed_from_u64(config.seed.unwrap_or(0)); + let stacked_borrows = if config.stacked_borrows { + Some(Rc::new(RefCell::new(stacked_borrows::GlobalState::new( + config.tracked_pointer_tag, + config.tracked_call_id, + config.track_raw, + )))) } else { None }; @@ -150,8 +149,8 @@ impl MemoryExtra { intptrcast: Default::default(), extern_statics: FxHashMap::default(), rng: RefCell::new(rng), - tracked_alloc_id, - check_alignment, + tracked_alloc_id: config.tracked_alloc_id, + check_alignment: config.check_alignment, } } diff --git a/src/range_map.rs b/src/range_map.rs index 16ad5fd7c2..607c830530 100644 --- a/src/range_map.rs +++ b/src/range_map.rs @@ -61,7 +61,9 @@ impl RangeMap { /// Provides read-only iteration over everything in the given range. This does /// *not* split items if they overlap with the edges. Do not use this to mutate /// through interior mutability. - pub fn iter<'a>(&'a self, offset: Size, len: Size) -> impl Iterator + 'a { + /// + /// The iterator also provides the offset of the given element. + pub fn iter<'a>(&'a self, offset: Size, len: Size) -> impl Iterator + 'a { let offset = offset.bytes(); let len = len.bytes(); // Compute a slice starting with the elements we care about. @@ -75,7 +77,7 @@ impl RangeMap { }; // The first offset that is not included any more. let end = offset + len; - slice.iter().take_while(move |elem| elem.range.start < end).map(|elem| &elem.data) + slice.iter().take_while(move |elem| elem.range.start < end).map(|elem| (Size::from_bytes(elem.range.start), &elem.data)) } pub fn iter_mut_all<'a>(&'a mut self) -> impl Iterator + 'a { @@ -112,11 +114,13 @@ impl RangeMap { /// this will split entries in the map that are only partially hit by the given range, /// to make sure that when they are mutated, the effect is constrained to the given range. /// Moreover, this will opportunistically merge neighbouring equal blocks. + /// + /// The iterator also provides the offset of the given element. pub fn iter_mut<'a>( &'a mut self, offset: Size, len: Size, - ) -> impl Iterator + 'a + ) -> impl Iterator + 'a where T: Clone + PartialEq, { @@ -197,7 +201,7 @@ impl RangeMap { // Now we yield the slice. `end` is inclusive. &mut self.v[first_idx..=end_idx] }; - slice.iter_mut().map(|elem| &mut elem.data) + slice.iter_mut().map(|elem| (Size::from_bytes(elem.range.start), &mut elem.data)) } } @@ -209,7 +213,7 @@ mod tests { fn to_vec(map: &RangeMap, offset: u64, len: u64) -> Vec { (offset..offset + len) .into_iter() - .map(|i| map.iter(Size::from_bytes(i), Size::from_bytes(1)).next().map(|&t| t).unwrap()) + .map(|i| map.iter(Size::from_bytes(i), Size::from_bytes(1)).next().map(|(_, &t)| t).unwrap()) .collect() } @@ -217,7 +221,7 @@ mod tests { fn basic_insert() { let mut map = RangeMap::::new(Size::from_bytes(20), -1); // Insert. - for x in map.iter_mut(Size::from_bytes(10), Size::from_bytes(1)) { + for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(1)) { *x = 42; } // Check. @@ -225,10 +229,10 @@ mod tests { assert_eq!(map.v.len(), 3); // Insert with size 0. - for x in map.iter_mut(Size::from_bytes(10), Size::from_bytes(0)) { + for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(0)) { *x = 19; } - for x in map.iter_mut(Size::from_bytes(11), Size::from_bytes(0)) { + for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(0)) { *x = 19; } assert_eq!(to_vec(&map, 10, 2), vec![42, -1]); @@ -238,16 +242,16 @@ mod tests { #[test] fn gaps() { let mut map = RangeMap::::new(Size::from_bytes(20), -1); - for x in map.iter_mut(Size::from_bytes(11), Size::from_bytes(1)) { + for (_, x) in map.iter_mut(Size::from_bytes(11), Size::from_bytes(1)) { *x = 42; } - for x in map.iter_mut(Size::from_bytes(15), Size::from_bytes(1)) { + for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(1)) { *x = 43; } assert_eq!(map.v.len(), 5); assert_eq!(to_vec(&map, 10, 10), vec![-1, 42, -1, -1, -1, 43, -1, -1, -1, -1]); - for x in map.iter_mut(Size::from_bytes(10), Size::from_bytes(10)) { + for (_, x) in map.iter_mut(Size::from_bytes(10), Size::from_bytes(10)) { if *x < 42 { *x = 23; } @@ -256,14 +260,14 @@ mod tests { assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 43, 23, 23, 23, 23]); assert_eq!(to_vec(&map, 13, 5), vec![23, 23, 43, 23, 23]); - for x in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) { + for (_, x) in map.iter_mut(Size::from_bytes(15), Size::from_bytes(5)) { *x = 19; } assert_eq!(map.v.len(), 6); assert_eq!(to_vec(&map, 10, 10), vec![23, 42, 23, 23, 23, 19, 19, 19, 19, 19]); // Should be seeing two blocks with 19. assert_eq!( - map.iter(Size::from_bytes(15), Size::from_bytes(2)).map(|&t| t).collect::>(), + map.iter(Size::from_bytes(15), Size::from_bytes(2)).map(|(_, &t)| t).collect::>(), vec![19, 19] ); diff --git a/src/stacked_borrows.rs b/src/stacked_borrows.rs index 257208056d..616950eb0a 100644 --- a/src/stacked_borrows.rs +++ b/src/stacked_borrows.rs @@ -108,6 +108,8 @@ pub struct GlobalState { tracked_pointer_tag: Option, /// The call id to trace tracked_call_id: Option, + /// Whether to track raw pointers. + track_raw: bool, } /// Memory extra state gives us interior mutable access to the global state. pub type MemoryExtra = Rc>; @@ -155,7 +157,7 @@ impl fmt::Display for RefKind { /// Utilities for initialization and ID generation impl GlobalState { - pub fn new(tracked_pointer_tag: Option, tracked_call_id: Option) -> Self { + pub fn new(tracked_pointer_tag: Option, tracked_call_id: Option, track_raw: bool) -> Self { GlobalState { next_ptr_id: NonZeroU64::new(1).unwrap(), base_ptr_ids: FxHashMap::default(), @@ -163,6 +165,7 @@ impl GlobalState { active_calls: FxHashSet::default(), tracked_pointer_tag, tracked_call_id, + track_raw, } } @@ -309,14 +312,14 @@ impl<'tcx> Stack { /// Test if a memory `access` using pointer tagged `tag` is granted. /// If yes, return the index of the item that granted it. - fn access(&mut self, access: AccessKind, tag: Tag, global: &GlobalState) -> InterpResult<'tcx> { + fn access(&mut self, access: AccessKind, ptr: Pointer, global: &GlobalState) -> InterpResult<'tcx> { // Two main steps: Find granting item, remove incompatible items above. // Step 1: Find granting item. - let granting_idx = self.find_granting(access, tag).ok_or_else(|| { + let granting_idx = self.find_granting(access, ptr.tag).ok_or_else(|| { err_sb_ub(format!( - "no item granting {} to tag {:?} found in borrow stack.", - access, tag + "no item granting {} to tag {:?} at {} found in borrow stack.", + access, ptr.tag, ptr.erase_tag(), )) })?; @@ -328,7 +331,7 @@ impl<'tcx> Stack { let first_incompatible_idx = self.find_first_write_incompatible(granting_idx); for item in self.borrows.drain(first_incompatible_idx..).rev() { trace!("access: popping item {:?}", item); - Stack::check_protector(&item, Some(tag), global)?; + Stack::check_protector(&item, Some(ptr.tag), global)?; } } else { // On a read, *disable* all `Unique` above the granting item. This ensures U2 for read accesses. @@ -343,7 +346,7 @@ impl<'tcx> Stack { let item = &mut self.borrows[idx]; if item.perm == Permission::Unique { trace!("access: disabling item {:?}", item); - Stack::check_protector(item, Some(tag), global)?; + Stack::check_protector(item, Some(ptr.tag), global)?; item.perm = Permission::Disabled; } } @@ -355,12 +358,12 @@ impl<'tcx> Stack { /// Deallocate a location: Like a write access, but also there must be no /// active protectors at all because we will remove all items. - fn dealloc(&mut self, tag: Tag, global: &GlobalState) -> InterpResult<'tcx> { + fn dealloc(&mut self, ptr: Pointer, global: &GlobalState) -> InterpResult<'tcx> { // Step 1: Find granting item. - self.find_granting(AccessKind::Write, tag).ok_or_else(|| { + self.find_granting(AccessKind::Write, ptr.tag).ok_or_else(|| { err_sb_ub(format!( - "no item granting write access for deallocation to tag {:?} found in borrow stack", - tag, + "no item granting write access for deallocation to tag {:?} at {} found in borrow stack", + ptr.tag, ptr.erase_tag(), )) })?; @@ -372,20 +375,20 @@ impl<'tcx> Stack { Ok(()) } - /// Derived a new pointer from one with the given tag. + /// Derive a new pointer from one with the given tag. /// `weak` controls whether this operation is weak or strong: weak granting does not act as /// an access, and they add the new item directly on top of the one it is derived /// from instead of all the way at the top of the stack. - fn grant(&mut self, derived_from: Tag, new: Item, global: &GlobalState) -> InterpResult<'tcx> { + fn grant(&mut self, derived_from: Pointer, new: Item, global: &GlobalState) -> InterpResult<'tcx> { // Figure out which access `perm` corresponds to. let access = if new.perm.grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read }; // Now we figure out which item grants our parent (`derived_from`) this kind of access. // We use that to determine where to put the new item. - let granting_idx = self.find_granting(access, derived_from) + let granting_idx = self.find_granting(access, derived_from.tag) .ok_or_else(|| err_sb_ub(format!( - "trying to reborrow for {:?}, but parent tag {:?} does not have an appropriate item in the borrow stack", - new.perm, derived_from, + "trying to reborrow for {:?} at {}, but parent tag {:?} does not have an appropriate item in the borrow stack", + new.perm, derived_from.erase_tag(), derived_from.tag, )))?; // Compute where to put the new item. @@ -443,12 +446,14 @@ impl<'tcx> Stacks { &self, ptr: Pointer, size: Size, - f: impl Fn(&mut Stack, &GlobalState) -> InterpResult<'tcx>, + f: impl Fn(Pointer, &mut Stack, &GlobalState) -> InterpResult<'tcx>, ) -> InterpResult<'tcx> { let global = self.global.borrow(); let mut stacks = self.stacks.borrow_mut(); - for stack in stacks.iter_mut(ptr.offset, size) { - f(stack, &*global)?; + for (offset, stack) in stacks.iter_mut(ptr.offset, size) { + let mut cur_ptr = ptr; + cur_ptr.offset = offset; + f(cur_ptr, stack, &*global)?; } Ok(()) } @@ -477,9 +482,12 @@ impl Stacks { // The base pointer is not unique, so the base permission is `SharedReadWrite`. MemoryKind::Machine(MiriMemoryKind::Global | MiriMemoryKind::ExternStatic | MiriMemoryKind::Tls | MiriMemoryKind::Env) => (extra.borrow_mut().global_base_ptr(id), Permission::SharedReadWrite), - // Everything else we handle entirely untagged for now. - // FIXME: experiment with more precise tracking. - _ => (Tag::Untagged, Permission::SharedReadWrite), + // Everything else we handle like raw pointers for now. + _ => { + let mut extra = extra.borrow_mut(); + let tag = if extra.track_raw { Tag::Tagged(extra.new_ptr()) } else { Tag::Untagged }; + (tag, Permission::SharedReadWrite) + } }; (Stacks::new(size, perm, tag, extra), tag) } @@ -487,19 +495,13 @@ impl Stacks { #[inline(always)] pub fn memory_read<'tcx>(&self, ptr: Pointer, size: Size) -> InterpResult<'tcx> { trace!("read access with tag {:?}: {:?}, size {}", ptr.tag, ptr.erase_tag(), size.bytes()); - self.for_each(ptr, size, |stack, global| { - stack.access(AccessKind::Read, ptr.tag, global)?; - Ok(()) - }) + self.for_each(ptr, size, |ptr, stack, global| stack.access(AccessKind::Read, ptr, global)) } #[inline(always)] pub fn memory_written<'tcx>(&mut self, ptr: Pointer, size: Size) -> InterpResult<'tcx> { trace!("write access with tag {:?}: {:?}, size {}", ptr.tag, ptr.erase_tag(), size.bytes()); - self.for_each(ptr, size, |stack, global| { - stack.access(AccessKind::Write, ptr.tag, global)?; - Ok(()) - }) + self.for_each(ptr, size, |ptr, stack, global| stack.access(AccessKind::Write, ptr, global)) } #[inline(always)] @@ -509,7 +511,7 @@ impl Stacks { size: Size, ) -> InterpResult<'tcx> { trace!("deallocation with tag {:?}: {:?}, size {}", ptr.tag, ptr.erase_tag(), size.bytes()); - self.for_each(ptr, size, |stack, global| stack.dealloc(ptr.tag, global)) + self.for_each(ptr, size, |ptr, stack, global| stack.dealloc(ptr, global)) } } @@ -561,14 +563,14 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx Permission::SharedReadWrite }; let item = Item { perm, tag: new_tag, protector }; - stacked_borrows.for_each(cur_ptr, size, |stack, global| { - stack.grant(cur_ptr.tag, item, global) + stacked_borrows.for_each(cur_ptr, size, |cur_ptr, stack, global| { + stack.grant(cur_ptr, item, global) }) }); } }; let item = Item { perm, tag: new_tag, protector }; - stacked_borrows.for_each(ptr, size, |stack, global| stack.grant(ptr.tag, item, global)) + stacked_borrows.for_each(ptr, size, |ptr, stack, global| stack.grant(ptr, item, global)) } /// Retags an indidual pointer, returning the retagged version. @@ -597,16 +599,14 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx } // Compute new borrow. - let new_tag = match kind { - // Give up tracking for raw pointers. - // FIXME: Experiment with more precise tracking. Blocked on `&raw` - // because `Rc::into_raw` currently creates intermediate references, - // breaking `Rc::from_raw`. - RefKind::Raw { .. } => Tag::Untagged, - // All other pointesr are properly tracked. - _ => Tag::Tagged( - this.memory.extra.stacked_borrows.as_ref().unwrap().borrow_mut().new_ptr(), - ), + let new_tag = { + let mut mem_extra = this.memory.extra.stacked_borrows.as_ref().unwrap().borrow_mut(); + match kind { + // Give up tracking for raw pointers. + RefKind::Raw { .. } if !mem_extra.track_raw => Tag::Untagged, + // All other pointers are properly tracked. + _ => Tag::Tagged(mem_extra.new_ptr()), + } }; // Reborrow. diff --git a/tests/compile-fail/stacked_borrows/raw_tracking.rs b/tests/compile-fail/stacked_borrows/raw_tracking.rs new file mode 100644 index 0000000000..b9ddee328f --- /dev/null +++ b/tests/compile-fail/stacked_borrows/raw_tracking.rs @@ -0,0 +1,13 @@ +// compile-flags: -Zmiri-track-raw-pointers +// ignore-windows (FIXME: tracking raw pointers does not work on Windows) +//! This demonstrates a provenance problem that requires tracking of raw pointers to be detected. + +fn main() { + let mut l = 13; + let raw1 = &mut l as *mut _; + let raw2 = &mut l as *mut _; // invalidates raw1 + // Without raw pointer tracking, Stacked Borrows cannot distinguish raw1 and raw2, and thus + // fails to realize that raw1 should not be used any more. + unsafe { *raw1 = 13; } //~ ERROR no item granting write access to tag + unsafe { *raw2 = 13; } +} diff --git a/tests/run-pass/vec.rs b/tests/run-pass/vec.rs index 5fea4a9147..ad6c5363ac 100644 --- a/tests/run-pass/vec.rs +++ b/tests/run-pass/vec.rs @@ -1,3 +1,5 @@ +// compile-flags: -Zmiri-track-raw-pointers +// ignore-windows (FIXME: tracking raw pointers does not work on Windows) // Gather all references from a mutable iterator and make sure Miri notices if // using them is dangerous. fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator) { diff --git a/tests/run-pass/vecdeque.rs b/tests/run-pass/vecdeque.rs index 34f32ee1d9..55b47f622f 100644 --- a/tests/run-pass/vecdeque.rs +++ b/tests/run-pass/vecdeque.rs @@ -1,3 +1,5 @@ +// compile-flags: -Zmiri-track-raw-pointers +// ignore-windows (FIXME: tracking raw pointers does not work on Windows) use std::collections::VecDeque; fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator) {