Skip to content

Commit eff2b42

Browse files
committed
some refactoring and cleanup
1 parent 4b82474 commit eff2b42

File tree

10 files changed

+128
-145
lines changed

10 files changed

+128
-145
lines changed

src/shims/native_lib/ffi.rs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1+
//! Support code for dealing with libffi.
2+
13
use libffi::low::CodePtr;
24
use libffi::middle::{Arg as ArgPtr, Cif, Type as FfiType};
35

46
/// Perform the actual FFI call.
57
///
6-
/// SAFETY: The safety invariants of the foreign function being called must be
7-
/// upheld (if any).
8+
/// # Safety
9+
///
10+
/// The safety invariants of the foreign function being called must be upheld (if any).
811
pub unsafe fn call<R: libffi::high::CType>(fun: CodePtr, args: &mut [OwnedArg]) -> R {
9-
let mut arg_tys = vec![];
10-
let mut arg_ptrs = vec![];
11-
for arg in args {
12-
arg_tys.push(arg.take_ty());
13-
arg_ptrs.push(arg.ptr())
14-
}
15-
let cif = Cif::new(arg_tys, R::reify().into_middle());
12+
let arg_ptrs: Vec<_> = args.iter().map(|arg| arg.ptr()).collect();
13+
let cif = Cif::new(args.iter_mut().map(|arg| arg.ty.take().unwrap()), R::reify().into_middle());
1614
// SAFETY: Caller upholds that the function is safe to call, and since we
1715
// were passed a slice reference we know the `OwnedArg`s won't have been
1816
// dropped by this point.
@@ -34,13 +32,7 @@ impl OwnedArg {
3432
Self { ty: Some(ty), bytes }
3533
}
3634

37-
/// Gets the libffi type descriptor for this argument. Should only be
38-
/// called once on a given `OwnedArg`.
39-
fn take_ty(&mut self) -> FfiType {
40-
self.ty.take().unwrap()
41-
}
42-
43-
/// Instantiates a libffi argument pointer pointing to this argument's bytes.
35+
/// Creates a libffi argument pointer pointing to this argument's bytes.
4436
/// NB: Since `libffi::middle::Arg` ignores the lifetime of the reference
4537
/// it's derived from, it is up to the caller to ensure the `OwnedArg` is
4638
/// not dropped before unsafely calling `libffi::middle::Cif::call()`!

src/shims/native_lib/mod.rs

Lines changed: 58 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
7676
&mut self,
7777
link_name: Symbol,
7878
dest: &MPlaceTy<'tcx>,
79-
ptr: CodePtr,
79+
fun: CodePtr,
8080
libffi_args: &mut [OwnedArg],
8181
) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<MemEvents>)> {
8282
let this = self.eval_context_mut();
@@ -95,54 +95,54 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
9595
// Unsafe because of the call to native code.
9696
// Because this is calling a C function it is not necessarily sound,
9797
// but there is no way around this and we've checked as much as we can.
98-
let x = unsafe { ffi::call::<i8>(ptr, libffi_args) };
98+
let x = unsafe { ffi::call::<i8>(fun, libffi_args) };
9999
Scalar::from_i8(x)
100100
}
101101
ty::Int(IntTy::I16) => {
102-
let x = unsafe { ffi::call::<i16>(ptr, libffi_args) };
102+
let x = unsafe { ffi::call::<i16>(fun, libffi_args) };
103103
Scalar::from_i16(x)
104104
}
105105
ty::Int(IntTy::I32) => {
106-
let x = unsafe { ffi::call::<i32>(ptr, libffi_args) };
106+
let x = unsafe { ffi::call::<i32>(fun, libffi_args) };
107107
Scalar::from_i32(x)
108108
}
109109
ty::Int(IntTy::I64) => {
110-
let x = unsafe { ffi::call::<i64>(ptr, libffi_args) };
110+
let x = unsafe { ffi::call::<i64>(fun, libffi_args) };
111111
Scalar::from_i64(x)
112112
}
113113
ty::Int(IntTy::Isize) => {
114-
let x = unsafe { ffi::call::<isize>(ptr, libffi_args) };
114+
let x = unsafe { ffi::call::<isize>(fun, libffi_args) };
115115
Scalar::from_target_isize(x.try_into().unwrap(), this)
116116
}
117117
// uints
118118
ty::Uint(UintTy::U8) => {
119-
let x = unsafe { ffi::call::<u8>(ptr, libffi_args) };
119+
let x = unsafe { ffi::call::<u8>(fun, libffi_args) };
120120
Scalar::from_u8(x)
121121
}
122122
ty::Uint(UintTy::U16) => {
123-
let x = unsafe { ffi::call::<u16>(ptr, libffi_args) };
123+
let x = unsafe { ffi::call::<u16>(fun, libffi_args) };
124124
Scalar::from_u16(x)
125125
}
126126
ty::Uint(UintTy::U32) => {
127-
let x = unsafe { ffi::call::<u32>(ptr, libffi_args) };
127+
let x = unsafe { ffi::call::<u32>(fun, libffi_args) };
128128
Scalar::from_u32(x)
129129
}
130130
ty::Uint(UintTy::U64) => {
131-
let x = unsafe { ffi::call::<u64>(ptr, libffi_args) };
131+
let x = unsafe { ffi::call::<u64>(fun, libffi_args) };
132132
Scalar::from_u64(x)
133133
}
134134
ty::Uint(UintTy::Usize) => {
135-
let x = unsafe { ffi::call::<usize>(ptr, libffi_args) };
135+
let x = unsafe { ffi::call::<usize>(fun, libffi_args) };
136136
Scalar::from_target_usize(x.try_into().unwrap(), this)
137137
}
138138
// Functions with no declared return type (i.e., the default return)
139139
// have the output_type `Tuple([])`.
140140
ty::Tuple(t_list) if (*t_list).deref().is_empty() => {
141-
unsafe { ffi::call::<()>(ptr, libffi_args) };
141+
unsafe { ffi::call::<()>(fun, libffi_args) };
142142
return interp_ok(ImmTy::uninit(dest.layout));
143143
}
144144
ty::RawPtr(..) => {
145-
let x = unsafe { ffi::call::<*const ()>(ptr, libffi_args) };
145+
let x = unsafe { ffi::call::<*const ()>(fun, libffi_args) };
146146
let ptr = StrictPointer::new(Provenance::Wildcard, Size::from_bytes(x.addr()));
147147
Scalar::from_pointer(ptr, this)
148148
}
@@ -279,63 +279,68 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
279279
// of extra work for types that aren't supported yet.
280280
let ty = this.ty_to_ffitype(v.layout.ty)?;
281281

