Skip to content

Commit 0a0ebe2

Browse files
committed
Auto merge of #113569 - RalfJung:miri, r=oli-obk
miri: protect Move() function arguments during the call This gives `Move` operands a meaning specific to function calls: - for the duration of the call, the place the operand comes from is protected, making all read and write accesses insta-UB. - the contents of that place are reset to `Uninit`, so looking at them again after the function returns, we cannot observe their contents Turns out we can replace the existing "retag return place" hack with the exact same sort of protection on the return place, which is nicely symmetric. Fixes rust-lang/rust#112564 Fixes #2927 This starts with a Miri rustc-push, since we'd otherwise conflict with a PR that recently landed in Miri. (The "miri tree borrows" commit is an unrelated cleanup I noticed while doing the PR. I can remove it if you prefer.) r? `@oli-obk`
2 parents aa2e56c + f8a331a commit 0a0ebe2

25 files changed

+546
-84
lines changed

rust-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
d4096e0412ac5de785d739a0aa2b1c1c7b9d3b7d
1+
743333f3dd90721461c09387ec73d09c080d5f5f

src/borrow_tracker/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
302302
}
303303
}
304304

305-
fn retag_return_place(&mut self) -> InterpResult<'tcx> {
305+
fn protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
306306
let this = self.eval_context_mut();
307307
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
308308
match method {
309-
BorrowTrackerMethod::StackedBorrows => this.sb_retag_return_place(),
310-
BorrowTrackerMethod::TreeBorrows => this.tb_retag_return_place(),
309+
BorrowTrackerMethod::StackedBorrows => this.sb_protect_place(place),
310+
BorrowTrackerMethod::TreeBorrows => this.tb_protect_place(place),
311311
}
312312
}
313313

src/borrow_tracker/stacked_borrows/diagnostics.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ struct RetagOp {
189189
#[derive(Debug, Clone, Copy, PartialEq)]
190190
pub enum RetagCause {
191191
Normal,
192-
FnReturnPlace,
192+
InPlaceFnPassing,
193193
FnEntry,
194194
TwoPhase,
195195
}
@@ -501,7 +501,7 @@ impl RetagCause {
501501
match self {
502502
RetagCause::Normal => "retag",
503503
RetagCause::FnEntry => "function-entry retag",
504-
RetagCause::FnReturnPlace => "return-place retag",
504+
RetagCause::InPlaceFnPassing => "in-place function argument/return passing protection",
505505
RetagCause::TwoPhase => "two-phase retag",
506506
}
507507
.to_string()

src/borrow_tracker/stacked_borrows/mod.rs

+11-21
Original file line numberDiff line numberDiff line change
@@ -994,35 +994,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
994994
}
995995
}
996996

997-
/// After a stack frame got pushed, retag the return place so that we are sure
998-
/// it does not alias with anything.
999-
///
1000-
/// This is a HACK because there is nothing in MIR that would make the retag
1001-
/// explicit. Also see <https://github.com/rust-lang/rust/issues/71117>.
1002-
fn sb_retag_return_place(&mut self) -> InterpResult<'tcx> {
997+
/// Protect a place so that it cannot be used any more for the duration of the current function
998+
/// call.
999+
///
1000+
/// This is used to ensure soundness of in-place function argument/return passing.
1001+
fn sb_protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
10031002
let this = self.eval_context_mut();
1004-
let return_place = &this.frame().return_place;
1005-
if return_place.layout.is_zst() {
1006-
// There may not be any memory here, nothing to do.
1007-
return Ok(());
1008-
}
1009-
// We need this to be in-memory to use tagged pointers.
1010-
let return_place = this.force_allocation(&return_place.clone())?;
10111003

1012-
// We have to turn the place into a pointer to use the existing code.
1004+
// We have to turn the place into a pointer to use the usual retagging logic.
10131005
// (The pointer type does not matter, so we use a raw pointer.)
1014-
let ptr_layout = this.layout_of(Ty::new_mut_ptr(this.tcx.tcx, return_place.layout.ty))?;
1015-
let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout);
1016-
// Reborrow it. With protection! That is part of the point.
1006+
let ptr_layout = this.layout_of(Ty::new_mut_ptr(this.tcx.tcx, place.layout.ty))?;
1007+
let ptr = ImmTy::from_immediate(place.to_ref(this), ptr_layout);
1008+
// Reborrow it. With protection! That is the entire point.
10171009
let new_perm = NewPermission::Uniform {
10181010
perm: Permission::Unique,
10191011
access: Some(AccessKind::Write),
10201012
protector: Some(ProtectorKind::StrongProtector),
10211013
};
1022-
let val = this.sb_retag_reference(&val, new_perm, RetagCause::FnReturnPlace)?;
1023-
// And use reborrowed pointer for return place.
1024-
let return_place = this.ref_to_mplace(&val)?;
1025-
this.frame_mut().return_place = return_place.into();
1014+
let _new_ptr = this.sb_retag_reference(&ptr, new_perm, RetagCause::InPlaceFnPassing)?;
1015+
// We just throw away `new_ptr`, so nobody can access this memory while it is protected.
10261016

