use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
use hyperlight_common::flatbuffer_wrappers::function_types::{
ParameterValue, ReturnType, ReturnValue,
};
use tracing::{instrument, Span};
use super::guest_err::check_for_guest_error;
use crate::hypervisor::hypervisor_handler::HypervisorHandlerAction;
use crate::sandbox::WrapperGetter;
use crate::HyperlightError::GuestExecutionHungOnHostFunctionCall;
use crate::{HyperlightError, Result};
#[instrument(
err(Debug),
skip(wrapper_getter, args),
parent = Span::current(),
level = "Trace"
)]
pub(crate) fn call_function_on_guest<WrapperGetterT: WrapperGetter>(
wrapper_getter: &mut WrapperGetterT,
function_name: &str,
return_type: ReturnType,
args: Option<Vec<ParameterValue>>,
) -> Result<ReturnValue> {
let mut timedout = false;
let fc = FunctionCall::new(
function_name.to_string(),
args,
FunctionCallType::Guest,
return_type,
);
let buffer: Vec<u8> = fc
.try_into()
.map_err(|_| HyperlightError::Error("Failed to serialize FunctionCall".to_string()))?;
{
let mem_mgr = wrapper_getter.get_mgr_wrapper_mut();
mem_mgr.as_mut().write_guest_function_call(&buffer)?;
}
let mut hv_handler = wrapper_getter.get_hv_handler().clone();
match hv_handler.execute_hypervisor_handler_action(
HypervisorHandlerAction::DispatchCallFromHost(function_name.to_string()),
) {
Ok(()) => {}
Err(e) => match e {
HyperlightError::HypervisorHandlerMessageReceiveTimedout() => {
timedout = true;
match hv_handler.terminate_hypervisor_handler_execution_and_reinitialise(
wrapper_getter.get_mgr_wrapper_mut().unwrap_mgr_mut(),
)? {
HyperlightError::HypervisorHandlerExecutionCancelAttemptOnFinishedExecution() =>
{}
e => return Err(e),
}
}
e => return Err(e),
},
};
let mem_mgr = wrapper_getter.get_mgr_wrapper_mut();
mem_mgr.check_stack_guard()?; check_for_guest_error(mem_mgr)?;
mem_mgr
.as_mut()
.get_guest_function_call_result()
.map_err(|e| {
if timedout {
log::error!("Guest execution hung on host function call");
GuestExecutionHungOnHostFunctionCall()
} else {
e
}
})
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use std::thread;
use hyperlight_testing::{callback_guest_as_string, simple_guest_as_string};
use super::*;
use crate::func::call_ctx::{MultiUseGuestCallContext, SingleUseGuestCallContext};
use crate::func::host_functions::HostFunction0;
use crate::sandbox::is_hypervisor_present;
use crate::sandbox::uninitialized::GuestBinary;
use crate::sandbox_state::sandbox::EvolvableSandbox;
use crate::sandbox_state::transition::Noop;
use crate::{
new_error, HyperlightError, MultiUseSandbox, Result, SingleUseSandbox, UninitializedSandbox,
};
fn test_function0(_: MultiUseGuestCallContext) -> Result<i32> {
Ok(42)
}
struct GuestStruct;
fn test_function1(_: SingleUseGuestCallContext) -> Result<GuestStruct> {
Ok(GuestStruct)
}
fn test_function2(_: MultiUseGuestCallContext, param: i32) -> Result<i32> {
Ok(param)
}
#[test]
#[ignore]
#[cfg(target_os = "linux")]
fn test_violate_seccomp_filters() -> Result<()> {
if !is_hypervisor_present() {
panic!("Panic on create_multi_use_sandbox because no hypervisor is present");
}
fn make_get_pid_syscall() -> Result<u64> {
let pid = unsafe { libc::syscall(libc::SYS_getpid) };
Ok(pid as u64)
}
{
let make_get_pid_syscall_func = Arc::new(Mutex::new(make_get_pid_syscall));
let mut usbox = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
None,
None,
)
.unwrap();
make_get_pid_syscall_func.register(&mut usbox, "MakeGetpidSyscall")?;
let mut sbox: MultiUseSandbox = usbox.evolve(Noop::default())?;
let res =
sbox.call_guest_function_by_name("ViolateSeccompFilters", ReturnType::ULong, None);
#[cfg(feature = "seccomp")]
match res {
Ok(_) => panic!("Expected to fail due to seccomp violation"),
Err(e) => match e {
HyperlightError::DisallowedSyscall => {}
_ => panic!("Expected DisallowedSyscall error: {}", e),
},
}
#[cfg(not(feature = "seccomp"))]
match res {
Ok(_) => (),
Err(e) => panic!("Expected to succeed without seccomp: {}", e),
}
}
#[cfg(feature = "seccomp")]
{
let make_get_pid_syscall_func = Arc::new(Mutex::new(make_get_pid_syscall));
let mut usbox = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
None,
None,
)
.unwrap();
make_get_pid_syscall_func.register_with_extra_allowed_syscalls(
&mut usbox,
"MakeGetpidSyscall",
vec![libc::SYS_getpid],
)?;
let mut sbox: MultiUseSandbox = usbox.evolve(Noop::default())?;
let res =
sbox.call_guest_function_by_name("ViolateSeccompFilters", ReturnType::ULong, None);
match res {
Ok(_) => {}
Err(e) => panic!("Expected to succeed due to seccomp violation: {}", e),
}
}
Ok(())
}
#[test]
fn test_execute_in_host() {
let uninitialized_sandbox = || {
UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
None,
None,
)
.unwrap()
};
{
let usbox = uninitialized_sandbox();
let sandbox: MultiUseSandbox = usbox
.evolve(Noop::default())
.expect("Failed to initialize sandbox");
let result = test_function0(sandbox.new_call_context());
assert_eq!(result.unwrap(), 42);
}
{
let usbox = uninitialized_sandbox();
let sandbox: SingleUseSandbox = usbox
.evolve(Noop::default())
.expect("Failed to initialize sandbox");
let result = test_function1(sandbox.new_call_context());
assert!(result.is_ok());
}
{
let usbox = uninitialized_sandbox();
let sandbox: MultiUseSandbox = usbox
.evolve(Noop::default())
.expect("Failed to initialize sandbox");
let result = test_function2(sandbox.new_call_context(), 42);
assert_eq!(result.unwrap(), 42);
}
{
let count = Arc::new(Mutex::new(0));
let order = Arc::new(Mutex::new(vec![]));
let mut handles = vec![];
for _ in 0..10 {
let usbox = uninitialized_sandbox();
let sandbox: MultiUseSandbox = usbox
.evolve(Noop::default())
.expect("Failed to initialize sandbox");
let _ctx = sandbox.new_call_context();
let count = Arc::clone(&count);
let order = Arc::clone(&order);
let handle = thread::spawn(move || {
let mut num = count
.try_lock()
.map_err(|_| new_error!("Error locking"))
.unwrap();
*num += 1;
order
.try_lock()
.map_err(|_| new_error!("Error locking"))
.unwrap()
.push(*num);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let order = order
.try_lock()
.map_err(|_| new_error!("Error locking"))
.unwrap();
for i in 0..10 {
assert_eq!(order[i], i + 1);
}
}
}
#[track_caller]
fn guest_bin() -> GuestBinary {
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing"))
}
#[track_caller]
fn test_call_guest_function_by_name(u_sbox: UninitializedSandbox) {
let mu_sbox: MultiUseSandbox = u_sbox.evolve(Noop::default()).unwrap();
let msg = "Hello, World!!\n".to_string();
let len = msg.len() as i32;
let mut ctx = mu_sbox.new_call_context();
let result = ctx
.call(
"PrintOutput",
ReturnType::Int,
Some(vec![ParameterValue::String(msg.clone())]),
)
.unwrap();
assert_eq!(result, ReturnValue::Int(len));
}
fn call_guest_function_by_name_hv() {
let u_sbox = UninitializedSandbox::new(
guest_bin(),
None,
None,
None,
)
.unwrap();
test_call_guest_function_by_name(u_sbox);
}
#[test]
fn test_call_guest_function_by_name_hv() {
call_guest_function_by_name_hv();
}
#[test]
#[cfg(all(target_os = "windows", inprocess))]
fn test_call_guest_function_by_name_in_proc_load_lib() {
use hyperlight_testing::simple_guest_exe_as_string;
let u_sbox = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_exe_as_string().expect("Guest Exe Missing")),
None,
Some(crate::SandboxRunOptions::RunInProcess(true)),
None,
)
.unwrap();
test_call_guest_function_by_name(u_sbox);
}
#[test]
#[cfg(inprocess)]
fn test_call_guest_function_by_name_in_proc_manual() {
let u_sbox = UninitializedSandbox::new(
guest_bin(),
None,
Some(crate::SandboxRunOptions::RunInProcess(false)),
None,
)
.unwrap();
test_call_guest_function_by_name(u_sbox);
}
fn terminate_vcpu_after_1000ms() -> Result<()> {
if !is_hypervisor_present() {
println!("Skipping terminate_vcpu_after_1000ms because no hypervisor is present");
return Ok(());
}
let usbox = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")),
None,
None,
None,
)?;
let sandbox: MultiUseSandbox = usbox.evolve(Noop::default())?;
let mut ctx = sandbox.new_call_context();
let result = ctx.call("Spin", ReturnType::Void, None);
assert!(result.is_err());
match result.unwrap_err() {
HyperlightError::ExecutionCanceledByHost() => {}
e => panic!(
"Expected HyperlightError::ExecutionCanceledByHost() but got {:?}",
e
),
}
Ok(())
}
#[test]
fn test_terminate_vcpu_spinning_cpu() -> Result<()> {
terminate_vcpu_after_1000ms()?;
Ok(())
}
#[test]
fn test_terminate_vcpu_and_then_call_guest_function_on_the_same_host_thread() -> Result<()> {
terminate_vcpu_after_1000ms()?;
call_guest_function_by_name_hv();
Ok(())
}
#[test]
fn test_terminate_vcpu_calling_host_spinning_cpu() {
if !is_hypervisor_present() {
println!("Skipping test_call_guest_function_by_name because no hypervisor is present");
return;
}
let mut usbox = UninitializedSandbox::new(
GuestBinary::FilePath(callback_guest_as_string().expect("Guest Binary Missing")),
None,
None,
None,
)
.unwrap();
fn spin() -> Result<()> {
thread::sleep(std::time::Duration::from_secs(5));
Ok(())
}
let host_spin_func = Arc::new(Mutex::new(spin));
#[cfg(any(target_os = "windows", not(feature = "seccomp")))]
host_spin_func.register(&mut usbox, "Spin").unwrap();
#[cfg(all(target_os = "linux", feature = "seccomp"))]
host_spin_func
.register_with_extra_allowed_syscalls(
&mut usbox,
"Spin",
vec![libc::SYS_clock_nanosleep],
)
.unwrap();
let sandbox: MultiUseSandbox = usbox.evolve(Noop::default()).unwrap();
let mut ctx = sandbox.new_call_context();
let result = ctx.call("CallHostSpin", ReturnType::Void, None);
assert!(result.is_err());
match result.unwrap_err() {
HyperlightError::GuestExecutionHungOnHostFunctionCall() => {}
e => panic!(
"Expected HyperlightError::GuestExecutionHungOnHostFunctionCall but got {:?}",
e
),
}
}
}