1
Fork 0

extend Miri to correctly pass mutable pointers through FFI

Co-authored-by: Ralf Jung <post@ralfj.de>
This commit is contained in:
Strophox 2024-09-30 20:29:05 +02:00
parent 5926e82dd1
commit 712ceaba35
19 changed files with 476 additions and 59 deletions

View file

@ -168,9 +168,9 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
})
}
fn expose_ptr(
_ecx: &mut InterpCx<'tcx, Self>,
_ptr: interpret::Pointer<Self::Provenance>,
fn expose_provenance(
_ecx: &InterpCx<'tcx, Self>,
_provenance: Self::Provenance,
) -> interpret::InterpResult<'tcx> {
unimplemented!()
}

View file

@ -21,9 +21,8 @@ use crate::errors::{LongRunning, LongRunningWarn};
use crate::fluent_generated as fluent;
use crate::interpret::{
self, AllocId, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, GlobalAlloc, ImmTy,
InterpCx, InterpResult, MPlaceTy, OpTy, Pointer, RangeSet, Scalar, compile_time_machine,
interp_ok, throw_exhaust, throw_inval, throw_ub, throw_ub_custom, throw_unsup,
throw_unsup_format,
InterpCx, InterpResult, MPlaceTy, OpTy, RangeSet, Scalar, compile_time_machine, interp_ok,
throw_exhaust, throw_inval, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format,
};
/// When hitting this many interpreted terminators we emit a deny by default lint
@ -586,7 +585,10 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
}
#[inline(always)]
fn expose_ptr(_ecx: &mut InterpCx<'tcx, Self>, _ptr: Pointer) -> InterpResult<'tcx> {
fn expose_provenance(
_ecx: &InterpCx<'tcx, Self>,
_provenance: Self::Provenance,
) -> InterpResult<'tcx> {
// This is only reachable with -Zunleash-the-miri-inside-of-you.
throw_unsup_format!("exposing pointers is not possible at compile-time")
}

View file

@ -238,7 +238,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
let scalar = src.to_scalar();
let ptr = scalar.to_pointer(self)?;
match ptr.into_pointer_or_addr() {
Ok(ptr) => M::expose_ptr(self, ptr)?,
Ok(ptr) => M::expose_provenance(self, ptr.provenance)?,
Err(_) => {} // Do nothing, exposing an invalid pointer (`None` provenance) is a NOP.
};
interp_ok(ImmTy::from_scalar(

View file

@ -327,11 +327,11 @@ pub trait Machine<'tcx>: Sized {
addr: u64,
) -> InterpResult<'tcx, Pointer<Option<Self::Provenance>>>;
/// Marks a pointer as exposed, allowing it's provenance
/// Marks a pointer as exposed, allowing its provenance
/// to be recovered. "Pointer-to-int cast"
fn expose_ptr(
ecx: &mut InterpCx<'tcx, Self>,
ptr: Pointer<Self::Provenance>,
fn expose_provenance(
ecx: &InterpCx<'tcx, Self>,
provenance: Self::Provenance,
) -> InterpResult<'tcx>;
/// Convert a pointer with provenance into an allocation-offset pair and extra provenance info.

View file

@ -944,6 +944,52 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
interp_ok(())
}
/// Handle the effect an FFI call might have on the state of allocations.
/// This overapproximates the modifications which external code might make to memory:
/// We set all reachable allocations as initialized, mark all provenances as exposed
/// and overwrite them with `Provenance::WILDCARD`.
pub fn prepare_for_native_call(
&mut self,
id: AllocId,
initial_prov: M::Provenance,
) -> InterpResult<'tcx> {
// Expose provenance of the root allocation.
M::expose_provenance(self, initial_prov)?;
let mut done = FxHashSet::default();
let mut todo = vec![id];
while let Some(id) = todo.pop() {
if !done.insert(id) {
// We already saw this allocation before, don't process it again.
continue;
}
let info = self.get_alloc_info(id);
// If there is no data behind this pointer, skip this.
if !matches!(info.kind, AllocKind::LiveData) {
continue;
}
// Expose all provenances in this allocation, and add them to `todo`.
let alloc = self.get_alloc_raw(id)?;
for prov in alloc.provenance().provenances() {
M::expose_provenance(self, prov)?;
if let Some(id) = prov.get_alloc_id() {
todo.push(id);
}
}
// Prepare for possible write from native code if mutable.
if info.mutbl.is_mut() {
self.get_alloc_raw_mut(id)?
.0
.prepare_for_native_write()
.map_err(|e| e.to_interp_error(id))?;
}
}
interp_ok(())
}
/// Create a lazy debug printer that prints the given allocation and all allocations it points
/// to, recursively.
#[must_use]

View file

@ -643,6 +643,28 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
Ok(())
}
/// Initialize all previously uninitialized bytes in the entire allocation, and set
/// provenance of everything to `Wildcard`. Before calling this, make sure all
/// provenance in this allocation is exposed!
pub fn prepare_for_native_write(&mut self) -> AllocResult {
let full_range = AllocRange { start: Size::ZERO, size: Size::from_bytes(self.len()) };
// Overwrite uninitialized bytes with 0, to ensure we don't leak whatever their value happens to be.
for chunk in self.init_mask.range_as_init_chunks(full_range) {
if !chunk.is_init() {
let uninit_bytes = &mut self.bytes
[chunk.range().start.bytes_usize()..chunk.range().end.bytes_usize()];
uninit_bytes.fill(0);
}
}
// Mark everything as initialized now.
self.mark_init(full_range, true);
// Set provenance of all bytes to wildcard.
self.provenance.write_wildcards(self.len());
Ok(())
}
/// Remove all provenance in the given memory range.
pub fn clear_provenance(&mut self, cx: &impl HasDataLayout, range: AllocRange) -> AllocResult {
self.provenance.clear(range, cx)?;

View file

@ -195,6 +195,25 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
Ok(())
}
/// Overwrites all provenance in the allocation with wildcard provenance.
///
/// Provided for usage in Miri and panics otherwise.
pub fn write_wildcards(&mut self, alloc_size: usize) {
assert!(
Prov::OFFSET_IS_ADDR,
"writing wildcard provenance is not supported when `OFFSET_IS_ADDR` is false"
);
let wildcard = Prov::WILDCARD.unwrap();
// Remove all pointer provenances, then write wildcards into the whole byte range.
self.ptrs.clear();
let last = Size::from_bytes(alloc_size);
let bytes = self.bytes.get_or_insert_with(Box::default);
for offset in Size::ZERO..last {
bytes.insert(offset, wildcard);
}
}
}
/// A partial, owned list of provenance to transfer into another allocation.

View file

@ -66,6 +66,9 @@ pub trait Provenance: Copy + fmt::Debug + 'static {
/// pointer, and implement ptr-to-int transmutation by stripping provenance.
const OFFSET_IS_ADDR: bool;
/// If wildcard provenance is implemented, contains the unique, general wildcard provenance variant.
const WILDCARD: Option<Self>;
/// Determines how a pointer should be printed.
fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result;
@ -168,6 +171,9 @@ impl Provenance for CtfeProvenance {
// so ptr-to-int casts are not possible (since we do not know the global physical offset).
const OFFSET_IS_ADDR: bool = false;
// `CtfeProvenance` does not implement wildcard provenance.
const WILDCARD: Option<Self> = None;
fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Print AllocId.
fmt::Debug::fmt(&ptr.provenance.alloc_id(), f)?; // propagates `alternate` flag
@ -197,6 +203,9 @@ impl Provenance for AllocId {
// so ptr-to-int casts are not possible (since we do not know the global physical offset).
const OFFSET_IS_ADDR: bool = false;
// `AllocId` does not implement wildcard provenance.
const WILDCARD: Option<Self> = None;
fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Forward `alternate` flag to `alloc_id` printing.
if f.alternate() {