10271017
Ok(())
10281018
}

src/borrow_tracker/tree_borrows/mod.rs

+27-37
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
178178
&mut self,
179179
place: &MPlaceTy<'tcx, Provenance>, // parent tag extracted from here
180180
ptr_size: Size,
181-
new_perm: Option<NewPermission>,
181+
new_perm: NewPermission,
182182
new_tag: BorTag,
183183
) -> InterpResult<'tcx, Option<(AllocId, BorTag)>> {
184184
let this = self.eval_context_mut();
@@ -256,10 +256,6 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
256256
ptr_size.bytes()
257257
);
258258

259-
let Some(new_perm) = new_perm else {
260-
return Ok(Some((alloc_id, orig_tag)));
261-
};
262-
263259
if let Some(protect) = new_perm.protector {
264260
// We register the protection in two different places.
265261
// This makes creating a protector slower, but checking whether a tag
@@ -305,7 +301,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
305301
fn tb_retag_reference(
306302
&mut self,
307303
val: &ImmTy<'tcx, Provenance>,
308-
new_perm: Option<NewPermission>,
304+
new_perm: NewPermission,
309305
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
310306
let this = self.eval_context_mut();
311307
// We want a place for where the ptr *points to*, so we get one.
@@ -317,7 +313,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
317313
// - if the pointer is not reborrowed (raw pointer) or if `zero_size` is set
318314
// then we override the size to do a zero-length reborrow.
319315
let reborrow_size = match new_perm {
320-
Some(NewPermission { zero_size: false, .. }) =>
316+
NewPermission { zero_size: false, .. } =>
321317
this.size_and_align_of_mplace(&place)?
322318
.map(|(size, _)| size)
323319
.unwrap_or(place.layout.size),
@@ -374,7 +370,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
374370
NewPermission::from_ref_ty(pointee, mutability, kind, this),
375371
_ => None,
376372
};
377-
this.tb_retag_reference(val, new_perm)
373+
if let Some(new_perm) = new_perm {
374+
this.tb_retag_reference(val, new_perm)
375+
} else {
376+
Ok(val.clone())
377+
}
378378
}
379379

380380
/// Retag all pointers that are stored in this place.
@@ -405,9 +405,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
405405
place: &PlaceTy<'tcx, Provenance>,
406406
new_perm: Option<NewPermission>,
407407
) -> InterpResult<'tcx> {
408-
let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
409-
let val = self.ecx.tb_retag_reference(&val, new_perm)?;
410-
self.ecx.write_immediate(*val, place)?;
408+
if let Some(new_perm) = new_perm {
409+
let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
410+
let val = self.ecx.tb_retag_reference(&val, new_perm)?;
411+
self.ecx.write_immediate(*val, place)?;
412+
}
411413
Ok(())
412414
}
413415
}
@@ -493,37 +495,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
493495
}
494496
}
495497

496-
/// After a stack frame got pushed, retag the return place so that we are sure
497-
/// it does not alias with anything.
498-
///
499-
/// This is a HACK because there is nothing in MIR that would make the retag
500-
/// explicit. Also see <https://github.com/rust-lang/rust/issues/71117>.
501-
fn tb_retag_return_place(&mut self) -> InterpResult<'tcx> {
498+
/// Protect a place so that it cannot be used any more for the duration of the current function
499+
/// call.
500+
///
501+
/// This is used to ensure soundness of in-place function argument/return passing.
502+
fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
502503
let this = self.eval_context_mut();
503-
//this.debug_hint_location();
504-
let return_place = &this.frame().return_place;
505-
if return_place.layout.is_zst() {
506-
// There may not be any memory here, nothing to do.
507-
return Ok(());
508-
}
509-
// We need this to be in-memory to use tagged pointers.
510-
let return_place = this.force_allocation(&return_place.clone())?;
511504

