1 use std::cell::RefCell;
2 use std::collections::hash_map::Entry;
7 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8 use rustc_target::abi::{HasDataLayout, Size};
12 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
13 pub enum ProvenanceMode {
14 /// Int2ptr casts return pointers with "wildcard" provenance
15 /// that basically matches that of all exposed pointers
16 /// (and SB tags, if enabled).
18 /// Int2ptr casts return pointers with an invalid provenance,
19 /// i.e., not valid for any memory access.
21 /// Int2ptr casts determine the allocation they point to at cast time.
22 /// All allocations are considered exposed.
26 pub type GlobalState = RefCell<GlobalStateInner>;
28 #[derive(Clone, Debug)]
29 pub struct GlobalStateInner {
30 /// This is used as a map between the address of each allocation and its `AllocId`.
31 /// It is always sorted
32 int_to_ptr_map: Vec<(u64, AllocId)>,
33 /// The base address for each allocation. We cannot put that into
34 /// `AllocExtra` because function pointers also have a base address, and
35 /// they do not have an `AllocExtra`.
36 /// This is the inverse of `int_to_ptr_map`.
37 base_addr: FxHashMap<AllocId, u64>,
38 /// Whether an allocation has been exposed or not. This cannot be put
39 /// into `AllocExtra` for the same reason as `base_addr`.
40 exposed: FxHashSet<AllocId>,
41 /// This is used as a memory address when a new pointer is casted to an integer. It
42 /// is always larger than any address that was previously made part of a block.
44 /// The provenance to use for int2ptr casts
45 provenance_mode: ProvenanceMode,
48 impl GlobalStateInner {
49 pub fn new(config: &MiriConfig) -> Self {
51 int_to_ptr_map: Vec::default(),
52 base_addr: FxHashMap::default(),
53 exposed: FxHashSet::default(),
54 next_base_addr: STACK_ADDR,
55 provenance_mode: config.provenance_mode,
60 impl<'mir, 'tcx> GlobalStateInner {
61 // Returns the exposed `AllocId` that corresponds to the specified addr,
62 // or `None` if the addr is out of bounds
63 fn alloc_id_from_addr(ecx: &MiriEvalContext<'mir, 'tcx>, addr: u64) -> Option<AllocId> {
64 let global_state = ecx.machine.intptrcast.borrow();
65 assert!(global_state.provenance_mode != ProvenanceMode::Strict);
67 let pos = global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr);
69 let alloc_id = match pos {
70 Ok(pos) => Some(global_state.int_to_ptr_map[pos].1),
73 // This is the largest of the adresses smaller than `int`,
74 // i.e. the greatest lower bound (glb)
75 let (glb, alloc_id) = global_state.int_to_ptr_map[pos - 1];
76 // This never overflows because `addr >= glb`
77 let offset = addr - glb;
78 // If the offset exceeds the size of the allocation, don't use this `alloc_id`.
82 .get_alloc_size_and_align(alloc_id, AllocCheck::MaybeDead)
94 // In legacy mode, we consider all allocations exposed.
95 if global_state.provenance_mode == ProvenanceMode::Legacy
96 || global_state.exposed.contains(&alloc_id)
104 pub fn expose_addr(ecx: &MiriEvalContext<'mir, 'tcx>, alloc_id: AllocId) {
105 trace!("Exposing allocation id {:?}", alloc_id);
107 let mut global_state = ecx.machine.intptrcast.borrow_mut();
108 // In legacy and strict mode, we don't need this, so we can save some cycles
109 // by not tracking it.
110 if global_state.provenance_mode == ProvenanceMode::Permissive {
111 global_state.exposed.insert(alloc_id);
115 pub fn ptr_from_addr_transmute(
116 ecx: &MiriEvalContext<'mir, 'tcx>,
118 ) -> Pointer<Option<Tag>> {
119 trace!("Transmuting 0x{:x} to a pointer", addr);
121 if ecx.machine.allow_ptr_int_transmute {
122 // When we allow transmutes, treat them like casts.
123 Self::ptr_from_addr_cast(ecx, addr)
125 // We consider transmuted pointers to be "invalid" (`None` provenance).
126 Pointer::new(None, Size::from_bytes(addr))
130 pub fn ptr_from_addr_cast(
131 ecx: &MiriEvalContext<'mir, 'tcx>,
133 ) -> Pointer<Option<Tag>> {
134 trace!("Casting 0x{:x} to a pointer", addr);
136 let global_state = ecx.machine.intptrcast.borrow();
138 match global_state.provenance_mode {
139 ProvenanceMode::Legacy => {
140 // Determine the allocation this points to at cast time.
141 let alloc_id = Self::alloc_id_from_addr(ecx, addr);
143 alloc_id.map(|alloc_id| {
144 Tag::Concrete(ConcreteTag { alloc_id, sb: SbTag::Untagged })
146 Size::from_bytes(addr),
149 ProvenanceMode::Strict => {
150 // We don't support int2ptr casts in this mode (i.e., we treat them like
152 Pointer::new(None, Size::from_bytes(addr))
154 ProvenanceMode::Permissive => {
155 // This is how wildcard pointers are born.
156 Pointer::new(Some(Tag::Wildcard), Size::from_bytes(addr))
161 fn alloc_base_addr(ecx: &MiriEvalContext<'mir, 'tcx>, alloc_id: AllocId) -> u64 {
162 let mut global_state = ecx.machine.intptrcast.borrow_mut();
163 let global_state = &mut *global_state;
165 match global_state.base_addr.entry(alloc_id) {
166 Entry::Occupied(entry) => *entry.get(),
167 Entry::Vacant(entry) => {
168 // There is nothing wrong with a raw pointer being cast to an integer only after
169 // it became dangling. Hence `MaybeDead`.
171 ecx.get_alloc_size_and_align(alloc_id, AllocCheck::MaybeDead).unwrap();
173 // This allocation does not have a base address yet, pick one.
174 // Leave some space to the previous allocation, to give it some chance to be less aligned.
176 let mut rng = ecx.machine.rng.borrow_mut();
177 // This means that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
180 // From next_base_addr + slack, round up to adjust for alignment.
181 let base_addr = global_state.next_base_addr.checked_add(slack).unwrap();
182 let base_addr = Self::align_addr(base_addr, align.bytes());
183 entry.insert(base_addr);
185 "Assigning base address {:#x} to allocation {:?} (size: {}, align: {}, slack: {})",
193 // Remember next base address. Leave a gap of at least 1 to avoid two zero-sized allocations
194 // having the same base address, and to avoid ambiguous provenance for the address between two
195 // allocations (also see https://github.com/rust-lang/unsafe-code-guidelines/issues/313).
196 let size_plus_1 = size.bytes().checked_add(1).unwrap();
197 global_state.next_base_addr = base_addr.checked_add(size_plus_1).unwrap();
198 // Given that `next_base_addr` increases in each allocation, pushing the
199 // corresponding tuple keeps `int_to_ptr_map` sorted
200 global_state.int_to_ptr_map.push((base_addr, alloc_id));
207 /// Convert a relative (tcx) pointer to an absolute address.
208 pub fn rel_ptr_to_addr(ecx: &MiriEvalContext<'mir, 'tcx>, ptr: Pointer<AllocId>) -> u64 {
209 let (alloc_id, offset) = ptr.into_parts(); // offset is relative (AllocId provenance)
210 let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);
212 // Add offset with the right kind of pointer-overflowing arithmetic.
213 let dl = ecx.data_layout();
214 dl.overflowing_offset(base_addr, offset.bytes()).0
217 pub fn abs_ptr_to_rel(
218 ecx: &MiriEvalContext<'mir, 'tcx>,
220 ) -> Option<(AllocId, Size)> {
221 let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance)
223 let alloc_id = if let Tag::Concrete(concrete) = tag {
226 // A wildcard pointer.
227 assert_eq!(ecx.machine.intptrcast.borrow().provenance_mode, ProvenanceMode::Permissive);
228 GlobalStateInner::alloc_id_from_addr(ecx, addr.bytes())?
231 let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);
233 // Wrapping "addr - base_addr"
234 let dl = ecx.data_layout();
235 let neg_base_addr = (base_addr as i64).wrapping_neg();
238 Size::from_bytes(dl.overflowing_signed_offset(addr.bytes(), neg_base_addr).0),
242 /// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
243 /// of `align` that is larger or equal to `addr`
244 fn align_addr(addr: u64, align: u64) -> u64 {
247 rem => addr.checked_add(align).unwrap() - rem,
257 fn test_align_addr() {
258 assert_eq!(GlobalStateInner::align_addr(37, 4), 40);
259 assert_eq!(GlobalStateInner::align_addr(44, 4), 44);