Skip to content

Commit b539d51

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 b539d51

File tree

1 file changed

+187
-4
lines changed

1 file changed

+187
-4
lines changed

src/shims/native_lib/mod.rs

Lines changed: 187 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,12 @@ 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(
307+
&self,
308+
v: &OpTy<'tcx>,
309+
tracing: bool,
310+
link_name: &Symbol,
311+
) -> InterpResult<'tcx, OwnedArg> {
289312
let this = self.eval_context_ref();
290313

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

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

0 commit comments

Comments
 (0)