Skip to content

Commit a544425

Browse files
committed
expose apis needed for better miri ffi
1 parent 2cd3783 commit a544425

File tree

4 files changed

+130
-25
lines changed

4 files changed

+130
-25
lines changed

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 114 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -977,12 +977,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
977977
}
978978

979979
/// Handle the effect an FFI call might have on the state of allocations.
980-
/// This overapproximates the modifications which external code might make to memory:
981-
/// We set all reachable allocations as initialized, mark all reachable provenances as exposed
982-
/// and overwrite them with `Provenance::WILDCARD`.
980+
/// If `paranoid` is true, overapproximates the modifications which external code might make
981+
/// to memory: We set all reachable allocations as initialized, mark all reachable provenances
982+
/// as exposed and overwrite them with `Provenance::WILDCARD`. Otherwise, it just makes sure
983+
/// that all allocations are properly set up so that we don't leak whatever was in the uninit
984+
/// bytes on FFI call.
983985
///
984986
/// The allocations in `ids` are assumed to be already exposed.
985-
pub fn prepare_for_native_call(&mut self, ids: Vec<AllocId>) -> InterpResult<'tcx> {
987+
pub fn prepare_for_native_call(
988+
&mut self,
989+
ids: Vec<AllocId>,
990+
paranoid: bool,
991+
) -> InterpResult<'tcx> {
986992
let mut done = FxHashSet::default();
987993
let mut todo = ids;
988994
while let Some(id) = todo.pop() {
@@ -997,25 +1003,119 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
9971003
continue;
9981004
}
9991005

1000-
// Expose all provenances in this allocation, and add them to `todo`.
1006+
// Make sure we iterate over everything recursively, preparing the extra alloc info.
10011007
let alloc = self.get_alloc_raw(id)?;
10021008
for prov in alloc.provenance().provenances() {
1003-
M::expose_provenance(self, prov)?;
1009+
if paranoid {
1010+
// Expose all provenances in this allocation, and add them to `todo`.
1011+
M::expose_provenance(self, prov)?;
1012+
}
10041013
if let Some(id) = prov.get_alloc_id() {
10051014
todo.push(id);
10061015
}
10071016
}
1017+
10081018
// Also expose the provenance of the interpreter-level allocation, so it can
10091019
// be read by FFI. The `black_box` is defensive programming as LLVM likes
10101020
// to (incorrectly) optimize away ptr2int casts whose result is unused.
1011-
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
1012-
1013-
// Prepare for possible write from native code if mutable.
1014-
if info.mutbl.is_mut() {
1015-
self.get_alloc_raw_mut(id)?
1016-
.0
1017-
.prepare_for_native_write()
1018-
.map_err(|e| e.to_interp_error(id))?;
1021+
if paranoid {
1022+
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
1023+
// Prepare for possible write from native code if mutable.
1024+
if info.mutbl.is_mut() {
1025+
self.get_alloc_raw_mut(id)?.0.prepare_for_native_write();
1026+
}
1027+
}
1028+
}
1029+
interp_ok(())
1030+
}
1031+
1032+
/// Updates the machine state "as if" the accesses given had been performed.
1033+
/// Used only by Miri for FFI, for taking note of events that were intercepted from foreign
1034+
/// code and properly (but still conservatively) marking their effects. Remember to call
1035+
/// `prepare_for_native_call` with `paranoid` set to false first on the same `AllocId`s, or
1036+
/// some writes may be discarded!
1037+
///
1038+
/// The allocations in `ids` are assumed to be already exposed.
1039+
pub fn apply_accesses(
1040+
&mut self,
1041+
mut ids: Vec<AllocId>,
1042+
reads: Vec<std::ops::Range<u64>>,
1043+
writes: Vec<std::ops::Range<u64>>,
1044+
) -> InterpResult<'tcx> {
1045+
// Helper function to avoid some code duplication
1046+
fn get_start_size(
1047+
rg: std::ops::Range<u64>,
1048+
alloc_base: u64,
1049+
alloc_size: u64,
1050+
) -> (u64, u64) {
1051+
// A bunch of range bounds nonsense that effectively simplifies to
1052+
// "get the starting point of the overlap and the length from there"
1053+
let signed_start = rg.start.cast_signed() - alloc_base.cast_signed();
1054+
let size_uncapped = if signed_start < 0 {
1055+
// We already know the ranges overlap, so this must be > 0
1056+
(signed_start + (rg.end - rg.start).cast_signed()).try_into().unwrap()
1057+
} else {
1058+
rg.end - rg.start
1059+
};
1060+
let start: u64 = signed_start.try_into().unwrap_or(0);
1061+
let size = std::cmp::min(size_uncapped, alloc_size - start);
1062+
(start, size)
1063+
}
1064+
1065+
let mut done = FxHashSet::default();
1066+
while let Some(id) = ids.pop() {
1067+
if !done.insert(id) {
1068+
continue;
1069+
}
1070+
let info = self.get_alloc_info(id);
1071+
1072+
// If there is no data behind this pointer, skip this.
1073+
if !matches!(info.kind, AllocKind::LiveData) {
1074+
continue;
1075+
}
1076+
1077+
let alloc_base: u64 = {
1078+
// Keep the alloc here so the borrow checker is happy
1079+
let alloc = self.get_alloc_raw(id)?;
1080+
// No need for black_box trickery since we actually use the address
1081+
alloc.get_bytes_unchecked_raw().expose_provenance().try_into().unwrap()
1082+
};
1083+
let alloc_size = info.size.bytes();
1084+
1085+
// Find reads which overlap with the current allocation
1086+
for rg in &reads {
1087+
let overlap = rg.start <= alloc_base + alloc_size && alloc_base <= rg.end;
1088+
if overlap {
1089+
let (start, size) = get_start_size(rg.clone(), alloc_base, alloc_size);
1090+
1091+
let alloc = self.get_alloc_raw(id)?;
1092+
let prov_map = alloc.provenance();
1093+
// Only iterate on the bytes that overlap with the access
1094+
for i in start..start + size {
1095+
// We can be conservative and only expose provenances actually read
1096+
if let Some(prov) = prov_map.get(Size::from_bytes(1), self)
1097+
&& rg.contains(&(alloc_base + i))
1098+
{
1099+
M::expose_provenance(self, prov)?;
1100+
if let Some(id) = prov.get_alloc_id() {
1101+
ids.push(id);
1102+
}
1103+
}
1104+
}
1105+
}
1106+
}
1107+
1108+
// Then do the same thing for writes
1109+
for rg in &writes {
1110+
let overlap = rg.start <= alloc_base + alloc_size && alloc_base <= rg.end;
1111+
if overlap {
1112+
let (start, size) = get_start_size(rg.clone(), alloc_base, alloc_size);
1113+
1114+
let alloc_mut = self.get_alloc_raw_mut(id)?.0;
1115+
let range =
1116+
AllocRange { start: Size::from_bytes(start), size: Size::from_bytes(size) };
1117+
alloc_mut.mark_foreign_write(range);
1118+
}
10191119
}
10201120
}
10211121
interp_ok(())