512-
// We have to turn the place into a pointer to use the existing code.
505+
// We have to turn the place into a pointer to use the usual retagging logic.
513506
// (The pointer type does not matter, so we use a raw pointer.)
514-
let ptr_layout = this.layout_of(Ty::new_mut_ptr(this.tcx.tcx, return_place.layout.ty))?;
515-
let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout);
516-
// Reborrow it. With protection! That is part of the point.
517-
// FIXME: do we truly want a 2phase borrow here?
518-
let new_perm = Some(NewPermission {
519-
initial_state: Permission::new_unique_2phase(/*freeze*/ false),
507+
let ptr_layout = this.layout_of(Ty::new_mut_ptr(this.tcx.tcx, place.layout.ty))?;
508+
let ptr = ImmTy::from_immediate(place.to_ref(this), ptr_layout);
509+
// Reborrow it. With protection! That is the entire point.
510+
let new_perm = NewPermission {
511+
initial_state: Permission::new_active(),
520512
zero_size: false,
521513
protector: Some(ProtectorKind::StrongProtector),
522-
});
523-
let val = this.tb_retag_reference(&val, new_perm)?;
524-
// And use reborrowed pointer for return place.
525-
let return_place = this.ref_to_mplace(&val)?;
526-
this.frame_mut().return_place = return_place.into();
514+
};
515+
let _new_ptr = this.tb_retag_reference(&ptr, new_perm)?;
516+
// We just throw away `new_ptr`, so nobody can access this memory while it is protected.
527517

528518
Ok(())
529519
}

src/borrow_tracker/tree_borrows/perms.rs

+5
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ impl Permission {
138138
Self { inner: Reserved { ty_is_freeze } }
139139
}
140140

