Skip to content

Commit 0b04a2d

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

File tree

10 files changed

+127
-145
lines changed

10 files changed

+127
-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: 57 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,67 @@ 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+
// Now grab the byte-level representation of the argument. If there's a pointer in there, we
295+
// rely on its interpreter-level provenance being exposed in the `visit_reachable_allocs`
296+
// callback.
283297
let bytes = match v.as_mplace_or_imm() {
284298
either::Either::Left(mplace) => {
285299
// Get the alloc id corresponding to this mplace, alongside
286300
// a pointer that's offset to point to this particular
287301
// mplace (not one at the base addr of the allocation).
288-
let mplace_ptr = mplace.ptr();
289302
let sz = mplace.layout.size.bytes_usize();
290303
if sz == 0 {
291304
throw_unsup_format!("attempting to pass a ZST over FFI");
292305
}
293-
let (id, ofs, _) = this.ptr_get_alloc_id(mplace_ptr, sz.try_into().unwrap())?;
306+
let (id, ofs, _) = this.ptr_get_alloc_id(mplace.ptr(), sz.try_into().unwrap())?;
294307
let ofs = ofs.bytes_usize();
295-
// Expose all provenances in the allocation within the byte
296-
// range of the struct, if any.
308+
let range = ofs..ofs.strict_add(sz);
309+
// Expose all provenances in the allocation within the byte range of the struct, if
310+
// any. These pointers are being directly passed to native code by-value.
297311
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-
))
312+
for prov in alloc.provenance().get_range(this, range.clone().into()) {
313+
expose(prov)?;
311314
}
315+
// Read the bytes that make up this argument. We cannot use the normal getter as
316+
// those would fail if any part of the argument is uninitialized. Native code
317+
// is kind of outside the interpreter, after all...
318+
Box::from(alloc.inspect_with_uninit_and_ptr_outside_interpreter(range))
312319
}
313320
either::Either::Right(imm) => {
321+
let mut bytes: Box<[u8]> = vec![0; imm.layout.size.bytes_usize()].into();
322+
314323
// A little helper to write scalars to our byte array.
315-
let write_scalar = |this: &MiriInterpCx<'tcx>, sc: Scalar, bytes: &mut [u8]| {
324+
let mut write_scalar = |this: &MiriInterpCx<'tcx>, sc: Scalar, pos: usize| {
316325
// If a scalar is a pointer, then expose its provenance.
317326
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)?;
327+
expose(p.provenance)?;
321328
}
322-
// `bytes[0]` should be the first byte we want to write to.
323329
write_target_uint(
324330
this.data_layout().endian,
325-
&mut bytes[..sc.size().bytes_usize()],
331+
&mut bytes[pos..][..sc.size().bytes_usize()],
326332
sc.to_scalar_int()?.to_bits_unchecked(),
327333
)
328334
.unwrap();
329335
interp_ok(())
330336
};
331337

332-
let mut bytes: Box<[u8]> =
333-
(0..imm.layout.size.bytes_usize()).map(|_| 0u8).collect();
334-
338+
// Write the scalar into the `bytes` buffer.
335339
match *imm {
336-
Immediate::Scalar(sc) => write_scalar(this, sc, &mut bytes)?,
340+
Immediate::Scalar(sc) => write_scalar(this, sc, 0)?,
337341
Immediate::ScalarPair(sc_first, sc_second) => {
338-
// The first scalar has an offset of zero.
342+
// The first scalar has an offset of zero; compute the offset of the 2nd.
339343
let ofs_second = {
340344
let rustc_abi::BackendRepr::ScalarPair(a, b) = imm.layout.backend_repr
341345
else {
@@ -348,11 +352,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
348352
a.size(this).align_to(b.align(this).abi).bytes_usize()
349353
};
350354

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

358363
bytes
@@ -381,7 +386,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
381386

382387
let this = self.eval_context_ref();
383388
let mut fields = vec![];
384-
for field in adt_def.all_fields() {
389+
for field in &adt_def.non_enum_variant().fields {
385390
fields.push(this.ty_to_ffitype(field.ty(*this.tcx, args))?);
386391
}
387392

@@ -407,20 +412,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
407412
_ => throw_unsup_format!("unsupported argument type for native call: {}", ty),
408413
})
409414
}
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-
}
424415
}
425416

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

474465
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.
466+
// Expose all provenances in this allocation, since the native code can do
467+
// $whatever. Can be skipped when tracing; in that case we'll expose just the
468+
// actually-read parts later.
477469
for prov in alloc.provenance().provenances() {
478470
this.expose_provenance(prov)?;
479471
}
@@ -483,7 +475,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
483475
if info.mutbl.is_mut() {
484476
let (alloc, cx) = this.get_alloc_raw_mut(alloc_id)?;
485477
// 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.
478+
// We can skip that when tracing; in that case we'll later do that only for the
479+
// memory that got actually written.
487480
if !tracing {
488481
alloc.process_native_write(&cx.tcx, None);
489482
}

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)