compiler/rustc_middle/src/mir/interpret/allocation.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
775775
/// Initialize all previously uninitialized bytes in the entire allocation, and set
776776
/// provenance of everything to `Wildcard`. Before calling this, make sure all
777777
/// provenance in this allocation is exposed!
778-
pub fn prepare_for_native_write(&mut self) -> AllocResult {
778+
pub fn prepare_for_native_write(&mut self) {
779779
let full_range = AllocRange { start: Size::ZERO, size: Size::from_bytes(self.len()) };
780780
// Overwrite uninitialized bytes with 0, to ensure we don't leak whatever their value happens to be.
781781
for chunk in self.init_mask.range_as_init_chunks(full_range) {
@@ -785,18 +785,23 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
785785
uninit_bytes.fill(0);
786786
}
787787
}
788+
self.mark_foreign_write(full_range);
789+
}
790+
791+
/// Initialise previously uninitialised bytes in the given range, and set provenance of
792+
/// everything in it to `Wildcard`. Before calling this, make sure all provenance in this
793+
/// range is exposed!
794+
pub fn mark_foreign_write(&mut self, range: AllocRange) {
788795
// Mark everything as initialized now.
789-
self.mark_init(full_range, true);
796+
self.mark_init(range, true);
790797

791-
// Set provenance of all bytes to wildcard.
792-
self.provenance.write_wildcards(self.len());
798+
// Set provenance of affected bytes to wildcard.
799+
self.provenance.write_wildcards(range);
793800

794801
// Also expose the provenance of the interpreter-level allocation, so it can
795802
// be written by FFI. The `black_box` is defensive programming as LLVM likes
796803
// to (incorrectly) optimize away ptr2int casts whose result is unused.
797804
std::hint::black_box(self.get_bytes_unchecked_raw_mut().expose_provenance());
798-
799-
Ok(())
800805
}
801806

802807
/// Remove all provenance in the given memory range.

compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,11 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
213213
Ok(())
214214
}
215215

216-
/// Overwrites all provenance in the allocation with wildcard provenance.
216+
/// Overwrites all provenance in the specified range within the allocation
217+
/// with wildcard provenance.
217218
///
218219
/// Provided for usage in Miri and panics otherwise.
219-
pub fn write_wildcards(&mut self, alloc_size: usize) {
220+
pub fn write_wildcards(&mut self, range: AllocRange) {
220221
assert!(
221222
Prov::OFFSET_IS_ADDR,
222223
"writing wildcard provenance is not supported when `OFFSET_IS_ADDR` is false"
@@ -225,9 +226,8 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
225226

226227
// Remove all pointer provenances, then write wildcards into the whole byte range.
227228
self.ptrs.clear();
228-
let last = Size::from_bytes(alloc_size);
229229
let bytes = self.bytes.get_or_insert_with(Box::default);
230-
for offset in Size::ZERO..last {
230+
for offset in range.start..range.start + range.size {
231231
bytes.insert(offset, wildcard);
232232
}
233233
}

src/tools/miri/src/alloc_addresses/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
471471
// for the search within `prepare_for_native_call`.
472472
let exposed: Vec<AllocId> =
473473
this.machine.alloc_addresses.get_mut().exposed.iter().copied().collect();
474-
this.prepare_for_native_call(exposed)
474+
this.prepare_for_native_call(exposed, true)
475475
}
476476
}
477477

0 commit comments

Comments
 (0)