Skip to content

Commit 9b408f4

Browse files
committed
wip: hook up bits
wip: tests
1 parent ffa564d commit 9b408f4

File tree

7 files changed

+170
-21
lines changed

7 files changed

+170
-21
lines changed

src/shims/native_lib/mod.rs

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,34 @@ pub struct MemEvents {
3434
/// A single memory access.
3535
#[allow(dead_code)]
3636
#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))]
37-
#[derive(Debug)]
37+
#[derive(Clone, Debug)]
3838
pub enum AccessEvent {
39-
/// A read may have occurred on this memory range.
40-
/// Some instructions *may* read memory without *always* doing that,
41-
/// so this can be an over-approximation.
42-
/// The range info, however, is reliable if the access did happen.
39+
/// A read occurred on this memory range.
4340
Read(AccessRange),
44-
/// A read may have occurred on this memory range.
41+
/// A write may have occurred on this memory range.
4542
/// Some instructions *may* write memory without *always* doing that,
4643
/// so this can be an over-approximation.
4744
/// The range info, however, is reliable if the access did happen.
48-
Write(AccessRange),
45+
/// If the second field is true, the access definitely happened.
46+
Write(AccessRange, bool),
47+
}
48+
49+
impl AccessEvent {
50+
fn get_range(&self) -> AccessRange {
51+
match self {
52+
AccessEvent::Read(access_range) => access_range.clone(),
53+
AccessEvent::Write(access_range, _) => access_range.clone(),
54+
}
55+
}
56+
57+
fn is_read(&self) -> bool {
58+
matches!(self, AccessEvent::Read(_))
59+
}
60+
61+
fn definitely_happened(&self) -> bool {
62+
// Anything except a maybe-write is definitive.
63+
!matches!(self, AccessEvent::Write(_, false))
64+
}
4965
}
5066

5167
/// The memory touched by a given access.
@@ -59,6 +75,12 @@ pub struct AccessRange {
5975
pub size: usize,
6076
}
6177

78+
impl AccessRange {
79+
fn end(&self) -> usize {
80+
self.addr.strict_add(self.size)
81+
}
82+
}
83+
6284
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
6385
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
6486
/// Call native host function and return the output as an immediate.
@@ -196,6 +218,44 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
196218
}
197219
None
198220
}
221+
222+
/// Applies the `events` to Miri's internal state. The event vector must be
223+
/// ordered sequentially by when the accesses happened, and the sizes are
224+
/// assumed to be exact.
225+
fn tracing_apply_accesses(&mut self, events: MemEvents) -> InterpResult<'tcx> {
226+
let this = self.eval_context_mut();
227+
228+
for evt in events.acc_events {
229+
let evt_rg = evt.get_range();
230+
// We're assuming an access only touches 1 allocation.
231+
let alloc_id = this
232+
.alloc_id_from_addr(evt_rg.addr.to_u64(), evt_rg.size.try_into().unwrap(), true)
233+
.expect("Foreign code did an out-of-bounds access!");
234+
235+
let alloc = this.get_alloc_raw(alloc_id)?;
236+
let alloc_addr = alloc.get_bytes_unchecked_raw().addr();
237+
238+
// Shift the overlap range to be an offset from the allocation base addr.
239+
let overlap = evt_rg.addr.strict_sub(alloc_addr)
240+
..evt_rg.end().strict_sub(alloc_addr);
241+
242+
// Reads are infallible, writes might not be.
243+
if evt.is_read() {
244+
let p_map = alloc.provenance();
245+
for idx in overlap {
246+
// If a provenance was read by the foreign code, expose it.
247+
if let Some(prov) = p_map.get(Size::from_bytes(idx), this) {
248+
this.expose_provenance(prov)?;
249+
}
250+
}
251+
} else if evt.definitely_happened() || alloc.mutability.is_mut() {
252+
//let (alloc, cx) = this.get_alloc_raw_mut(alloc_id)?;
253+
//alloc.process_native_write(AllocRange { start: overlap.start, size: overlap.len() })
254+
}
255+
}
256+
257+
interp_ok(())
258+
}
199259
}
200260

201261
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -221,6 +281,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
221281
}
222282
};
223283

284+
// Do we have ptrace?
285+
let tracing = trace::Supervisor::is_enabled();
286+
224287
// Get the function arguments, and convert them to `libffi`-compatible form.
225288
let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
226289
for arg in args.iter() {
@@ -240,9 +303,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
240303
// The first time this happens, print a warning.
241304
if !this.machine.native_call_mem_warned.replace(true) {
242305
// Newly set, so first time we get here.
243-
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem {
244-
tracing: self::trace::Supervisor::is_enabled(),
245-
});
306+
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
246307
}
247308

