Skip to content

Commit e553417

Browse files
committed
Basic support for FFI callbacks
This commit adds basic support for FFI callbacks by registering a shim function via libffi. This shim function currently only notes if it's actually called and registers an error for these cases. The main motivation for this is to prevent miri segfaulting as described in #4639. In the future miri could try to continue execution in the registered callback, although as far as I understand Ralf that is no easy problem.
1 parent b39f1e7 commit e553417

File tree

1 file changed

+184
-4
lines changed

1 file changed

+184
-4
lines changed

src/shims/native_lib/mod.rs

Lines changed: 184 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! Implements calling functions from a native library.
22
3+
use std::borrow::Cow;
4+
use std::cell::RefCell;
35
use std::ops::Deref;
6+
use std::os::raw::c_void;
47
use std::sync::atomic::AtomicBool;
58

69
use libffi::low::CodePtr;
@@ -16,6 +19,14 @@ use self::helpers::ToSoft;
1619

1720
mod ffi;
1821

22+
struct CallbackError {
23+
message: Cow<'static, str>,
24+
}
25+
26+
thread_local! {
27+
pub static CALLBACK_MESSAGES: RefCell<Vec<CallbackError>> = RefCell::new(Vec::new());
28+
}
29+
1930
#[cfg_attr(
2031
not(all(
2132
target_os = "linux",
@@ -92,6 +103,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
92103
let alloc = ();
93104

94105
trace::Supervisor::do_ffi(alloc, || {
106+
// clear the callback error buffer
107+
CALLBACK_MESSAGES.with_borrow_mut(|c| c.clear());
95108
// Call the function (`ptr`) with arguments `libffi_args`, and obtain the return value
96109
// as the specified primitive integer type
97110
let scalar = match dest.layout.ty.kind() {
@@ -166,6 +179,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
166179
))
167180
.into(),
168181
};
182+
let callback_error_messages = CALLBACK_MESSAGES.take();
183+
if !callback_error_messages.is_empty() {
184+
let first = callback_error_messages.first().unwrap();
185+
return Err(err_unsup_format!("{}", first.message)).into();
186+
}
169187
interp_ok(ImmTy::from_scalar(scalar, dest.layout))
170188
})
171189
}
@@ -285,7 +303,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
285303