282-
// Now grab the bytes of the argument.
282+
// Helper to print a warning when a pointer is shared with the native code.
283+
let expose = |prov: Provenance| -> InterpResult<'tcx> {
284+
// The first time this happens, print a warning.
285+
if !this.machine.native_call_mem_warned.replace(true) {
286+
// Newly set, so first time we get here.
287+
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
288+
}
289+
290+
this.expose_provenance(prov)?;
291+
interp_ok(())
292+
};
293+
294+
// Compute the byte-level representation of the argument. If there's a pointer in there, we
295+
// expose it inside the AM. Later in `visit_reachable_allocs`, the "meta"-level provenance
296+
// for accessing the pointee gets exposed; this is crucial to justify the C code effectively
297+
// casting the integer in `byte` to a pointer and using that.
283298
let bytes = match v.as_mplace_or_imm() {
284299
either::Either::Left(mplace) => {
285300
// Get the alloc id corresponding to this mplace, alongside
286301
// a pointer that's offset to point to this particular
287302
// mplace (not one at the base addr of the allocation).
288-
let mplace_ptr = mplace.ptr();
289303
let sz = mplace.layout.size.bytes_usize();
290304
if sz == 0 {
291305
throw_unsup_format!("attempting to pass a ZST over FFI");
292306
}
293-
let (id, ofs, _) = this.ptr_get_alloc_id(mplace_ptr, sz.try_into().unwrap())?;
307+
let (id, ofs, _) = this.ptr_get_alloc_id(mplace.ptr(), sz.try_into().unwrap())?;
294308
let ofs = ofs.bytes_usize();
295-
// Expose all provenances in the allocation within the byte
296-
// range of the struct, if any.
309+
let range = ofs..ofs.strict_add(sz);
310+
// Expose all provenances in the allocation within the byte range of the struct, if
311+
// any. These pointers are being directly passed to native code by-value.
297312
let alloc = this.get_alloc_raw(id)?;
298-
let alloc_ptr = this.get_alloc_bytes_unchecked_raw(id)?;
299-
for prov in alloc.provenance().get_range(this, (ofs..ofs.strict_add(sz)).into()) {
300-
this.expose_provenance(prov)?;
301-
}
302-
// SAFETY: We know for sure that at alloc_ptr + ofs the next layout.size
303-
// bytes are part of this allocation and initialised. They might be marked
304-
// as uninit in Miri, but all bytes returned by `MiriAllocBytes` are
305-
// initialised.
306-
unsafe {
307-
Box::from(std::slice::from_raw_parts(
308-
alloc_ptr.add(ofs),
309-
mplace.layout.size.bytes_usize(),
310-
))
313+
for prov in alloc.provenance().get_range(this, range.clone().into()) {
314+
expose(prov)?;
311315
}
316+
// Read the bytes that make up this argument. We cannot use the normal getter as
317+
// those would fail if any part of the argument is uninitialized. Native code
318+
// is kind of outside the interpreter, after all...
319+
Box::from(alloc.inspect_with_uninit_and_ptr_outside_interpreter(range))
312320
}
313321
either::Either::Right(imm) => {
322+
let mut bytes: Box<[u8]> = vec![0; imm.layout.size.bytes_usize()].into();
323+
314324
// A little helper to write scalars to our byte array.
315-
let write_scalar = |this: &MiriInterpCx<'tcx>, sc: Scalar, bytes: &mut [u8]| {
325+
let mut write_scalar = |this: &MiriInterpCx<'tcx>, sc: Scalar, pos: usize| {
316326
// If a scalar is a pointer, then expose its provenance.
317327
if let interpret::Scalar::Ptr(p, _) = sc {
318-
// This relies on the `expose_provenance` in the `visit_reachable_allocs` callback
319-
// below to expose the actual interpreter-level allocation.
320-
this.expose_and_warn(Some(p.provenance), tracing)?;
328+
expose(p.provenance)?;
321329
}
322-
// `bytes[0]` should be the first byte we want to write to.
323330
write_target_uint(
324331
this.data_layout().endian,
325-
&mut bytes[..sc.size().bytes_usize()],
332+
&mut bytes[pos..][..sc.size().bytes_usize()],
326333
sc.to_scalar_int()?.to_bits_unchecked(),
327334
)
328335
.unwrap();
329336
interp_ok(())
330337
};
331338

332-
let mut bytes: Box<[u8]> =
333-
(0..imm.layout.size.bytes_usize()).map(|_| 0u8).collect();
334-
339+
// Write the scalar into the `bytes` buffer.
335340
match *imm {
336-
Immediate::Scalar(sc) => write_scalar(this, sc, &mut bytes)?,
341+
Immediate::Scalar(sc) => write_scalar(this, sc, 0)?,
337342
Immediate::ScalarPair(sc_first, sc_second) => {
338-
// The first scalar has an offset of zero.
343+
// The first scalar has an offset of zero; compute the offset of the 2nd.
339344
let ofs_second = {
340345
let rustc_abi::BackendRepr::ScalarPair(a, b) = imm.layout.backend_repr
341346
else {
@@ -348,11 +353,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
348353
a.size(this).align_to(b.align(this).abi).bytes_usize()
349354
};
350355

351-
write_scalar(this, sc_first, &mut bytes)?;
352-
write_scalar(this, sc_second, &mut bytes[ofs_second..])?;
356+
write_scalar(this, sc_first, 0)?;
357+
write_scalar(this, sc_second, ofs_second)?;
358+
}
359+
Immediate::Uninit => {
360+
// Nothing to write.
353361
}
354-
Immediate::Uninit =>
355-
span_bug!(this.cur_span(), "op_to_ffi_arg: argument is uninit: {:#?}", imm),
356362
}
357363

358364
bytes
@@ -381,7 +387,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
381387

382388
let this = self.eval_context_ref();
383389
let mut fields = vec![];
384-
for field in adt_def.all_fields() {
390+
for field in &adt_def.non_enum_variant().fields {
385391
fields.push(this.ty_to_ffitype(field.ty(*this.tcx, args))?);
386392
}
387393

@@ -407,20 +413,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
407413
_ => throw_unsup_format!("unsupported argument type for native call: {}", ty),
408414
})
409415
}
410-
411-
fn expose_and_warn(&self, prov: Option<Provenance>, tracing: bool) -> InterpResult<'tcx> {
412-
let this = self.eval_context_ref();
413-
if let Some(prov) = prov {
414-
// The first time this happens, print a warning.
415-
if !this.machine.native_call_mem_warned.replace(true) {
416-
// Newly set, so first time we get here.
417-
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
418-
}
419-
420-
this.expose_provenance(prov)?;
421-
};
422-
interp_ok(())
423-
}
424416
}
425417

