Skip to content

Commit 30d72f5

Browse files
committed
Initial work on permissive provenance
1 parent 19ef764 commit 30d72f5

File tree

9 files changed

+195
-45
lines changed

9 files changed

+195
-45
lines changed

src/bin/miri.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,10 @@ fn main() {
388388
miri_config.tag_raw = true;
389389
miri_config.check_number_validity = true;
390390
}
391+
"-Zmiri-permissive-provenance" => {
392+
miri_config.permissive_provenance = true;
393+
miri_config.tag_raw = true;
394+
}
391395
"-Zmiri-mute-stdout-stderr" => {
392396
miri_config.mute_stdout_stderr = true;
393397
}

src/eval.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ pub struct MiriConfig {
113113
pub panic_on_unsupported: bool,
114114
/// Which style to use for printing backtraces.
115115
pub backtrace_style: BacktraceStyle,
116+
/// Whether to allow "permissive provenance" rules. Enabling this means int2ptr casts return
117+
/// pointers with "wildcard" provenance that basically match that of all exposed pointers
118+
/// (and SB tags, if enabled).
119+
pub permissive_provenance: bool,
116120
/// Whether to enforce "strict provenance" rules. Enabling this means int2ptr casts return
117121
/// pointers with an invalid provenance, i.e., not valid for any memory access.
118122
pub strict_provenance: bool,
@@ -144,6 +148,7 @@ impl Default for MiriConfig {
144148
measureme_out: None,
145149
panic_on_unsupported: false,
146150
backtrace_style: BacktraceStyle::Short,
151+
permissive_provenance: false,
147152
strict_provenance: false,
148153
mute_stdout_stderr: false,
149154
}

src/helpers.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
787787
fn mark_immutable(&mut self, mplace: &MemPlace<Tag>) {
788788
let this = self.eval_context_mut();
789789
// This got just allocated, so there definitely is a pointer here.
790-
this.alloc_mark_immutable(mplace.ptr.into_pointer_or_addr().unwrap().provenance.alloc_id)
791-
.unwrap();
790+
let provenance = mplace.ptr.into_pointer_or_addr().unwrap().provenance;
791+
792+
if let Tag::Concrete(concrete) = provenance {
793+
this.alloc_mark_immutable(concrete.alloc_id).unwrap();
794+
} else {
795+
bug!("Machine allocation that was just created should have concrete provenance");
796+
}
792797
}
793798

794799
fn item_link_name(&self, def_id: DefId) -> Symbol {

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
@@ -79,8 +79,8 @@ pub use crate::eval::{
7979
};
8080
pub use crate::helpers::EvalContextExt as HelpersEvalContextExt;
8181
pub use crate::machine::{
82-
AllocExtra, Evaluator, FrameData, MiriEvalContext, MiriEvalContextExt, MiriMemoryKind, Tag,
83-
NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
82+
AllocExtra, ConcreteTag, Evaluator, FrameData, MiriEvalContext, MiriEvalContextExt,
83+
MiriMemoryKind, Tag, NUM_CPUS, PAGE_SIZE, STACK_ADDR, STACK_SIZE,
8484
};
8585
pub use crate::mono_hash_map::MonoHashMap;
8686
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;

src/machine.rs

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

127127
/// Pointer provenance (tag).
128128
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
129-
pub struct Tag {
129+
pub enum Tag {
130+
Concrete(ConcreteTag),
131+
Wildcard,
132+
}
133+
134+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
135+
pub struct ConcreteTag {
130136
pub alloc_id: AllocId,
131137
/// Stacked Borrows tag.
132138
pub sb: SbTag,
133139
}
134140

135141
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
136142
static_assert_size!(Pointer<Tag>, 24);
137-
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
138-
static_assert_size!(Pointer<Option<Tag>>, 24);
143+
// #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
144+
// static_assert_size!(Pointer<Option<Tag>>, 24);
139145
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
140146
static_assert_size!(ScalarMaybeUninit<Tag>, 32);
141147

@@ -150,17 +156,30 @@ impl Provenance for Tag {
150156
let (tag, addr) = ptr.into_parts(); // address is absolute
151157
write!(f, "0x{:x}", addr.bytes())?;
152158
// Forward `alternate` flag to `alloc_id` printing.
153-
if f.alternate() {
154-
write!(f, "[{:#?}]", tag.alloc_id)?;
155-
} else {
156-
write!(f, "[{:?}]", tag.alloc_id)?;
159+
160+
match tag {
161+
Tag::Concrete(tag) => {
162+
if f.alternate() {
163+
write!(f, "[{:#?}]", tag.alloc_id)?;
164+
} else {
165+
write!(f, "[{:?}]", tag.alloc_id)?;
166+
}
167+
// Print Stacked Borrows tag.
168+
write!(f, "{:?}", tag.sb)?;
169+
}
170+
Tag::Wildcard => {
171+
write!(f, "[Wildcard]")?;
172+
}
157173
}
158-
// Print Stacked Borrows tag.
159-
write!(f, "{:?}", tag.sb)
174+
175+
Ok(())
160176
}
161177

162178
fn get_alloc_id(self) -> Option<AllocId> {
163-
Some(self.alloc_id)
179+
match self {
180+
Tag::Concrete(concrete) => Some(concrete.alloc_id),
181+
Tag::Wildcard => None,
182+
}
164183
}
165184
}
166185

@@ -604,15 +623,18 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
604623
} else {
605624
SbTag::Untagged
606625
};
607-
Pointer::new(Tag { alloc_id: ptr.provenance, sb: sb_tag }, Size::from_bytes(absolute_addr))
626+
Pointer::new(
627+
Tag::Concrete(ConcreteTag { alloc_id: ptr.provenance, sb: sb_tag }),
628+
Size::from_bytes(absolute_addr),
629+
)
608630
}
609631

610632
#[inline(always)]
611633
fn ptr_from_addr_cast(
612634
ecx: &MiriEvalContext<'mir, 'tcx>,
613635
addr: u64,
614636
) -> Pointer<Option<Self::PointerTag>> {
615-
intptrcast::GlobalStateInner::ptr_from_addr(addr, ecx)
637+
intptrcast::GlobalStateInner::ptr_from_addr(ecx, addr)
616638
}
617639

618640
#[inline(always)]
@@ -625,9 +647,18 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
625647

626648
#[inline(always)]
627649
fn expose_ptr(
628-
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
629-
_ptr: Pointer<Self::PointerTag>,
650+
ecx: &mut InterpCx<'mir, 'tcx, Self>,
651+
ptr: Pointer<Self::PointerTag>,
630652
) -> InterpResult<'tcx> {
653+
let (tag, _) = ptr.into_parts();
654+
655+
// We have a concrete pointer, so we don't need to reify it.
656+
if let Tag::Concrete(concrete) = tag {
657+
intptrcast::GlobalStateInner::expose_addr(ecx, concrete.alloc_id);
658+
}
659+
660+
// No need to do anything for wildcard pointers as
661+
// their provenances have already been previously exposed.
631662
Ok(())
632663
}
633664

