Skip to content

Commit cd86199

Browse files
committed
Initial work on permissive provenance
1 parent c9039de commit cd86199

File tree

9 files changed

+203
-45
lines changed

9 files changed

+203
-45
lines changed

src/bin/miri.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ fn main() {
367367
miri_config.tag_raw = true;
368368
miri_config.check_number_validity = true;
369369
}
370+
"-Zmiri-permissive-provenance" => {
371+
miri_config.permissive_provenance = true;
372+
miri_config.tag_raw = true;
373+
}
370374
"-Zmiri-track-raw-pointers" => {
371375
eprintln!(
372376
"WARNING: -Zmiri-track-raw-pointers has been renamed to -Zmiri-tag-raw-pointers, the old name is deprecated."

src/eval.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ pub struct MiriConfig {
111111
pub panic_on_unsupported: bool,
112112
/// Which style to use for printing backtraces.
113113
pub backtrace_style: BacktraceStyle,
114+
/// Whether to allow "permissive provenance" rules. Enabling this means int2ptr casts return
115+
/// pointers with "wildcard" provenance that basically match that of all exposed pointers
116+
/// (and SB tags, if enabled).
117+
pub permissive_provenance: bool,
114118
/// Whether to enforce "strict provenance" rules. Enabling this means int2ptr casts return
115119
/// pointers with an invalid provenance, i.e., not valid for any memory access.
116120
pub strict_provenance: bool,
@@ -139,6 +143,7 @@ impl Default for MiriConfig {
139143
measureme_out: None,
140144
panic_on_unsupported: false,
141145
backtrace_style: BacktraceStyle::Short,
146+
permissive_provenance: false,
142147
strict_provenance: false,
143148
}
144149
}

src/helpers.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -771,8 +771,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
771771
fn mark_immutable(&mut self, mplace: &MemPlace<Tag>) {
772772
let this = self.eval_context_mut();
773773
// This got just allocated, so there definitely is a pointer here.
774-
this.alloc_mark_immutable(mplace.ptr.into_pointer_or_addr().unwrap().provenance.alloc_id)
775-
.unwrap();
774+
let provenance = mplace.ptr.into_pointer_or_addr().unwrap().provenance;
775+
776+
if let Tag::Concrete(concrete) = provenance {
777+
this.alloc_mark_immutable(concrete.alloc_id).unwrap();
778+
} else {
779+
bug!("Machine allocation that was just created should have concrete provenance");
780+
}
776781
}
777782
}
778783

src/intptrcast.rs

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::collections::hash_map::Entry;
44
use log::trace;
55
use rand::Rng;
66

7-
use rustc_data_structures::fx::FxHashMap;
7+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
88
use rustc_target::abi::{HasDataLayout, Size};
99

1010
use crate::*;
@@ -21,9 +21,16 @@ pub struct GlobalStateInner {
2121
/// they do not have an `AllocExtra`.
2222
/// This is the inverse of `int_to_ptr_map`.
2323
base_addr: FxHashMap<AllocId, u64>,
24+
/// Whether an allocation has been exposed or not. This cannot be put
25+
/// into `AllocExtra` for the same reason as `base_addr`.
26+
exposed: FxHashSet<AllocId>,
2427
/// This is used as a memory address when a new pointer is casted to an integer. It
2528
/// is always larger than any address that was previously made part of a block.
2629
next_base_addr: u64,
30+
/// Whether to allow "permissive provenance" rules. Enabling this means int2ptr casts return
31+
/// pointers with "wildcard" provenance that basically match that of all exposed pointers
32+
/// (and SB tags, if enabled).
33+
permissive_provenance: bool,
2734
/// Whether to enforce "strict provenance" rules. Enabling this means int2ptr casts return
2835
/// pointers with an invalid provenance, i.e., not valid for any memory access.
2936
strict_provenance: bool,
@@ -34,24 +41,32 @@ impl GlobalStateInner {
3441
GlobalStateInner {
3542
int_to_ptr_map: Vec::default(),
3643
base_addr: FxHashMap::default(),
44+
exposed: FxHashSet::default(),
3745
next_base_addr: STACK_ADDR,
46+
permissive_provenance: config.permissive_provenance,
3847
strict_provenance: config.strict_provenance,
3948
}
4049
}
4150
}
4251

4352
impl<'mir, 'tcx> GlobalStateInner {
44-
pub fn ptr_from_addr(addr: u64, ecx: &MiriEvalContext<'mir, 'tcx>) -> Pointer<Option<Tag>> {
45-
trace!("Casting 0x{:x} to a pointer", addr);
53+
// Returns the `AllocId` that corresponds to the specified addr,
54+
// or `None` if the addr is out of bounds
55+
fn alloc_id_from_addr(ecx: &MiriEvalContext<'mir, 'tcx>, addr: u64) -> Option<AllocId> {
4656
let global_state = ecx.machine.intptrcast.borrow();
4757

48-
if global_state.strict_provenance {
49-
return Pointer::new(None, Size::from_bytes(addr));
50-
}
51-
5258
let pos = global_state.int_to_ptr_map.binary_search_by_key(&addr, |(addr, _)| *addr);
53-
let alloc_id = match pos {
54-
Ok(pos) => Some(global_state.int_to_ptr_map[pos].1),
59+
60+
match pos {
61+
Ok(pos) => {
62+
let (_, alloc_id) = global_state.int_to_ptr_map[pos];
63+
64+
if !global_state.permissive_provenance || global_state.exposed.contains(&alloc_id) {
65+
Some(global_state.int_to_ptr_map[pos].1)
66+
} else {
67+
None
68+
}
69+
}
5570
Err(0) => None,
5671
Err(pos) => {
5772
// This is the largest of the adresses smaller than `int`,
@@ -60,24 +75,39 @@ impl<'mir, 'tcx> GlobalStateInner {
6075
// This never overflows because `addr >= glb`
6176
let offset = addr - glb;
6277
// If the offset exceeds the size of the allocation, don't use this `alloc_id`.
63-
if offset
64-
<= ecx
65-
.get_alloc_size_and_align(alloc_id, AllocCheck::MaybeDead)
66-
.unwrap()
67-
.0
68-
.bytes()
78+
79+
if (!global_state.permissive_provenance || global_state.exposed.contains(&alloc_id))
80+
&& offset
81+
<= ecx
82+
.get_alloc_size_and_align(alloc_id, AllocCheck::MaybeDead)
83+
.unwrap()
84+
.0
85+
.bytes()
6986
{
7087
Some(alloc_id)
7188
} else {
7289
None
7390
}
7491
}
75-
};
76-
// Pointers created from integers are untagged.
77-
Pointer::new(
78-
alloc_id.map(|alloc_id| Tag { alloc_id, sb: SbTag::Untagged }),
79-
Size::from_bytes(addr),
80-
)
92+
}
93+
}
94+
95+
pub fn expose_addr(ecx: &MiriEvalContext<'mir, 'tcx>, alloc_id: AllocId) {
96+
trace!("Exposing allocation id {:?}", alloc_id);
97+
98+
let mut global_state = ecx.machine.intptrcast.borrow_mut();
99+
global_state.exposed.insert(alloc_id);
100+
}
101+
102+
pub fn ptr_from_addr(ecx: &MiriEvalContext<'mir, 'tcx>, addr: u64) -> Pointer<Option<Tag>> {
103+
trace!("Casting 0x{:x} to a pointer", addr);
104+
let global_state = ecx.machine.intptrcast.borrow();
105+
106+
if addr == 0 || global_state.strict_provenance {
107+
return Pointer::new(None, Size::from_bytes(addr));
108+
}
109+
110+
Pointer::new(Some(Tag::Wildcard), Size::from_bytes(addr))
81111
}
82112

83113
fn alloc_base_addr(ecx: &MiriEvalContext<'mir, 'tcx>, alloc_id: AllocId) -> u64 {
@@ -128,22 +158,38 @@ impl<'mir, 'tcx> GlobalStateInner {
128158

129159
/// Convert a relative (tcx) pointer to an absolute address.
130160
pub fn rel_ptr_to_addr(ecx: &MiriEvalContext<'mir, 'tcx>, ptr: Pointer<AllocId>) -> u64 {
131-
let (alloc_id, offset) = ptr.into_parts(); // offset is relative (AllocId provenance)
161+
let (alloc_id, offset) = ptr.into_parts(); // offset is relative
132162
let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);
133163

134164
// Add offset with the right kind of pointer-overflowing arithmetic.
135165
let dl = ecx.data_layout();
136166
dl.overflowing_offset(base_addr, offset.bytes()).0
137167
}
138168

139-
pub fn abs_ptr_to_rel(ecx: &MiriEvalContext<'mir, 'tcx>, ptr: Pointer<Tag>) -> Size {
169+
pub fn abs_ptr_to_rel(
170+
ecx: &MiriEvalContext<'mir, 'tcx>,
171+
ptr: Pointer<Tag>,
172+
) -> Option<(AllocId, Size)> {
140173
let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance)
141-
let base_addr = GlobalStateInner::alloc_base_addr(ecx, tag.alloc_id);
174+
175+
let alloc_id = if let Tag::Concrete(concrete) = tag {
176+
concrete.alloc_id
177+
} else {
178+
match GlobalStateInner::alloc_id_from_addr(ecx, addr.bytes()) {
179+
Some(alloc_id) => alloc_id,
180+
None => return None,
181+
}
182+
};
183+
184+
let base_addr = GlobalStateInner::alloc_base_addr(ecx, alloc_id);
142185

143186
// Wrapping "addr - base_addr"
144187
let dl = ecx.data_layout();
145188
let neg_base_addr = (base_addr as i64).wrapping_neg();
146-
Size::from_bytes(dl.overflowing_signed_offset(addr.bytes(), neg_base_addr).0)
189+
Some((
190+
alloc_id,
191+
Size::from_bytes(dl.overflowing_signed_offset(addr.bytes(), neg_base_addr).0),
192+
))
147193
}
148194

149195
/// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ pub use crate::eval::{
6666
};
6767
pub use crate::helpers::EvalContextExt as HelpersEvalContextExt;
6868
pub use crate::machine::{
69-
AllocExtra, Evaluator, FrameData, MiriEvalContext, MiriEvalContextExt, MiriMemoryKind, Tag,
70-
NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
69+
AllocExtra, ConcreteTag, Evaluator, FrameData, MiriEvalContext, MiriEvalContextExt,
70+
MiriMemoryKind, Tag, NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
7171
};
7272
pub use crate::mono_hash_map::MonoHashMap;
7373
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;

src/machine.rs

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,22 @@ impl fmt::Display for MiriMemoryKind {
124124

125125
/// Pointer provenance (tag).
126126
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
127-
pub struct Tag {
127+
pub enum Tag {
128+
Concrete(ConcreteTag),
129+
Wildcard,
130+
}
131+
132+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
133+
pub struct ConcreteTag {
128134
pub alloc_id: AllocId,
129135
/// Stacked Borrows tag.
130136
pub sb: SbTag,
131137
}
132138

133139
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
134140
static_assert_size!(Pointer<Tag>, 24);
135-
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
136-
static_assert_size!(Pointer<Option<Tag>>, 24);
141+
// #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
142+
// static_assert_size!(Pointer<Option<Tag>>, 24);
137143
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
138144
static_assert_size!(ScalarMaybeUninit<Tag>, 32);
139145

@@ -148,17 +154,30 @@ impl Provenance for Tag {
148154
let (tag, addr) = ptr.into_parts(); // address is absolute
149155
write!(f, "0x{:x}", addr.bytes())?;
150156
// Forward `alternate` flag to `alloc_id` printing.
151-
if f.alternate() {
152-
write!(f, "[{:#?}]", tag.alloc_id)?;
153-
} else {
154-
write!(f, "[{:?}]", tag.alloc_id)?;
157+
158+
match tag {
159+
Tag::Concrete(tag) => {
160+
if f.alternate() {
161+
write!(f, "[{:#?}]", tag.alloc_id)?;
162+
} else {
163+
write!(f, "[{:?}]", tag.alloc_id)?;
164+
}
165+
// Print Stacked Borrows tag.
166+
write!(f, "{:?}", tag.sb)?;
167+
}
168+
Tag::Wildcard => {
169+
write!(f, "[Wildcard]")?;
170+
}
155171
}
156-
// Print Stacked Borrows tag.
157-
write!(f, "{:?}", tag.sb)
172+
173+
Ok(())
158174
}
159175

160-
fn get_alloc_id(self) -> AllocId {
161-
self.alloc_id
176+
fn get_alloc_id(self) -> Option<AllocId> {
177+
match self {
178+
Tag::Concrete(concrete) => Some(concrete.alloc_id),
179+
Tag::Wildcard => None,
180+
}
162181
}
163182
}
164183

@@ -594,25 +613,52 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
594613
} else {
595614
SbTag::Untagged
596615
};
597-
Pointer::new(Tag { alloc_id: ptr.provenance, sb: sb_tag }, Size::from_bytes(absolute_addr))
616+
Pointer::new(
617+
Tag::Concrete(ConcreteTag { alloc_id: ptr.provenance, sb: sb_tag }),
618+
Size::from_bytes(absolute_addr),
619+
)
598620
}
599621

600622
#[inline(always)]
601623
fn ptr_from_addr(
602624
ecx: &MiriEvalContext<'mir, 'tcx>,
603625
addr: u64,
604626
) -> Pointer<Option<Self::PointerTag>> {
605-
intptrcast::GlobalStateInner::ptr_from_addr(addr, ecx)
627+
intptrcast::GlobalStateInner::ptr_from_addr(ecx, addr)
628+
}
629+
630+
#[inline(always)]
631+
fn expose_ptr(
632+
ecx: &mut InterpCx<'mir, 'tcx, Self>,
633+
ptr: Pointer<Self::PointerTag>,
634+
) -> InterpResult<'tcx> {
635+
let (tag, _) = ptr.into_parts();
636+
637+
// We have a concrete pointer, so we don't need to reify it.
638+
if let Tag::Concrete(concrete) = tag {
639+
intptrcast::GlobalStateInner::expose_addr(ecx, concrete.alloc_id);
640+
}
641+
642+
// No need to do anything for wildcard pointers as
643+
// their provenances have already been previously exposed.
644+
Ok(())
606645
}
607646

608647
/// Convert a pointer with provenance into an allocation-offset pair,
609648
/// or a `None` with an absolute address if that conversion is not possible.
610649
fn ptr_get_alloc(
611650
ecx: &MiriEvalContext<'mir, 'tcx>,
612651
ptr: Pointer<Self::PointerTag>,
613-
) -> (AllocId, Size, Self::TagExtra) {
652+
) -> Option<(AllocId, Size, Self::TagExtra)> {
614653
let rel = intptrcast::GlobalStateInner::abs_ptr_to_rel(ecx, ptr);
615-
(ptr.provenance.alloc_id, rel, ptr.provenance.sb)
654+
655+
let (tag, _) = ptr.into_parts();
656+
let sb = match tag {
657+
Tag::Concrete(ConcreteTag { sb, .. }) => sb,
658+
Tag::Wildcard => SbTag::Untagged,
659+
};
660+
661+
rel.map(|(alloc_id, size)| (alloc_id, size, sb))
616662
}
617663

618664
#[inline(always)]

src/stacked_borrows.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,16 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
820820
this.reborrow(&place, size, kind, new_tag, protect)?;
821821

822822
// Adjust pointer.
823-
let new_place = place.map_provenance(|p| p.map(|t| Tag { sb: new_tag, ..t }));
823+
let new_place = place.map_provenance(|p| {
824+
p.map(|t| {
825+
// TODO: Fix this eventually
826+
if let Tag::Concrete(t) = t {
827+
Tag::Concrete(ConcreteTag { sb: new_tag, ..t })
828+
} else {
829+
t
830+
}
831+
})
832+
});
824833

825834
// Return new pointer.
826835
Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout))

tests/compile-fail/ptr_int_guess.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// compile-flags: -Zmiri-permissive-provenance -Zmiri-disable-stacked-borrows
2+
3+
fn main() {
4+
let x: i32 = 3;
5+
let x_ptr = &x as *const i32;
6+
7+
// TODO: switch this to addr() once we intrinsify it
8+
let x_usize: usize = unsafe { std::mem::transmute(x_ptr) };
9+
let ptr = x_usize as *const i32;
10+
assert_eq!(unsafe { *ptr }, 3); //~ ERROR Undefined Behavior: dereferencing pointer failed
11+
}

0 commit comments

Comments
 (0)