Skip to content

Commit cd16e5e

Browse files
committed
remove allocations from int_to_ptr_map when they get freed
1 parent 4c2024b commit cd16e5e

File tree

2 files changed

+31
-16
lines changed

2 files changed

+31
-16
lines changed

src/intptrcast.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ pub type GlobalState = RefCell<GlobalStateInner>;
2626

2727
#[derive(Clone, Debug)]
2828
pub struct GlobalStateInner {
29-
/// This is used as a map between the address of each allocation and its `AllocId`.
30-
/// It is always sorted
29+
/// This is used as a map between the address of each allocation and its `AllocId`. It is always
30+
/// sorted. We cannot use a `HashMap` since we can be given an address that is offset from the
31+
/// base address, and we need to find the `AllocId` it belongs to.
32+
/// This is not the *full* inverse of `base_addr`; dead allocations have been removed.
3133
int_to_ptr_map: Vec<(u64, AllocId)>,
3234
/// The base address for each allocation. We cannot put that into
3335
/// `AllocExtra` because function pointers also have a base address, and
@@ -102,18 +104,14 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
102104
}
103105
}?;
104106

105-
// We only use this provenance if it has been exposed, *and* is still live.
107+
// We only use this provenance if it has been exposede.
106108
if global_state.exposed.contains(&alloc_id) {
107-
let (_size, _align, kind) = ecx.get_alloc_info(alloc_id);
108-
match kind {
109-
AllocKind::LiveData | AllocKind::Function | AllocKind::VTable => {
110-
return Some(alloc_id);
111-
}
112-
AllocKind::Dead => {}
113-
}
109+
// This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed.
110+
debug_assert!(!matches!(ecx.get_alloc_info(alloc_id).2, AllocKind::Dead));
111+
Some(alloc_id)
112+
} else {
113+
None
114114
}
115-
116-
None
117115
}
118116

119117
fn addr_from_alloc_id(&self, alloc_id: AllocId) -> InterpResult<'tcx, u64> {
@@ -124,9 +122,12 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
124122
Ok(match global_state.base_addr.entry(alloc_id) {
125123
Entry::Occupied(entry) => *entry.get(),
126124
Entry::Vacant(entry) => {
127-
// There is nothing wrong with a raw pointer being cast to an integer only after
128-
// it became dangling. Hence we allow dead allocations.
129-
let (size, align, _kind) = ecx.get_alloc_info(alloc_id);
125+
let (size, align, kind) = ecx.get_alloc_info(alloc_id);
126+
// This is only called just after allocation, and when adjusting `tcx` pointers. So
127+
// the allocation can never be dead here. This also ensures that we never re-assign
128+
// an address to an allocation that previously had an address, but then was freed
129+
// and the address information was removed.
130+
assert!(!matches!(kind, AllocKind::Dead));
130131

131132
// This allocation does not have a base address yet, pick one.
132133
// Leave some space to the previous allocation, to give it some chance to be less aligned.
@@ -162,6 +163,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
162163
if global_state.next_base_addr > ecx.target_usize_max() {
163164
throw_exhaust!(AddressSpaceFull);
164165
}
166+
// Also maintain the opposite mapping in `int_to_ptr_map`.
165167
// Given that `next_base_addr` increases in each allocation, pushing the
166168
// corresponding tuple keeps `int_to_ptr_map` sorted
167169
global_state.int_to_ptr_map.push((base_addr, alloc_id));
@@ -246,7 +248,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
246248
};
247249

248250
// This cannot fail: since we already have a pointer with that provenance, rel_ptr_to_addr
249-
// must have been called in the past.
251+
// must have been called in the past, so we can just look up the address in the map.
250252
let base_addr = ecx.addr_from_alloc_id(alloc_id).unwrap();
251253

252254
// Wrapping "addr - base_addr"
@@ -260,6 +262,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
260262
}
261263
}
262264

265+
impl GlobalStateInner {
266+
pub fn free_alloc_id(&mut self, dead_id: AllocId) {
267+
// We can *not* remove this from `base_addr`, since `addr_from_alloc_id` is called on each
268+
// attempt at a memory access to determine the allocation ID and offset -- and there can
269+
// still be pointers with `dead_id` that one can attempt to use for a memory access.
270+
// However, we *can* remove it from `int_to_ptr_map`, since any wildcard pointers that exist
271+
// can no longer actually be accessing that address. This ensures `alloc_id_from_addr` never
272+
// returns a dead allocation.
273+
self.int_to_ptr_map.retain(|&(_, id)| id != dead_id);
274+
}
275+
}
276+
263277
#[cfg(test)]
264278
mod tests {
265279
use super::*;

src/machine.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
12521252
{
12531253
*deallocated_at = Some(machine.current_span());
12541254
}
1255+
machine.intptrcast.get_mut().free_alloc_id(alloc_id);
12551256
Ok(())
12561257
}
12571258

0 commit comments

Comments
 (0)