Skip to content

Commit c4e86e1

Browse files
committed
add option for recursive field retagging
1 parent 328a8c7 commit c4e86e1

File tree

11 files changed

+146
-6
lines changed

11 files changed

+146
-6
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,9 @@ to Miri failing to detect cases of undefined behavior in a program.
369369
application instead of raising an error within the context of Miri (and halting
370370
execution). Note that code might not expect these operations to ever panic, so
371371
this flag can lead to strange (mis)behavior.
372+
* `-Zmiri-retag-fields` changes Stacked Borrows retagging to recurse into fields.
373+
This means that references in fields of structs/enums/tuples/arrays/... are retagged,
374+
and in particular, they are protected when passed as function arguments.
372375
* `-Zmiri-track-alloc-id=<id1>,<id2>,...` shows a backtrace when the given allocations are
373376
being allocated or freed. This helps in debugging memory leaks and
374377
use after free bugs. Specifying this argument multiple times does not overwrite the previous

rust-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
493c960a3e6cdd2e2fbe8b6ea130fadea05f1ab0
1+
ddcbba036aee08f0709f98a92a342a278eae5c05

src/bin/miri.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,8 @@ fn main() {
383383
miri_config.provenance_mode = ProvenanceMode::Permissive;
384384
} else if arg == "-Zmiri-mute-stdout-stderr" {
385385
miri_config.mute_stdout_stderr = true;
386+
} else if arg == "-Zmiri-retag-fields" {
387+
miri_config.retag_fields = true;
386388
} else if arg == "-Zmiri-track-raw-pointers" {
387389
eprintln!(
388390
"WARNING: `-Zmiri-track-raw-pointers` has no effect; it is enabled by default"

src/eval.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ pub struct MiriConfig {
124124
pub preemption_rate: f64,
125125
/// Report the current instruction being executed every N basic blocks.
126126
pub report_progress: Option<u32>,
127+
/// Whether Stacked Borrows retagging should recurse into fields of datatypes.
128+
pub retag_fields: bool,
127129
}
128130

129131
impl Default for MiriConfig {
@@ -154,6 +156,7 @@ impl Default for MiriConfig {
154156
mute_stdout_stderr: false,
155157
preemption_rate: 0.01, // 1%
156158
report_progress: None,
159+
retag_fields: false,
157160
}
158161
}
159162
}

src/machine.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
354354
Some(RefCell::new(stacked_borrows::GlobalStateInner::new(
355355
config.tracked_pointer_tags.clone(),
356356
config.tracked_call_ids.clone(),
357+
config.retag_fields,
357358
)))
358359
} else {
359360
None

src/stacked_borrows.rs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ pub struct GlobalStateInner {
156156
tracked_pointer_tags: HashSet<SbTag>,
157157
/// The call ids to trace
158158
tracked_call_ids: HashSet<CallId>,
159+
/// Whether to recurse into datatypes when searching for pointers to retag.
160+
retag_fields: bool,
159161
}
160162

161163
/// We need interior mutable access to the global state.
@@ -204,14 +206,19 @@ impl fmt::Display for RefKind {
204206

205207
/// Utilities for initialization and ID generation
206208
impl GlobalStateInner {
207-
pub fn new(tracked_pointer_tags: HashSet<SbTag>, tracked_call_ids: HashSet<CallId>) -> Self {
209+
pub fn new(
210+
tracked_pointer_tags: HashSet<SbTag>,
211+
tracked_call_ids: HashSet<CallId>,
212+
retag_fields: bool,
213+
) -> Self {
208214
GlobalStateInner {
209215
next_ptr_tag: SbTag(NonZeroU64::new(1).unwrap()),
210216
base_ptr_tags: FxHashMap::default(),
211217
next_call_id: NonZeroU64::new(1).unwrap(),
212218
active_calls: FxHashSet::default(),
213219
tracked_pointer_tags,
214220
tracked_call_ids,
221+
retag_fields,
215222
}
216223
}
217224

@@ -1035,17 +1042,69 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
10351042
}
10361043
}
10371044

1038-
// We only reborrow "bare" references/boxes.
1039-
// Not traversing into fields helps with <https://github.com/rust-lang/unsafe-code-guidelines/issues/125>,
1040-
// but might also cost us optimization and analyses. We will have to experiment more with this.
1045+
// We need a visitor to visit all references. However, that requires
1046+
// a `MPlaceTy` (or `OpTy), so we have a fast path for reference types that
1047+
// avoids allocating.
1048+
10411049
if let Some((mutbl, protector)) = qualify(place.layout.ty, kind) {
10421050
// Fast path.
10431051
let val = this.read_immediate(&this.place_to_op(place)?)?;
10441052
let val = this.retag_reference(&val, mutbl, protector)?;
10451053
this.write_immediate(*val, place)?;
1054+
return Ok(());
10461055
}
10471056

1048-
Ok(())
1057+
// If we don't want to recurse, we are already done.
1058+
if !this.machine.stacked_borrows.as_mut().unwrap().get_mut().retag_fields {
1059+
return Ok(());
1060+
}
1061+
1062+
// Skip some types that have no further structure we might care about.
1063+
if matches!(
1064+
place.layout.ty.kind(),
1065+
ty::RawPtr(..)
1066+
| ty::Ref(..)
1067+
| ty::Int(..)
1068+
| ty::Uint(..)
1069+
| ty::Float(..)
1070+
| ty::Bool
1071+
| ty::Char
1072+
) {
1073+
return Ok(());
1074+
}
1075+
// Now go visit this thing.
1076+
let place = this.force_allocation(place)?;
1077+
1078+
let mut visitor = RetagVisitor { ecx: this, kind };
1079+
return visitor.visit_value(&place);
1080+
1081+
// The actual visitor.
1082+
struct RetagVisitor<'ecx, 'mir, 'tcx> {
1083+
ecx: &'ecx mut MiriEvalContext<'mir, 'tcx>,
1084+
kind: RetagKind,
1085+
}
1086+
impl<'ecx, 'mir, 'tcx> MutValueVisitor<'mir, 'tcx, Evaluator<'mir, 'tcx>>
1087+
for RetagVisitor<'ecx, 'mir, 'tcx>
1088+
{
1089+
type V = MPlaceTy<'tcx, Tag>;
1090+
1091+
#[inline(always)]
1092+
fn ecx(&mut self) -> &mut MiriEvalContext<'mir, 'tcx> {
1093+
&mut self.ecx
1094+
}
1095+
1096+
fn visit_value(&mut self, place: &MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> {
1097+
if let Some((mutbl, protector)) = qualify(place.layout.ty, self.kind) {
1098+
let val = self.ecx.read_immediate(&place.into())?;
1099+
let val = self.ecx.retag_reference(&val, mutbl, protector)?;
1100+
self.ecx.write_immediate(*val, &(*place).into())?;
1101+
} else {
1102+
// Maybe we need to go deeper.
1103+
self.walk_value(place)?;
1104+
}
1105+
Ok(())
1106+
}
1107+
}
10491108
}
10501109

10511110
/// After a stack frame got pushed, retag the return place so that we are sure
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// compile-flags: -Zmiri-retag-fields
2+
// error-pattern: incompatible item is protected
3+
struct Newtype<'a>(&'a mut i32);
4+
5+
fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
6+
dealloc();
7+
}
8+
9+
// Make sure that we protect references inside structs.
10+
fn main() {
11+
let ptr = Box::into_raw(Box::new(0i32));
12+
#[rustfmt::skip] // I like my newlines
13+
unsafe {
14+
dealloc_while_running(
15+
Newtype(&mut *ptr),
16+
|| drop(Box::from_raw(ptr)),
17+
)
18+
};
19+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
error: Undefined Behavior: not granting access to tag <TAG> because incompatible item is protected: [Unique for <TAG> (call ID)]
2+
--> RUSTLIB/alloc/src/boxed.rs:LL:CC
3+
|
4+
LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc)
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <TAG> because incompatible item is protected: [Unique for <TAG> (call ID)]
6+
|
7+
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
8+
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
9+
help: <TAG> was created by a retag at offsets [0x0..0x4]
10+
--> $DIR/newtype_retagging.rs:LL:CC
11+
|
12+
LL | let ptr = Box::into_raw(Box::new(0i32));
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
help: <TAG> was protected due to <TAG> which was created here
15+
--> $DIR/newtype_retagging.rs:LL:CC
16+
|
17+
LL | Newtype(&mut *ptr),
18+
| ^^^^^^^^^^^^^^^^^^
19+
help: this protector is live for this call
20+
--> $DIR/newtype_retagging.rs:LL:CC
21+
|
22+
LL | / fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
23+
LL | | dealloc();
24+
LL | | }
25+
| |_^
26+
= note: inside `std::boxed::Box::<i32>::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC
27+
= note: inside `std::boxed::Box::<i32>::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC
28+
note: inside closure at $DIR/newtype_retagging.rs:LL:CC
29+
--> $DIR/newtype_retagging.rs:LL:CC
30+
|
31+
LL | || drop(Box::from_raw(ptr)),
32+
| ^^^^^^^^^^^^^^^^^^
33+
note: inside `dealloc_while_running::<[closure@$DIR/newtype_retagging.rs:LL:CC]>` at $DIR/newtype_retagging.rs:LL:CC
34+
--> $DIR/newtype_retagging.rs:LL:CC
35+
|
36+
LL | dealloc();
37+
| ^^^^^^^^^
38+
note: inside `main` at $DIR/newtype_retagging.rs:LL:CC
39+
--> $DIR/newtype_retagging.rs:LL:CC
40+
|
41+
LL | / dealloc_while_running(
42+
LL | | Newtype(&mut *ptr),
43+
LL | | || drop(Box::from_raw(ptr)),
44+
LL | | )
45+
| |_________^
46+
47+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
48+
49+
error: aborting due to previous error
50+

tests/pass/stacked-borrows/interior_mutability.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// compile-flags: -Zmiri-retag-fields
12
use std::cell::{Cell, RefCell, UnsafeCell};
23
use std::mem::{self, MaybeUninit};
34

tests/pass/stacked-borrows/refcell.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// compile-flags: -Zmiri-retag-fields
12
use std::cell::{Ref, RefCell, RefMut};
23

34
fn main() {

0 commit comments

Comments
 (0)