426418
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -472,8 +464,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
472464
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
473465

474466
if !tracing {
475-
// Expose all provenances in this allocation, since the native code can do $whatever.
476-
// Can be skipped when tracing; in that case we'll expose just the actually-read parts later.
467+
// Expose all provenances in this allocation, since the native code can do
468+
// $whatever. Can be skipped when tracing; in that case we'll expose just the
469+
// actually-read parts later.
477470
for prov in alloc.provenance().provenances() {
478471
this.expose_provenance(prov)?;
479472
}
@@ -483,7 +476,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
483476
if info.mutbl.is_mut() {
484477
let (alloc, cx) = this.get_alloc_raw_mut(alloc_id)?;
485478
// These writes could initialize everything and wreck havoc with the pointers.
486-
// We can skip that when tracing; in that case we'll later do that only for the memory that got actually written.
479+
// We can skip that when tracing; in that case we'll later do that only for the
480+
// memory that got actually written.
487481
if !tracing {
488482
alloc.process_native_write(&cx.tcx, None);
489483
}

tests/native-lib/aggregate_arguments.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
// See comments in build_native_lib()
44
#define EXPORT __attribute__((visibility("default")))
55

6+
/* Test: fail/pass_struct_expose_only_range */
7+
8+
typedef struct HasPointer {
9+
uint8_t *ptr;
10+
} HasPointer;
11+
12+
EXPORT uint8_t access_struct_ptr(const HasPointer s) {
13+
return *s.ptr;
14+
}
15+
616
/* Test: test_pass_struct */
717

818
typedef struct PassMe {

tests/native-lib/fail/multi_struct_alloc.rs

Lines changed: 0 additions & 21 deletions
This file was deleted.

tests/native-lib/fail/multi_struct_alloc.stderr

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@compile-flags: -Zmiri-permissive-provenance
2+
3+
#[repr(C)]
4+
#[derive(Copy, Clone)]
5+
struct HasPointer {
6+
ptr: *const u8,
7+
}
8+
9+
extern "C" {
10+
fn access_struct_ptr(s: HasPointer) -> u8;
11+
}
12+
13+
fn main() {
14+
let structs = vec![HasPointer { ptr: &0 }, HasPointer { ptr: &1 }];
15+
unsafe {
16+
let r = access_struct_ptr(structs[1]);
17+
assert_eq!(r, 1);
18+
// There are two pointers stored in the allocation backing `structs`; ensure
19+
// we only exposed the one that was actually passed to C.
20+
let _val = *std::ptr::with_exposed_provenance::<u8>(structs[1].ptr.addr()); // fine, ptr got sent to C
21+
let _val = *std::ptr::with_exposed_provenance::<u8>(structs[0].ptr.addr()); //~ ERROR: memory access failed
22+
};
23+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
warning: sharing memory with a native function called via FFI
2+
--> tests/native-lib/fail/pass_struct_expose_only_range.rs:LL:CC
3+
|
4+
LL | let r = access_struct_ptr(structs[1]);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ sharing memory with a native function
6+
|
7+
= help: when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory
8+
= help: in particular, Miri assumes that the native call initializes all memory it has access to
9+
= help: Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory
10+
= help: what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free
11+
= note: BACKTRACE:
12+
= note: inside `main` at tests/native-lib/fail/pass_struct_expose_only_range.rs:LL:CC
13+
14+
error: Undefined Behavior: memory access failed: attempting to access 1 byte, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
15+
--> tests/native-lib/fail/pass_struct_expose_only_range.rs:LL:CC
16+
|
17+
LL | let _val = *std::ptr::with_exposed_provenance::<u8>(structs[0].ptr.addr());
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
19+
|
20+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
21+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
22+
= note: BACKTRACE:
23+
= note: inside `main` at tests/native-lib/fail/pass_struct_expose_only_range.rs:LL:CC
24+
25+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
26+
27+
error: aborting due to 1 previous error; 1 warning emitted
28+

tests/native-lib/pass/aggregate_arguments.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ fn main() {
55

66
/// Test passing a basic struct as an argument.
77
fn test_pass_struct() {
8+
// Exactly two fields, so that we hit the ScalarPair case.
89
#[repr(C)]
910
struct PassMe {
1011
value: i32,

0 commit comments

Comments
 (0)