Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions fuzz/fuzz_targets/host_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,57 @@ limitations under the License.
use std::sync::{Mutex, OnceLock};

use hyperlight_host::func::{ParameterValue, ReturnType};
use hyperlight_host::sandbox::SandboxConfiguration;
use hyperlight_host::sandbox::snapshot::Snapshot;
use hyperlight_host::sandbox::uninitialized::GuestBinary;
use hyperlight_host::{HyperlightError, MultiUseSandbox, UninitializedSandbox};
use hyperlight_testing::simple_guest_for_fuzzing_as_string;
use libfuzzer_sys::fuzz_target;

// TODO: this SNAPSHOT is needed because of the memory leak in: https://github.com/hyperlight-dev/hyperlight/issues/826
// This should be removed once the leak is fixed
static SNAPSHOT: OnceLock<Mutex<Snapshot>> = OnceLock::new();
static SANDBOX: OnceLock<Mutex<MultiUseSandbox>> = OnceLock::new();

// This fuzz target tests all combinations of ReturnType and Parameters for `call_guest_function_by_name`.
// For fuzzing efficiency, we create one Sandbox and reuse it for all fuzzing iterations.
fuzz_target!(
init: {
let mut cfg = SandboxConfiguration::default();
cfg.set_output_data_size(64 * 1024); // 64 KB output buffer
cfg.set_input_data_size(64 * 1024); // 64 KB input buffer
let u_sbox = UninitializedSandbox::new(
GuestBinary::FilePath(simple_guest_for_fuzzing_as_string().expect("Guest Binary Missing")),
None
Some(cfg)
)
.unwrap();

let mu_sbox: MultiUseSandbox = u_sbox.evolve().unwrap();
let mut mu_sbox: MultiUseSandbox = u_sbox.evolve().unwrap();
let snapshot = mu_sbox.snapshot().unwrap();
SANDBOX.set(Mutex::new(mu_sbox)).unwrap();
SNAPSHOT.set(Mutex::new(snapshot)).map_err(|_| "Snapshot already set").unwrap();
},

|data: (String, ReturnType, Vec<ParameterValue>)| {
let (host_func_name, host_func_return, mut host_func_params) = data;
let mut sandbox = SANDBOX.get().unwrap().lock().unwrap();
let snapshot = SNAPSHOT.get().unwrap().lock().unwrap();
sandbox.restore(&snapshot).unwrap();

host_func_params.insert(0, ParameterValue::String(host_func_name));
match sandbox.call_type_erased_guest_function_by_name("FuzzHostFunc", host_func_return, host_func_params) {
Err(HyperlightError::GuestAborted(_, message)) if !message.contains("Host Function Not Found") => {
// We don't allow GuestAborted errors, except for the "Host Function Not Found" case
panic!("Guest Aborted: {}", message);
Err(e) => {
match e {
// the following are expected errors and occur frequently since
// we are randomly generating the function name and parameters
// to call with.
HyperlightError::HostFunctionNotFound(_) => {}
HyperlightError::UnexpectedNoOfArguments(_, _) => {},
HyperlightError::ParameterValueConversionFailure(_, _) => {},

// any other error should be reported
_ => panic!("Guest Aborted with Unexpected Error: {:?}", e),
}
}
_ => {}
}
Expand Down
2 changes: 2 additions & 0 deletions src/hyperlight_host/src/sandbox/initialized_multi_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,8 @@ impl MultiUseSandbox {
ret_type: ReturnType,
args: Vec<ParameterValue>,
) -> Result<ReturnValue> {
// Reset snapshot since we are mutating the sandbox state
self.snapshot = None;
Comment on lines +384 to +385
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we clearing this here? Shouldn't the restore call above take care of this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise I would have expected it too look like

pub fn call_guest_function_by_name<Output: SupportedReturnType>(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a bug: since this function is wrapping call_guest_function_by_name_no_reset and we're mutating the guest sandbox, the attached snapshot will be invalidated here, right? If we don't clear the snapshot, the restore call will hit the "snapshot exists" case and won't do the restore.

Copy link
Contributor Author

@adamperlin adamperlin Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the moment we could change the behavior to do the snapshot and restore in the call_type_erased_guest_function_by_name though, but since it seemed more clear to do it in the fuzzing code!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I think I miss read this. I think this is fine and mimics the 'call func'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, I'm glad it looks fine!

maybe_time_and_emit_guest_call(func_name, || {
self.call_guest_function_by_name_no_reset(func_name, ret_type, args)
})
Expand Down
Loading