286304
/// Extract the value from the result of reading an operand from the machine
287305
/// and convert it to a `OwnedArg`.
288-
fn op_to_ffi_arg(&self, v: &OpTy<'tcx>, tracing: bool) -> InterpResult<'tcx, OwnedArg> {
306+
fn op_to_ffi_arg(&self, v: &OpTy<'tcx>, tracing: bool, link_name: &Symbol) -> InterpResult<'tcx, OwnedArg> {
289307
let this = self.eval_context_ref();
290308

291309
// This should go first so that we emit unsupported before doing a bunch
@@ -310,6 +328,46 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
310328
// casting the integer in `byte` to a pointer and using that.
311329
let bytes = match v.as_mplace_or_imm() {
312330
either::Either::Left(mplace) => {
331+
let ptr_overwrite = match v.layout.ty.kind() {
332+
ty::Adt(_adt_def, args) =>
333+
if let ty::FnPtr(fn_ptr, _header) =
334+
args.type_at(0).kind()
335+
{
336+
let args = fn_ptr
337+
.skip_binder()
338+
.inputs()
339+
.into_iter()
340+
.map(|i| {
341+
let layout = this.layout_of(i.clone())?;
342+
this.ty_to_ffitype(layout)
343+
})
344+
.collect::<InterpResult<'_, Vec<_>>>()?;
345+
let res_type = fn_ptr.skip_binder().output();
346+
let res_type = {
347+
let layout = this.layout_of(res_type)?;
348+
this.ty_to_ffitype(layout)?
349+
};
350+
let closure_builder = libffi::middle::Builder::new()
351+
.args(args)
352+
.res(res_type)
353+
.abi(libffi::raw::ffi_abi_FFI_UNIX64);
354+
let data = CallbackData {
355+
args: fn_ptr.skip_binder().inputs().to_vec(),
356+
result: fn_ptr.skip_binder().output(),
357+
this,
358+
link_name: link_name.clone(),
359+
ty: v.layout.ty,
360+
};
361+
// todo: leaking is likely not optimal here
362+
let data = Box::leak(Box::new(data));
363+
364+
let closure = closure_builder.into_closure(callback_callback, data);
365+
Some(closure)
366+
} else {
367+
None
368+
},
369+
_ => None,
370+
};
313371
// Get the alloc id corresponding to this mplace, alongside
314372
// a pointer that's offset to point to this particular
315373
// mplace (not one at the base addr of the allocation).
@@ -330,7 +388,35 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
330388
// Read the bytes that make up this argument. We cannot use the normal getter as
331389
// those would fail if any part of the argument is uninitialized. Native code
332390
// is kind of outside the interpreter, after all...
333-
Box::from(alloc.inspect_with_uninit_and_ptr_outside_interpreter(range))
391+
let ret: Box<[u8]> =
392+
Box::from(alloc.inspect_with_uninit_and_ptr_outside_interpreter(range));
393+
if ret.iter().any(|b| *b != 0)
394+
&& let Some(ptr_overwrite) = ptr_overwrite
395+
{
396+
// we need to leak the closure here as we don't know when it's actually called
397+
// I'm not sure if it's possible to have a better solution for that
398+
let ptr_overwrite = Box::leak(Box::new(ptr_overwrite));
399+
400+
// we get a **reference** to a function ptr here
401+
// (The actual argument type doesn't matter)
402+
let ptr = unsafe {
403+
ptr_overwrite.instantiate_code_ptr::<unsafe extern "C" fn(*const c_void)>()
404+
};
405+
// so deref away the reference
406+
let ptr = *ptr;
407+
// cast it to void as the actual function type doesn't matter
408+
let ptr = ptr as *const c_void;
409+
// get a the address as usize to write it into the
410+
// right memory location
411+
let bytes = ptr.addr();
412+
// bytes are in native endian, as that's literally
413+
// the definition of native endian
414+
let bytes = usize::to_ne_bytes(bytes);
415+
// return the bytes of the ptr
416+
Box::from(bytes)
417+
} else {
418+
ret
419+
}
334420
}
335421
either::Either::Right(imm) => {
336422
let mut bytes: Box<[u8]> = vec![0; imm.layout.size.bytes_usize()].into();
@@ -439,7 +525,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
439525
interp_ok(match layout.ty.kind() {
440526
// Scalar types have already been handled above.
441527
ty::Adt(adt_def, args) => self.adt_to_ffitype(layout.ty, *adt_def, args)?,
442-
_ => throw_unsup_format!("unsupported argument type for native call: {}", layout.ty),
528+
// Functions with no declared return type (i.e., the default return)
529+
// have the output_type `Tuple([])`.
530+
ty::Tuple(t_list) if (*t_list).deref().is_empty() => FfiType::void(),
531+
_ => {
532+
throw_unsup_format!("unsupported argument type for native call: {}", layout.ty)
533+
}
443534
})
444535
}
445536
}
@@ -473,7 +564,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
473564
// Get the function arguments, copy them, and prepare the type descriptions.
474565
let mut libffi_args = Vec::<OwnedArg>::with_capacity(args.len());
475566
for arg in args.iter() {
476-
libffi_args.push(this.op_to_ffi_arg(arg, tracing)?);
567+
libffi_args.push(this.op_to_ffi_arg(arg, tracing, &link_name)?);
477568
}
478569

479570
// Prepare all exposed memory (both previously exposed, and just newly exposed since a
@@ -536,3 +627,92 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
536627
interp_ok(true)
537628
}
538629
}
630+
631+
struct CallbackData<'a, 'tcx> {
632+
args: Vec<Ty<'tcx>>,
633+
result: Ty<'tcx>,
634+
this: &'a MiriInterpCx<'tcx>,
635+
link_name: Symbol,
636+
ty: Ty<'tcx>,
637+
}
638+
639+
unsafe extern "C" fn callback_callback(
640+
cif: &libffi::low::ffi_cif,
641+
result: &mut c_void,
642+
args: *const *const c_void,
643+
infos: &CallbackData<'_, '_>,
644+
) {
645+
debug_assert_eq!(cif.nargs as usize, infos.args.len());
646+
let mut rust_args = Vec::with_capacity(infos.args.len());
647+
// cast away the pointer to pointer
648+
let mut args = args as *const c_void;
649+
for arg in &infos.args {
650+
let scalar = match arg.kind() {
651+
ty::RawPtr(..) => {
652+
let ptr = StrictPointer::new(Provenance::Wildcard, Size::from_bytes(args.addr()));
653+
args = unsafe { args.offset(1) };
654+
Scalar::from_pointer(ptr, infos.this)
655+
}
656+
// the other types
657+
_ => todo!(),
658+
};
659+
rust_args.push(scalar);
660+
}
661+
662+
CALLBACK_MESSAGES.with_borrow_mut(|msgs| {
663+
msgs.push(CallbackError {
664+
message: format!("Tried to call a function pointer via FFI boundary. \
665+
That's not supported yet by miri\n This function pointer was registered by a call to `{}` \
666+
using an argument of the type `{}`", infos.link_name, infos.ty)
667+
.into(),
668+
});
669+
});
670+
671+
// write here the output
672+
// For now we just try to write some dummy output
673+
// by using some "reasonable" default values
674+
// to prevent crashing
675+
match infos.result.kind() {
676+
ty::RawPtr(..) => {
677+
write_helper::<*mut c_void>(result, std::ptr::null_mut());
678+
}
679+
ty::Int(IntTy::I8) => {
680+
write_helper::<i8>(result, 0);
681+
}
682+
ty::Int(IntTy::I16) => {
683+
write_helper::<i32>(result, 0);
684+
}
685+
ty::Int(IntTy::I32) => {
686+
write_helper::<i32>(result, 0);
687+
}
688+
ty::Int(IntTy::I64) => {
689+
write_helper::<i64>(result, 0);
690+
}
691+
ty::Int(IntTy::Isize) => {
692+
write_helper::<isize>(result, 0);
693+
}
694+
ty::Uint(UintTy::U8) => {
695+
write_helper::<u8>(result, 0);
696+
}
697+
ty::Uint(UintTy::U16) => {
698+
write_helper::<u16>(result, 0);
699+
}
700+
ty::Uint(UintTy::U32) => {
701+
write_helper::<u32>(result, 0);
702+
}
703+
ty::Uint(UintTy::U64) => {
704+
write_helper::<u64>(result, 0);
705+
}
706+
ty::Uint(UintTy::Usize) => {
707+
write_helper::<usize>(result, 0);
708+
}
709+
// unsure how to handle that at allow
710+
// Just do nothing for now?
711+
_ => {}
712+
};
713+
}
714+
715+
fn write_helper<T>(ptr: &mut c_void, value: T) {
716+
let ptr = (ptr as *mut c_void) as *mut T;
717+
unsafe { std::ptr::write(ptr, value) };
718+
}

0 commit comments

Comments
 (0)