diff --git a/src/bin/miri.rs b/src/bin/miri.rs index be4776f459..f6007a3273 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -368,6 +368,11 @@ fn main() { miri_config.tag_raw = true; miri_config.check_number_validity = true; } + "-Zmiri-permissive-provenance" => { + miri_config.permissive_provenance = true; + miri_config.tag_raw = true; + miri_config.check_number_validity = true; + } "-Zmiri-track-raw-pointers" => { eprintln!( "WARNING: -Zmiri-track-raw-pointers has been renamed to -Zmiri-tag-raw-pointers, the old name is deprecated." diff --git a/src/eval.rs b/src/eval.rs index 4c006867e1..59d06125b7 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -112,6 +112,9 @@ pub struct MiriConfig { pub panic_on_unsupported: bool, /// Which style to use for printing backtraces. pub backtrace_style: BacktraceStyle, + /// Whether to enforce "permissive provenance" rules. Enabling this means int2ptr casts return + /// pointers with union provenance of all exposed pointers (and SB tags, if enabled). + pub permissive_provenance: bool, /// Whether to enforce "strict provenance" rules. Enabling this means int2ptr casts return /// pointers with an invalid provenance, i.e., not valid for any memory access. pub strict_provenance: bool, @@ -140,6 +143,7 @@ impl Default for MiriConfig { measureme_out: None, panic_on_unsupported: false, backtrace_style: BacktraceStyle::Short, + permissive_provenance: false, strict_provenance: false, } } diff --git a/src/intptrcast.rs b/src/intptrcast.rs index 5156de67e5..1fa68564c6 100644 --- a/src/intptrcast.rs +++ b/src/intptrcast.rs @@ -27,6 +27,9 @@ pub struct GlobalStateInner { /// This is used as a memory address when a new pointer is casted to an integer. It /// is always larger than any address that was previously made part of a block. next_base_addr: u64, + /// Whether to enforce "permissive provenance" rules. Enabling this means int2ptr casts return + /// pointers with union provenance of all exposed pointers (and SB tags, if enabled). + permissive_provenance: bool, /// Whether to enforce "strict provenance" rules. Enabling this means int2ptr casts return /// pointers with an invalid provenance, i.e., not valid for any memory access. strict_provenance: bool, @@ -39,6 +42,7 @@ impl GlobalStateInner { base_addr: FxHashMap::default(), exposed: FxHashSet::default(), next_base_addr: STACK_ADDR, + permissive_provenance: config.permissive_provenance, strict_provenance: config.strict_provenance, } } @@ -47,7 +51,7 @@ impl GlobalStateInner { impl<'mir, 'tcx> GlobalStateInner { // Returns the `AllocId` that corresponds to the specified addr, // or `None` if the addr is out of bounds - fn alloc_id_from_addr(addr: u64, ecx: &MiriEvalContext<'mir, 'tcx>) -> Option { + fn alloc_id_from_addr(ecx: &MiriEvalContext<'mir, 'tcx>, addr: u64) -> Option { let global_state = ecx.machine.intptrcast.borrow(); if addr == 0 { @@ -60,7 +64,7 @@ impl<'mir, 'tcx> GlobalStateInner { Ok(pos) => { let (_, alloc_id) = global_state.int_to_ptr_map[pos]; - if global_state.exposed.contains(&alloc_id) { + if !global_state.permissive_provenance || global_state.exposed.contains(&alloc_id) { Some(global_state.int_to_ptr_map[pos].1) } else { None @@ -75,7 +79,7 @@ impl<'mir, 'tcx> GlobalStateInner { let offset = addr - glb; // If the offset exceeds the size of the allocation, don't use this `alloc_id`. - if global_state.exposed.contains(&alloc_id) + if (!global_state.permissive_provenance || global_state.exposed.contains(&alloc_id)) && offset <= ecx .get_alloc_size_and_align(alloc_id, AllocCheck::MaybeDead) @@ -91,13 +95,9 @@ impl<'mir, 'tcx> GlobalStateInner { } } - pub fn expose_addr(ecx: &MiriEvalContext<'mir, 'tcx>, ptr: Pointer) { + pub fn expose_addr(ecx: &MiriEvalContext<'mir, 'tcx>, alloc_id: AllocId) { let mut global_state = ecx.machine.intptrcast.borrow_mut(); - let (tag, _) = ptr.into_parts(); - - if let machine::AllocType::Concrete(alloc_id) = tag.alloc_id { - global_state.exposed.insert(alloc_id); - } + global_state.exposed.insert(alloc_id); } pub fn abs_ptr_to_rel( @@ -109,7 +109,7 @@ impl<'mir, 'tcx> GlobalStateInner { let alloc_id = if let machine::AllocType::Concrete(alloc_id) = tag.alloc_id { alloc_id } else { - match GlobalStateInner::alloc_id_from_addr(addr.bytes(), ecx) { + match GlobalStateInner::alloc_id_from_addr(ecx, addr.bytes()) { Some(alloc_id) => alloc_id, None => return None, } @@ -135,11 +135,22 @@ impl<'mir, 'tcx> GlobalStateInner { return Pointer::new(None, Size::from_bytes(addr)); } - let alloc_id = GlobalStateInner::alloc_id_from_addr(addr, ecx); + let alloc_id = GlobalStateInner::alloc_id_from_addr(ecx, addr); // Pointers created from integers are untagged. + + let sb = if let Some(stacked_borrows) = &ecx.machine.stacked_borrows { + if global_state.permissive_provenance { + stacked_borrows.borrow_mut().wildcard_tag() + } else { + SbTag::Untagged + } + } else { + SbTag::Untagged + }; + Pointer::new( - alloc_id.map(|_| Tag { alloc_id: machine::AllocType::Casted, sb: SbTag::Untagged }), + alloc_id.map(|_| Tag { alloc_id: machine::AllocType::Casted, sb }), Size::from_bytes(addr), ) } diff --git a/src/machine.rs b/src/machine.rs index dcb506b41c..de9135e9c4 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -166,9 +166,6 @@ impl Provenance for Tag { /// Extra per-allocation data #[derive(Debug, Clone)] pub struct AllocExtra { - // TODO: this really doesn't need to be here, - // but we're forced to bodge it here for now - pub alloc_id: AllocId, /// Stacked Borrows state is only added if it is enabled. pub stacked_borrows: Option, /// Data race detection via the use of a vector-clock, @@ -579,7 +576,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { }; let alloc: Allocation = alloc.convert_tag_add_extra( &ecx.tcx, - AllocExtra { alloc_id: id, stacked_borrows: stacks, data_race: race_alloc }, + AllocExtra { stacked_borrows: stacks, data_race: race_alloc }, |ptr| Evaluator::tag_alloc_base_pointer(ecx, ptr), ); Cow::Owned(alloc) @@ -611,15 +608,29 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { /// Convert a pointer with provenance into an allocation-offset pair, /// or a `None` with an absolute address if that conversion is not possible. - fn ptr_get_alloc( + fn ptr_reify_alloc( ecx: &MiriEvalContext<'mir, 'tcx>, ptr: Pointer, - ) -> Option<(AllocId, Size)> { - intptrcast::GlobalStateInner::abs_ptr_to_rel(ecx, ptr) + ) -> Option<(AllocId, Size, Pointer)> { + let rel_ptr = intptrcast::GlobalStateInner::abs_ptr_to_rel(ecx, ptr); + rel_ptr.map(|(alloc_id, size)| { + let new_ptr = + ptr.map_provenance(|t| Tag { alloc_id: AllocType::Concrete(alloc_id), ..t }); + + (alloc_id, size, new_ptr) + }) } - fn expose_addr(ecx: &MiriEvalContext<'mir, 'tcx>, ptr: Pointer) { - intptrcast::GlobalStateInner::expose_addr(ecx, ptr) + fn expose_ptr(ecx: &InterpCx<'mir, 'tcx, Self>, ptr: Pointer) { + let (tag, _) = ptr.into_parts(); + + if let AllocType::Concrete(alloc_id) = tag.alloc_id { + intptrcast::GlobalStateInner::expose_addr(ecx, alloc_id); + + if let Some(stacked_borrows) = &ecx.machine.stacked_borrows { + stacked_borrows.borrow_mut().expose_ptr(tag.sb) + } + } } #[inline(always)] @@ -630,12 +641,18 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { tag: Tag, range: AllocRange, ) -> InterpResult<'tcx> { + let alloc_id = if let AllocType::Concrete(alloc_id) = tag.alloc_id { + alloc_id + } else { + bug!("`memory_read` called with non-concrete tag") + }; + if let Some(data_race) = &alloc_extra.data_race { - data_race.read(alloc_extra.alloc_id, range, machine.data_race.as_ref().unwrap())?; + data_race.read(alloc_id, range, machine.data_race.as_ref().unwrap())?; } if let Some(stacked_borrows) = &alloc_extra.stacked_borrows { stacked_borrows.memory_read( - alloc_extra.alloc_id, + alloc_id, tag.sb, range, machine.stacked_borrows.as_ref().unwrap(), @@ -653,12 +670,18 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { tag: Tag, range: AllocRange, ) -> InterpResult<'tcx> { + let alloc_id = if let AllocType::Concrete(alloc_id) = tag.alloc_id { + alloc_id + } else { + bug!("`memory_written` called with non-concrete tag") + }; + if let Some(data_race) = &mut alloc_extra.data_race { - data_race.write(alloc_extra.alloc_id, range, machine.data_race.as_mut().unwrap())?; + data_race.write(alloc_id, range, machine.data_race.as_mut().unwrap())?; } if let Some(stacked_borrows) = &mut alloc_extra.stacked_borrows { stacked_borrows.memory_written( - alloc_extra.alloc_id, + alloc_id, tag.sb, range, machine.stacked_borrows.as_mut().unwrap(), @@ -676,19 +699,21 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { tag: Tag, range: AllocRange, ) -> InterpResult<'tcx> { - if Some(alloc_extra.alloc_id) == machine.tracked_alloc_id { - register_diagnostic(NonHaltingDiagnostic::FreedAlloc(alloc_extra.alloc_id)); + let alloc_id = if let AllocType::Concrete(alloc_id) = tag.alloc_id { + alloc_id + } else { + bug!("`memory_deallocated` called with non-concrete tag") + }; + + if Some(alloc_id) == machine.tracked_alloc_id { + register_diagnostic(NonHaltingDiagnostic::FreedAlloc(alloc_id)); } if let Some(data_race) = &mut alloc_extra.data_race { - data_race.deallocate( - alloc_extra.alloc_id, - range, - machine.data_race.as_mut().unwrap(), - )?; + data_race.deallocate(alloc_id, range, machine.data_race.as_mut().unwrap())?; } if let Some(stacked_borrows) = &mut alloc_extra.stacked_borrows { stacked_borrows.memory_deallocated( - alloc_extra.alloc_id, + alloc_id, tag.sb, range, machine.stacked_borrows.as_mut().unwrap(), diff --git a/src/stacked_borrows.rs b/src/stacked_borrows.rs index a365d90998..c92112d4d8 100644 --- a/src/stacked_borrows.rs +++ b/src/stacked_borrows.rs @@ -26,6 +26,7 @@ pub type AllocExtra = Stacks; #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub enum SbTag { Tagged(PtrId), + Wildcard(PtrId), Untagged, } @@ -33,6 +34,7 @@ impl fmt::Debug for SbTag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { SbTag::Tagged(id) => write!(f, "<{}>", id), + SbTag::Wildcard(id) => write!(f, "", id), SbTag::Untagged => write!(f, ""), } } @@ -82,11 +84,15 @@ pub struct Stack { /// * Above a `SharedReadOnly` there can only be more `SharedReadOnly`. /// * Except for `Untagged`, no tag occurs in the stack more than once. borrows: Vec, + /// Tags that should be treated as wildcards. + /// This is used for reborrows of pointers with wildcard tags. + treated_as_wildcard: FxHashSet, } /// Extra per-allocation state. #[derive(Clone, Debug)] pub struct Stacks { + /// RangeMap of all current SB stacks // Even reading memory can have effects on the stack, so we need a `RefCell` here. stacks: RefCell>, } @@ -100,6 +106,8 @@ pub struct GlobalStateInner { /// The base tag is the one used for the initial pointer. /// We need this in a separate table to handle cyclic statics. base_ptr_ids: FxHashMap, + /// Hashset of all pointers that have been exposed + exposed_tags: FxHashSet, /// Next unused call ID (for protectors). next_call_id: CallId, /// Those call IDs corresponding to functions that are still running. @@ -165,6 +173,7 @@ impl GlobalStateInner { GlobalStateInner { next_ptr_id: NonZeroU64::new(1).unwrap(), base_ptr_ids: FxHashMap::default(), + exposed_tags: FxHashSet::default(), next_call_id: NonZeroU64::new(1).unwrap(), active_calls: FxHashSet::default(), tracked_pointer_tag, @@ -210,6 +219,10 @@ impl GlobalStateInner { }) } + pub fn wildcard_tag(&mut self) -> SbTag { + SbTag::Wildcard(self.new_ptr()) + } + pub fn base_tag_untagged(&mut self, id: AllocId) -> SbTag { trace!("New allocation {:?} has no base tag (untagged)", id); let tag = SbTag::Untagged; @@ -217,6 +230,16 @@ impl GlobalStateInner { self.base_ptr_ids.try_insert(id, tag).unwrap(); tag } + + pub fn expose_ptr<'tcx>(&mut self, tag: SbTag) { + trace!("pointer exposed with tag {:?}", tag,); + + if let SbTag::Tagged(_) = tag { + self.exposed_tags.insert(tag); + } else if self.tag_raw && tag == SbTag::Untagged { + bug!("expose_ptr called on invalid tag"); + } + } } /// Error reporting @@ -260,18 +283,30 @@ impl Permission { impl<'tcx> Stack { /// Find the item granting the given kind of access to the given tag, and return where /// it is on the stack. - fn find_granting(&self, access: AccessKind, tag: SbTag) -> Option { + fn find_granting( + &self, + access: AccessKind, + tag: SbTag, + global: &GlobalStateInner, + ) -> Option { + let is_wildcard = + matches!(tag, SbTag::Wildcard(_)) || self.treated_as_wildcard.contains(&tag); + self.borrows .iter() .enumerate() // we also need to know *where* in the stack .rev() // search top-to-bottom // Return permission of first item that grants access. // We require a permission with the right tag, ensuring U3 and F3. - .find_map( - |(idx, item)| { - if tag == item.tag && item.perm.grants(access) { Some(idx) } else { None } - }, - ) + .find_map(|(idx, item)| { + // a wildcard tag allows access to all exposed tags + let wildcard_match = is_wildcard && global.exposed_tags.contains(&item.tag); + if (wildcard_match || tag == item.tag) && item.perm.grants(access) { + Some(idx) + } else { + None + } + }) } /// Find the first write-incompatible item above the given one -- @@ -354,7 +389,7 @@ impl<'tcx> Stack { // Step 1: Find granting item. let granting_idx = self - .find_granting(access, tag) + .find_granting(access, tag, global) .ok_or_else(|| self.access_error(access, tag, alloc_id, range, offset))?; // Step 2: Remove incompatible items above them. Make sure we do not remove protected @@ -399,7 +434,7 @@ impl<'tcx> Stack { global: &GlobalStateInner, ) -> InterpResult<'tcx> { // Step 1: Find granting item. - self.find_granting(AccessKind::Write, tag).ok_or_else(|| { + self.find_granting(AccessKind::Write, tag, global).ok_or_else(|| { err_sb_ub(format!( "no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack", tag, dbg_ptr, @@ -433,7 +468,7 @@ impl<'tcx> Stack { // 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) + .find_granting(access, derived_from, global) .ok_or_else(|| self.grant_error(derived_from, new, alloc_id, alloc_range, offset))?; // Compute where to put the new item. @@ -471,6 +506,10 @@ impl<'tcx> Stack { self.borrows.insert(new_idx, new); } + if matches!(derived_from, SbTag::Wildcard(_)) && new.perm == Permission::SharedReadWrite { + self.treated_as_wildcard.insert(new.tag); + } + Ok(()) } @@ -547,7 +586,7 @@ impl<'tcx> Stacks { /// Creates new stack with initial tag. fn new(size: Size, perm: Permission, tag: SbTag) -> Self { let item = Item { perm, tag, protector: None }; - let stack = Stack { borrows: vec![item] }; + let stack = Stack { borrows: vec![item], treated_as_wildcard: FxHashSet::default() }; Stacks { stacks: RefCell::new(RangeMap::new(size, stack)) } } @@ -702,6 +741,18 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx ); return Ok(()); } + if matches!(new_tag, SbTag::Wildcard(_)) { + // Nothing to do for wildcard tags. + trace!( + "reborrow of wildcard: {} reference {:?} derived from {:?} (pointee {})", + kind, + new_tag, + place.ptr, + place.layout.ty, + ); + return Ok(()); + } + let (alloc_id, base_offset, ptr) = this.ptr_get_alloc_id(place.ptr)?; let orig_tag = ptr.provenance.sb; @@ -787,7 +838,6 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx } /// Retags an indidual pointer, returning the retagged version. - /// `mutbl` can be `None` to make this a raw pointer. fn retag_reference( &mut self, val: &ImmTy<'tcx, Tag>,