248309
this.expose_provenance(prov)?;
@@ -268,15 +329,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
268329
// be read by FFI. The `black_box` is defensive programming as LLVM likes
269330
// to (incorrectly) optimize away ptr2int casts whose result is unused.
270331
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
271-
// Expose all provenances in this allocation, since the native code can do $whatever.
272-
for prov in alloc.provenance().provenances() {
273-
this.expose_provenance(prov)?;
332+
333+
if !tracing {
334+
// Expose all provenances in this allocation, since the native code can do $whatever.
335+
for prov in alloc.provenance().provenances() {
336+
this.expose_provenance(prov)?;
337+
}
274338
}
275339

276340
// Prepare for possible write from native code if mutable.
277341
if info.mutbl.is_mut() {
278-
let alloc = &mut this.get_alloc_raw_mut(alloc_id)?.0;
342+
let (alloc, _cx) = this.get_alloc_raw_mut(alloc_id)?;
279343
alloc.prepare_for_native_access();
344+
if tracing {
345+
//alloc.process_native_write(&cx.tcx, None);
346+
}
280347
// Also expose *mutable* provenance for the interpreter-level allocation.
281348
std::hint::black_box(alloc.get_bytes_unchecked_raw_mut().expose_provenance());
282349
}
@@ -288,10 +355,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
288355
let (ret, maybe_memevents) =
289356
this.call_native_with_args(link_name, dest, code_ptr, libffi_args)?;
290357

291-
if cfg!(target_os = "linux")
292-
&& let Some(events) = maybe_memevents
293-
{
294-
trace!("Registered FFI events:\n{events:#0x?}");
358+
if tracing {
359+
this.tracing_apply_accesses(maybe_memevents.unwrap())?;
295360
}
296361

297362
this.write_immediate(*ret, dest)?;

src/shims/native_lib/trace/parent.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,11 @@ fn capstone_find_events(
386386
acc_events.push(AccessEvent::Read(push.clone()));
387387
}
388388
if acc_ty.is_writable() {
389-
acc_events.push(AccessEvent::Write(push));
389+
// FIXME: This could be made certain; either determine all cases where
390+
// only reads happen, or have an intermediate mempr_* function to first
391+
// map the page(s) as readonly and check if a segfault occurred.
392+
// If this did a read as well, it's possible the write didn't happen.
393+
acc_events.push(AccessEvent::Write(push, !acc_ty.is_readable()));
390394
}
391395

392396
return true;
@@ -442,8 +446,7 @@ fn handle_segfault(
442446
// Get information on what caused the segfault. This contains the address
443447
// that triggered it.
444448
let siginfo = ptrace::getsiginfo(pid).unwrap();
445-
// All x86, ARM, etc. instructions only have at most one memory operand
446-
// (thankfully!)
449+
// All x86 instructions only have at most one memory operand (thankfully!)
447450
// SAFETY: si_addr is safe to call.
448451
let addr = unsafe { siginfo.si_addr().addr() };
449452
let page_addr = addr.strict_sub(addr.strict_rem(page_size));

tests/native-lib/fail/trace_read.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//@only-target: linux
2+
//@only-target: gnu
3+
//@only-target: x86
4+
//@only-on-host
5+
//@compile-flags: -Zmiri-permissive-provenance -Zmiri-native-lib-enable-tracing
6+
7+
extern "C" {
8+
fn do_nothing();
9+
}
10+
11+
fn main() {
12+
unexposed_reachable_alloc();
13+
}
14+
15+
fn unexposed_reachable_alloc() {
16+
let inner = 42;
17+
let intermediate = &raw const inner;
18+
let exposed = (&raw const intermediate).expose_provenance();
19+
unsafe { do_nothing() };
20+
let invalid: *const i32 = std::ptr::with_exposed_provenance(intermediate.addr());
21+
let valid: *const *const i32 = std::ptr::with_exposed_provenance(exposed);
22+
unsafe {
23+
assert_ne!((*valid).addr(), 0);
24+
println!("{}", *invalid); //~ ERROR: Undefined Behavior: pointer not dereferenceable: pointer must be dereferenceable for 4 bytes, but got
25+
}
26+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error: Undefined Behavior: pointer not dereferenceable: pointer must be dereferenceable for 4 bytes, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
2+
--> tests/native-lib/fail/trace_read.rs:LL:CC
3+
|
4+
LL | println!("{}", *invalid);
5+
| ^^^^^^^^ Undefined Behavior occurred here
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `unexposed_reachable_alloc` at tests/native-lib/fail/trace_read.rs:LL:CC
11+
note: inside `main`
12+
--> tests/native-lib/fail/trace_read.rs:LL:CC
13+
|
14+
LL | unexposed_reachable_alloc();
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
17+
18+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
19+
20+
error: aborting due to 1 previous error
21+

tests/native-lib/fail/trace_write.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@only-target: linux
2+
//@only-target: gnu
3+
//@only-target: x86
4+
//@only-on-host
5+
//@compile-flags: -Zmiri-permissive-provenance -Zmiri-native-lib-enable-tracing
6+
7+
extern "C" {
8+
fn init_n(n: i32, ptr: *mut u8);
9+
}
10+
11+
fn main() {
12+
partial_init();
13+
}
14+
15+
fn partial_init() {
16+
let mut slice: [u8; 10];
17+
unsafe {
18+
init_n(5, (&raw mut slice).cast());
19+
assert!(slice[3] == 0);
20+
println!("{}", slice[6]);
21+
}
22+
}

tests/native-lib/ptr_write_access.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,11 @@ EXPORT void set_shared_mem(int32_t** ptr) {
107107
EXPORT void init_ptr_stored_in_shared_mem(int32_t val) {
108108
**shared_place = val;
109109
}
110+
111+
/* Test: partial_init */
112+
113+
EXPORT void init_n(int32_t n, char* ptr) {
114+
for (int i=0; i<n; i++) {
115+
*ptr = 0;
116+
}
117+
}

tests/native-lib/scalar_arguments.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ EXPORT int64_t add_short_to_long(int16_t x, int64_t y) {
3434
int32_t not_exported(void) {
3535
return 0;
3636
}
37+
38+
EXPORT void do_nothing(void) {
39+
return;
40+
}

0 commit comments

Comments
 (0)