Skip to content

Commit 58c76e0

Browse files
committed
Only clear io buffer after unsuccesfull guest call.
This commit also clears poisoned Mutex after a host function panics and introduces a new HyperlightError variant for use when a host function panics. Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent e760628 commit 58c76e0

File tree

10 files changed

+132
-49
lines changed

10 files changed

+132
-49
lines changed

src/hyperlight_host/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ pub enum HyperlightError {
122122
#[error("HostFunction {0} was not found")]
123123
HostFunctionNotFound(String),
124124

125+
/// Host function panicked
126+
#[error("Host function '{0}' panicked: {1}")]
127+
HostFunctionPanic(String, String),
128+
125129
/// Reading Writing or Seeking data failed.
126130
#[error("Reading Writing or Seeking data failed {0:?}")]
127131
IOError(#[from] std::io::Error),

src/hyperlight_host/src/func/host_functions.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,21 @@ macro_rules! impl_host_function {
179179
let func = Mutex::new(func);
180180
HostFunction {
181181
func: Arc::new(move |args: ($($P,)*)| {
182-
func.try_lock()
183-
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
184-
(args)
182+
match func.try_lock() {
183+
Ok(mut guard) => guard(args),
184+
Err(poison_err) => {
185+
match poison_err {
186+
// The previous call to this host function panicked, poisoning the lock.
187+
// We can clear the poison safely.
188+
std::sync::TryLockError::Poisoned(guard) => {
189+
guard.into_inner()(args)
190+
}
191+
std::sync::TryLockError::WouldBlock => {
192+
Err(new_error!("Error locking at {}:{}: mutex would block", file!(), line!()))
193+
}
194+
}
195+
}
196+
}
185197
})
186198
}
187199
}

src/hyperlight_host/src/sandbox/host_funcs.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,18 @@ fn maybe_with_seccomp<T: Send>(
190190
return Err(crate::HyperlightError::DisallowedSyscall);
191191
}
192192

193-
crate::log_then_return!("Host function {} panicked", name);
193+
let panic_msg = if let Some(s) = err.downcast_ref::<String>() {
194+
s.clone()
195+
} else if let Some(s) = err.downcast_ref::<&str>() {
196+
s.to_string()
197+
} else {
198+
"Unknown panic".to_string()
199+
};
200+
201+
Err(crate::HyperlightError::HostFunctionPanic(
202+
name.to_string(),
203+
panic_msg,
204+
))
194205
}
195206
}
196207
})?

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,14 @@ impl MultiUseSandbox {
421421
.get_guest_function_call_result()
422422
})();
423423

424-
// TODO: Do we want to allow re-entrant guest function calls?
425-
self.get_mgr_wrapper_mut().as_mut().clear_io_buffers();
426-
424+
// In the happy path we do not need to clear io-buffers from the host because:
425+
// - the serialized guest function call is be zeroed out by the guest during deserialization, see call to `try_pop_shared_input_data_into::<FunctionCall>()`
426+
// - the serialized guest function result is zeroed out by us (the host) during deserialization, see `get_guest_function_call_result`
427+
// - any serialized host function call are zeroed out by us (the host) during deserialization, see `get_host_function_call`
428+
// - any serialized host function result is zeroed out by the guest during deserialization, see `get_host_return_value`
429+
if res.is_err() {
430+
self.get_mgr_wrapper_mut().as_mut().clear_io_buffers();
431+
}
427432
res
428433
}
429434

@@ -502,6 +507,35 @@ mod tests {
502507
use crate::sandbox::SandboxConfiguration;
503508
use crate::{GuestBinary, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox};
504509

510+
// make sure memory is cleared even if host function panics
511+
#[test]
512+
fn test_host_func_panic_clear_buffer() {
513+
let path = simple_guest_as_string().unwrap();
514+
let mut cfg = SandboxConfiguration::default();
515+
cfg.set_input_data_size(20 * 1024);
516+
cfg.set_output_data_size(20 * 1024);
517+
let mut u_sbox = UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg)).unwrap();
518+
u_sbox
519+
.register("host_panic", |_bytes: Vec<u8>| {
520+
panic!("Host function panic msg");
521+
#[expect(unreachable_code, reason = "Needed for type inference")]
522+
Ok(())
523+
})
524+
.unwrap();
525+
let mut sbox = u_sbox.evolve().unwrap();
526+
527+
for _ in 0..30 {
528+
// if io-buffers are not cleared on host func panic, this would eventually fail due to filling up input/output buffer
529+
let res = sbox
530+
.call::<Vec<u8>>("CallHostPanic", (vec![1; 1024],))
531+
.unwrap_err();
532+
assert!(matches!(
533+
res,
534+
crate::HyperlightError::HostFunctionPanic(func_name, payload) if func_name == "host_panic" && payload == "Host function panic msg"
535+
));
536+
}
537+
}
538+
505539
/// Tests that call_guest_function_by_name restores the state correctly
506540
#[test]
507541
fn test_call_guest_function_by_name() {

src/hyperlight_host/src/seccomp/guest.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ fn syscalls_allowlist() -> Result<Vec<(i64, Vec<SeccompRule>)>> {
8181
// since it will try to open /proc/sys/vm/overcommit_memory (https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/malloc-sysdep.h;h=778d8971d53e284397c3a5dcdd923e93be5e4731;hb=HEAD)
8282
// We have another more restrictive filter for it below so it will return EACCES instead of trap, in which case libc will use the default value
8383
(libc::SYS_openat, vec![]),
84+
// the following 3 are needed by Rust's panic handler
85+
(libc::SYS_getcwd, vec![]),
86+
(libc::SYS_readlink, vec![]),
87+
(libc::SYS_gettid, vec![]),
8488
])
8589
}
8690

src/tests/rust_guests/callbackguest/Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tests/rust_guests/dummyguest/Cargo.lock

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tests/rust_guests/simpleguest/Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tests/rust_guests/simpleguest/src/main.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,9 +898,27 @@ fn exec_mapped_buffer(function_call: &FunctionCall) -> Result<Vec<u8>> {
898898
}
899899
}
900900

901+
fn call_host_panic(_: &FunctionCall) -> Result<Vec<u8>> {
902+
call_host_function::<()>(
903+
"host_panic",
904+
Some(vec![ParameterValue::VecBytes(vec![1; 1024])]),
905+
ReturnType::VecBytes,
906+
)
907+
.unwrap();
908+
Ok(get_flatbuffer_result::<&[u8]>(vec![1; 1024].as_ref()))
909+
}
910+
901911
#[no_mangle]
902912
#[hyperlight_guest_tracing::trace_function]
903913
pub extern "C" fn hyperlight_main() {
914+
let call_host_panic_def = GuestFunctionDefinition::new(
915+
"CallHostPanic".to_string(),
916+
Vec::from(&[ParameterType::VecBytes]),
917+
ReturnType::VecBytes,
918+
call_host_panic as usize,
919+
);
920+
register_function(call_host_panic_def);
921+
904922
let read_from_user_memory_def = GuestFunctionDefinition::new(
905923
"ReadFromUserMemory".to_string(),
906924
Vec::from(&[ParameterType::ULong, ParameterType::VecBytes]),

0 commit comments

Comments
 (0)