141+
/// Default initial permission for return place.
142+
pub fn new_active() -> Self {
143+
Self { inner: Active }
144+
}
145+
141146
/// Default initial permission of a reborrowed shared reference
142147
pub fn new_frozen() -> Self {
143148
Self { inner: Frozen }

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ extern crate rustc_index;
5555
extern crate rustc_session;
5656
extern crate rustc_span;
5757
extern crate rustc_target;
58+
extern crate either; // the one from rustc
5859

5960
// Necessary to pull in object code as the rest of the rustc crates are shipped only as rmeta
6061
// files.

src/machine.rs

+41-12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::fmt;
77
use std::path::Path;
88
use std::process;
99

10+
use either::Either;
1011
use rand::rngs::StdRng;
1112
use rand::SeedableRng;
1213

@@ -533,15 +534,16 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
533534
let target = &tcx.sess.target;
534535
match target.arch.as_ref() {
535536
"wasm32" | "wasm64" => 64 * 1024, // https://webassembly.github.io/spec/core/exec/runtime.html#memory-instances
536-
"aarch64" =>
537+
"aarch64" => {
537538
if target.options.vendor.as_ref() == "apple" {
538539
// No "definitive" source, but see:
539540
// https://www.wwdcnotes.com/notes/wwdc20/10214/
540541
// https://github.com/ziglang/zig/issues/11308 etc.
541542
16 * 1024
542543
} else {
543544
4 * 1024
544-
},
545+
}
546+
}
545547
_ => 4 * 1024,
546548
}
547549
};
@@ -892,7 +894,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
892894
ecx: &mut MiriInterpCx<'mir, 'tcx>,
893895
instance: ty::Instance<'tcx>,
894896
abi: Abi,
895-
args: &[OpTy<'tcx, Provenance>],
897+
args: &[FnArg<'tcx, Provenance>],
896898
dest: &PlaceTy<'tcx, Provenance>,
897899
ret: Option<mir::BasicBlock>,
898900
unwind: mir::UnwindAction,
@@ -905,12 +907,13 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
905907
ecx: &mut MiriInterpCx<'mir, 'tcx>,
906908
fn_val: Dlsym,
907909
abi: Abi,
908-
args: &[OpTy<'tcx, Provenance>],
910+
args: &[FnArg<'tcx, Provenance>],
909911
dest: &PlaceTy<'tcx, Provenance>,
910912
ret: Option<mir::BasicBlock>,
911913
_unwind: mir::UnwindAction,
912914
) -> InterpResult<'tcx> {
913-
ecx.call_dlsym(fn_val, abi, args, dest, ret)
915+
let args = ecx.copy_fn_args(args)?; // FIXME: Should `InPlace` arguments be reset to uninit?
916+
ecx.call_dlsym(fn_val, abi, &args, dest, ret)
914917
}
915918

916919
#[inline(always)]
@@ -1094,8 +1097,9 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
10941097
ptr: Pointer<Self::Provenance>,
10951098
) -> InterpResult<'tcx> {
10961099
match ptr.provenance {
1097-
Provenance::Concrete { alloc_id, tag } =>
1098-
intptrcast::GlobalStateInner::expose_ptr(ecx, alloc_id, tag),
1100+
Provenance::Concrete { alloc_id, tag } => {
1101+
intptrcast::GlobalStateInner::expose_ptr(ecx, alloc_id, tag)
1102+
}
10991103
Provenance::Wildcard => {
11001104
// No need to do anything for wildcard pointers as
11011105
// their provenances have already been previously exposed.
@@ -1206,6 +1210,25 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
12061210
Ok(())
12071211
}
12081212

1213+
fn protect_in_place_function_argument(
1214+
ecx: &mut InterpCx<'mir, 'tcx, Self>,
1215+
place: &PlaceTy<'tcx, Provenance>,
1216+
) -> InterpResult<'tcx> {
1217+
// We do need to write `uninit` so that even after the call ends, the former contents of
1218+
// this place cannot be observed any more.
1219+
ecx.write_uninit(place)?;
1220+
// If we have a borrow tracker, we also have it set up protection so that all reads *and
1221+
// writes* during this call are insta-UB.
1222+
if ecx.machine.borrow_tracker.is_some() {
1223+
if let Either::Left(place) = place.as_mplace_or_local() {
1224+
ecx.protect_place(&place)?;
1225+
} else {
1226+
// Locals that don't have their address taken are as protected as they can ever be.
1227+
}
1228+
}
1229+
Ok(())
1230+
}
1231+
12091232
#[inline(always)]
12101233
fn init_frame_extra(
12111234
ecx: &mut InterpCx<'mir, 'tcx, Self>,
@@ -1288,8 +1311,17 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
12881311
let stack_len = ecx.active_thread_stack().len();
12891312
ecx.active_thread_mut().set_top_user_relevant_frame(stack_len - 1);
12901313
}
1291-
if ecx.machine.borrow_tracker.is_some() {
1292-
ecx.retag_return_place()?;
1314+
Ok(())
1315+
}
1316+
1317+
fn before_stack_pop(
1318+
ecx: &InterpCx<'mir, 'tcx, Self>,
1319+
frame: &Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>,
1320+
) -> InterpResult<'tcx> {
1321+
// We want this *before* the return value copy, because the return place itself is protected
1322+
// until we do `end_call` here.
1323+
if let Some(borrow_tracker) = &ecx.machine.borrow_tracker {
1324+
borrow_tracker.borrow_mut().end_call(&frame.extra);
12931325
}
12941326
Ok(())
12951327
}
@@ -1308,9 +1340,6 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
13081340
ecx.active_thread_mut().recompute_top_user_relevant_frame();
13091341
}
13101342
let timing = frame.extra.timing.take();
1311-
if let Some(borrow_tracker) = &ecx.machine.borrow_tracker {
1312-
borrow_tracker.borrow_mut().end_call(&frame.extra);
1313-
}
13141343
let res = ecx.handle_stack_pop_unwind(frame.extra, unwinding);
13151344
if let Some(profiler) = ecx.machine.profiler.as_ref() {
13161345
profiler.finish_recording_interval_event(timing.unwrap());

src/shims/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
3131
&mut self,
3232
instance: ty::Instance<'tcx>,
3333
abi: Abi,
34-
args: &[OpTy<'tcx, Provenance>],
34+
args: &[FnArg<'tcx, Provenance>],
3535
dest: &PlaceTy<'tcx, Provenance>,
3636
ret: Option<mir::BasicBlock>,
3737
unwind: mir::UnwindAction,
@@ -41,7 +41,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
4141

4242
// There are some more lang items we want to hook that CTFE does not hook (yet).
4343
if this.tcx.lang_items().align_offset_fn() == Some(instance.def.def_id()) {
44-
let [ptr, align] = check_arg_count(args)?;
44+
let args = this.copy_fn_args(args)?;
45+
let [ptr, align] = check_arg_count(&args)?;
4546
if this.align_offset(ptr, align, dest, ret, unwind)? {
4647
return Ok(None);
4748
}
@@ -55,7 +56,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
5556
// to run extra MIR), and Ok(Some(body)) if we found MIR to run for the
5657
// foreign function
5758
// Any needed call to `goto_block` will be performed by `emulate_foreign_item`.
58-
return this.emulate_foreign_item(instance.def_id(), abi, args, dest, ret, unwind);
59+
let args = this.copy_fn_args(args)?; // FIXME: Should `InPlace` arguments be reset to uninit?
60+
return this.emulate_foreign_item(instance.def_id(), abi, &args, dest, ret, unwind);
5961
}
6062

6163
// Otherwise, load the MIR.

0 commit comments

Comments
 (0)