Skip to content
This repository was archived by the owner on Mar 24, 2022. It is now read-only.

Commit bce2247

Browse files
committed
[lucet-runtime] implement counter for unwinding nested hostcalls
This doesn't work yet, because the prologue for guest functions is trying to push a frame pointer. At least in the new test added in this commit, the value of `rbp` at entry to the callback `guest_func` is zero, which screws up how the debugger (and presumably the unwinding runtime) interpret the stack.
1 parent 9389a4c commit bce2247

File tree

7 files changed

+142
-2
lines changed

7 files changed

+142
-2
lines changed

lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,38 @@ macro_rules! lucet_hostcalls {
3838
vmctx_raw: *mut $crate::vmctx::lucet_vmctx,
3939
$( $arg: $arg_ty ),*
4040
) -> $ret_ty {
41+
use $crate::vmctx::VmctxInternal;
42+
4143
#[inline(always)]
4244
unsafe fn hostcall_impl(
4345
$vmctx: &mut $crate::vmctx::Vmctx,
4446
$( $arg : $arg_ty ),*
4547
) -> $ret_ty {
4648
$($body)*
4749
}
50+
51+
let mut vmctx = $crate::vmctx::Vmctx::from_raw(vmctx_raw);
52+
53+
// increment the nesting level before calling the implementation...
54+
vmctx.increment_hostcall_nesting();
4855
let res = std::panic::catch_unwind(move || {
4956
hostcall_impl(&mut $crate::vmctx::Vmctx::from_raw(vmctx_raw), $( $arg ),*)
5057
});
58+
// and decrement it afterwards, whether or not there was a panic
59+
vmctx.decrement_hostcall_nesting();
60+
61+
// get this as a stack variable so that vmctx doesn't leak if we terminate below
62+
let in_nested_hostcall = vmctx.in_nested_hostcall();
63+
drop(vmctx);
64+
5165
match res {
5266
Ok(res) => res,
5367
Err(e) => {
68+
// only terminate once we've unwound through all hostcall segments of the
69+
// guest stack
70+
if in_nested_hostcall {
71+
std::panic::resume_unwind(e);
72+
}
5473
if let Some(details) = e.downcast_ref::<$crate::instance::TerminationDetails>() {
5574
let mut vmctx = $crate::vmctx::Vmctx::from_raw(vmctx_raw);
5675
vmctx.terminate_no_unwind(details.clone());

lucet-runtime/lucet-runtime-internals/src/instance.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ pub struct Instance {
216216
/// Pointer to the function used as the entrypoint (for use in backtraces)
217217
entrypoint: Option<FunctionPointer>,
218218

219+
/// The number of nested hostcalls currently present on the guest stack.
220+
///
221+
/// Primarily used when implementing instance termination, this represents the number of times
222+
/// unwinding must continue in order to unwind through all hostcall segments of the guest stack.
223+
///
224+
/// For example, if the guest calls `host_fn1()`, which in turn calls back into `guest_fn1()`,
225+
/// which calls `host_fn2()`, the value of this field must be `2` while `host_fn2()` is
226+
/// executing. This number is automatically managed by the `lucet_hostcalls!` macro.
227+
pub(crate) hostcall_nesting: usize,
228+
219229
/// `_padding` must be the last member of the structure.
220230
/// This marks where the padding starts to make the structure exactly 4096 bytes long.
221231
/// It is also used to compute the size of the structure up to that point, i.e. without padding.
@@ -517,6 +527,7 @@ impl Instance {
517527
c_fatal_handler: None,
518528
signal_handler: Box::new(signal_handler_none) as Box<SignalHandler>,
519529
entrypoint: None,
530+
hostcall_nesting: 0,
520531
_padding: (),
521532
};
522533
inst.set_globals_ptr(globals_ptr);

lucet-runtime/lucet-runtime-internals/src/vmctx.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ pub trait VmctxInternal {
5858
/// you could not use orthogonal `&mut` refs that come from `Vmctx`, like the heap or
5959
/// terminating the instance.
6060
unsafe fn instance_mut(&self) -> &mut Instance;
61+
62+
/// Increment the hostcall nesting level.
63+
///
64+
/// This must be done whenever entering a hostcall implementation.
65+
fn increment_hostcall_nesting(&self);
66+
67+
/// Decrement the hostcall nesting level.
68+
///
69+
/// This must be done whenever a hostcall implementation returns, but before any unwinding logic
70+
/// is evaluated.
71+
fn decrement_hostcall_nesting(&self);
72+
73+
/// Returns `true` if there are hostcall stack segments present on the guest stack.
74+
fn in_nested_hostcall(&self) -> bool;
6175
}
6276

6377
impl VmctxInternal for Vmctx {
@@ -68,6 +82,27 @@ impl VmctxInternal for Vmctx {
6882
unsafe fn instance_mut(&self) -> &mut Instance {
6983
instance_from_vmctx(self.vmctx)
7084
}
85+
86+
fn increment_hostcall_nesting(&self) {
87+
let inst = unsafe { self.instance_mut() };
88+
inst.hostcall_nesting = inst
89+
.hostcall_nesting
90+
.checked_add(1)
91+
.expect("hostcall nesting level overflowed");
92+
}
93+
94+
fn decrement_hostcall_nesting(&self) {
95+
let inst = unsafe { self.instance_mut() };
96+
debug_assert!(inst.hostcall_nesting > 0);
97+
inst.hostcall_nesting = inst
98+
.hostcall_nesting
99+
.checked_sub(1)
100+
.expect("hostcall nesting level underflowed");
101+
}
102+
103+
fn in_nested_hostcall(&self) -> bool {
104+
self.instance().hostcall_nesting > 0
105+
}
71106
}
72107

73108
impl Vmctx {

lucet-runtime/lucet-runtime-tests/guests/host/bindings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"env": {
33
"hostcall_test_func_hello": "hostcall_test_func_hello",
44
"hostcall_test_func_hostcall_error": "hostcall_test_func_hostcall_error",
5-
"hostcall_test_func_hostcall_error_unwind": "hostcall_test_func_hostcall_error_unwind"
5+
"hostcall_test_func_hostcall_error_unwind": "hostcall_test_func_hostcall_error_unwind",
6+
"hostcall_test_func_hostcall_nested_error_unwind1": "hostcall_test_func_hostcall_nested_error_unwind1",
7+
"hostcall_test_func_hostcall_nested_error_unwind2": "hostcall_test_func_hostcall_nested_error_unwind2"
68
}
79
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <stddef.h>
2+
3+
extern void hostcall_test_func_hostcall_nested_error_unwind1(void (*)(void));
4+
extern void hostcall_test_func_hostcall_nested_error_unwind2(void);
5+
6+
void guest_func(void) {
7+
hostcall_test_func_hostcall_nested_error_unwind2();
8+
}
9+
10+
int main(void)
11+
{
12+
hostcall_test_func_hostcall_nested_error_unwind1(guest_func);
13+
return 0;
14+
}

lucet-runtime/lucet-runtime-tests/src/host.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ macro_rules! host_tests {
2727

2828
lazy_static! {
2929
static ref HOSTCALL_MUTEX: Mutex<()> = Mutex::new(());
30+
static ref HOSTCALL_MUTEX_1: Mutex<()> = Mutex::new(());
31+
static ref HOSTCALL_MUTEX_2: Mutex<()> = Mutex::new(());
3032
}
3133

3234
lucet_hostcalls! {
@@ -66,6 +68,37 @@ macro_rules! host_tests {
6668
drop(lock);
6769
}
6870

71+
#[no_mangle]
72+
pub unsafe extern "C" fn hostcall_test_func_hostcall_nested_error_unwind1(
73+
&mut vmctx,
74+
cb_idx: u32,
75+
) -> () {
76+
let lock = HOSTCALL_MUTEX_1.lock().unwrap();
77+
78+
let func = vmctx
79+
.get_func_from_idx(0, cb_idx)
80+
.expect("can get function by index");
81+
let func = std::mem::transmute::<
82+
usize,
83+
extern "C" fn(*mut lucet_vmctx)
84+
>(func.ptr.as_usize());
85+
(func)(vmctx.as_raw());
86+
87+
drop(lock);
88+
}
89+
90+
#[allow(unreachable_code)]
91+
#[no_mangle]
92+
pub unsafe extern "C" fn hostcall_test_func_hostcall_nested_error_unwind2(
93+
&mut vmctx,
94+
) -> () {
95+
let lock = HOSTCALL_MUTEX_2.lock().unwrap();
96+
unsafe {
97+
lucet_hostcall_terminate!(ERROR_MESSAGE);
98+
}
99+
drop(lock);
100+
}
101+
69102
#[no_mangle]
70103
pub unsafe extern "C" fn hostcall_bad_borrow(
71104
&mut vmctx,
@@ -188,6 +221,33 @@ macro_rules! host_tests {
188221
assert!(HOSTCALL_MUTEX.is_poisoned());
189222
}
190223

224+
#[test]
225+
fn run_hostcall_nested_error_unwind() {
226+
let module =
227+
test_module_c("host", "hostcall_nested_error_unwind.c").expect("build and load module");
228+
let region = TestRegion::create(1, &Limits::default()).expect("region can be created");
229+
let mut inst = region
230+
.new_instance(module)
231+
.expect("instance can be created");
232+
233+
match inst.run("main", &[0u32.into(), 0u32.into()]) {
234+
Err(Error::RuntimeTerminated(term)) => {
235+
assert_eq!(
236+
*term
237+
.provided_details()
238+
.expect("user provided termination reason")
239+
.downcast_ref::<&'static str>()
240+
.expect("error was static str"),
241+
ERROR_MESSAGE
242+
);
243+
}
244+
res => panic!("unexpected result: {:?}", res),
245+
}
246+
247+
assert!(HOSTCALL_MUTEX_1.is_poisoned());
248+
assert!(HOSTCALL_MUTEX_2.is_poisoned());
249+
}
250+
191251
#[test]
192252
fn run_fpe() {
193253
let module = test_module_c("host", "fpe.c").expect("build and load module");

lucet-runtime/src/c_api.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use lucet_runtime_internals::c_api::*;
55
use lucet_runtime_internals::instance::{
66
instance_handle_from_raw, instance_handle_to_raw, InstanceInternal,
77
};
8-
use lucet_runtime_internals::vmctx::VmctxInternal;
98
use lucet_runtime_internals::WASM_PAGE_SIZE;
109
use lucet_runtime_internals::{
1110
assert_nonnull, lucet_hostcall_terminate, lucet_hostcalls, with_ffi_arcs,

0 commit comments

Comments
 (0)