@@ -638,7 +669,14 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
638669
ptr: Pointer<Self::PointerTag>,
639670
) -> Option<(AllocId, Size, Self::TagExtra)> {
640671
let rel = intptrcast::GlobalStateInner::abs_ptr_to_rel(ecx, ptr);
641-
Some((ptr.provenance.alloc_id, rel, ptr.provenance.sb))
672+
673+
let (tag, _) = ptr.into_parts();
674+
let sb = match tag {
675+
Tag::Concrete(ConcreteTag { sb, .. }) => sb,
676+
Tag::Wildcard => SbTag::Untagged,
677+
};
678+
679+
rel.map(|(alloc_id, size)| (alloc_id, size, sb))
642680
}
643681

644682
#[inline(always)]

src/stacked_borrows.rs

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

872872
// Adjust pointer.
873-
let new_place = place.map_provenance(|p| p.map(|t| Tag { sb: new_tag, ..t }));
873+
let new_place = place.map_provenance(|p| {
874+
p.map(|t| {
875+
// TODO: Fix this eventually
876+
if let Tag::Concrete(t) = t {
877+
Tag::Concrete(ConcreteTag { sb: new_tag, ..t })
878+
} else {
879+
t
880+
}
881+
})
882+
});
874883

875884
// Return new pointer.
876885
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)