diff --git a/.github/workflows/Fuzzing.yml b/.github/workflows/Fuzzing.yml index c1cde4463..71bfb6130 100644 --- a/.github/workflows/Fuzzing.yml +++ b/.github/workflows/Fuzzing.yml @@ -10,9 +10,9 @@ permissions: contents: read jobs: - fuzzing: uses: ./.github/workflows/dep_fuzzing.yml with: + targets: '["host_print", "guest_call", "host_call"]' # Pass as a JSON array max_total_time: 18000 # 5 hours in seconds secrets: inherit \ No newline at end of file diff --git a/.github/workflows/ValidatePullRequest.yml b/.github/workflows/ValidatePullRequest.yml index 0380813fa..c1ddc525f 100644 --- a/.github/workflows/ValidatePullRequest.yml +++ b/.github/workflows/ValidatePullRequest.yml @@ -54,6 +54,7 @@ jobs: - docs-pr uses: ./.github/workflows/dep_fuzzing.yml with: + targets: '["host_print", "guest_call", "host_call"]' # Pass as a JSON array max_total_time: 300 # 5 minutes in seconds docs_only: ${{needs.docs-pr.outputs.docs-only}} secrets: inherit diff --git a/.github/workflows/dep_fuzzing.yml b/.github/workflows/dep_fuzzing.yml index 742754501..d9f5a7562 100644 --- a/.github/workflows/dep_fuzzing.yml +++ b/.github/workflows/dep_fuzzing.yml @@ -7,6 +7,10 @@ on: description: Maximum total time for the fuzz run in seconds required: true type: number + targets: + description: Fuzz targets to run + required: true + type: string docs_only: description: Skip fuzzing if docs only required: false @@ -21,6 +25,9 @@ jobs: fuzz: if: ${{ inputs.docs_only == 'false' }} runs-on: [ self-hosted, Linux, X64, "1ES.Pool=hld-kvm-amd" ] + strategy: + matrix: + target: ${{ fromJson(inputs.targets) }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -44,7 +51,7 @@ jobs: run: cargo install cargo-fuzz - name: Run Fuzzing - run: cargo +nightly fuzz run --release fuzz_target_1 -- -max_total_time=300 + run: just fuzz-timed ${{ matrix.target }} ${{ inputs.max_total_time }} working-directory: src/hyperlight_host - name: Upload Crash Artifacts @@ -52,4 +59,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: fuzz-crash-artifacts - path: src/hyperlight_host/fuzz/artifacts/ + path: fuzz/artifacts/ diff --git a/Cargo.lock b/Cargo.lock index a9a778cd0..68ba78612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,9 @@ name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "async-stream" @@ -589,6 +592,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -1092,6 +1106,7 @@ name = "hyperlight-common" version = "0.2.0" dependencies = [ "anyhow", + "arbitrary", "flatbuffers", "hyperlight-testing", "log", @@ -1099,6 +1114,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlight-fuzz" +version = "0.0.0" +dependencies = [ + "hyperlight-host", + "hyperlight-testing", + "libfuzzer-sys", +] + [[package]] name = "hyperlight-guest" version = "0.2.0" @@ -1209,15 +1233,6 @@ dependencies = [ "log", ] -[[package]] -name = "hyperlight_host-fuzz" -version = "0.0.0" -dependencies = [ - "hyperlight-host", - "hyperlight-testing", - "libfuzzer-sys", -] - [[package]] name = "iana-time-zone" version = "0.1.61" diff --git a/Cargo.toml b/Cargo.toml index dae97c452..3fa824ad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ "src/hyperlight_host", "src/hyperlight_guest_capi", "src/hyperlight_testing", - "src/hyperlight_host/fuzz", + "fuzz", ] # Because hyperlight-guest has custom linker flags, # we exclude it from the default-members list diff --git a/Justfile b/Justfile index c91c08780..ed43a2d58 100644 --- a/Justfile +++ b/Justfile @@ -190,8 +190,11 @@ bench target=default-target features="": cargo bench --profile={{ if target == "debug" { "dev" } else { target } }} {{ if features =="" {''} else { "--features " + features } }} -- --verbose # FUZZING -fuzz: - cd src/hyperlight_host && cargo +nightly fuzz run fuzz_target_1 -fuzz-timed: - cd src/hyperlight_host && cargo +nightly fuzz run fuzz_target_1 -- -max_total_time=300 +# Fuzzes the given target +fuzz fuzz-target: + cargo +nightly fuzz run {{ fuzz-target }} --release + +# Fuzzes the given target. Stops after `max_time` seconds +fuzz-timed fuzz-target max_time: + cargo +nightly fuzz run {{ fuzz-target }} --release -- -max_total_time={{ max_time }} diff --git a/src/hyperlight_host/fuzz/.gitignore b/fuzz/.gitignore similarity index 100% rename from src/hyperlight_host/fuzz/.gitignore rename to fuzz/.gitignore diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..7d48f94ee --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "hyperlight-fuzz" +version = "0.0.0" +publish = false +edition = { workspace = true } + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +hyperlight-testing = { workspace = true } +hyperlight-host = { workspace = true, default-features = true, features = ["fuzzing"]} + +[[bin]] +name = "host_print" +path = "fuzz_targets/host_print.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "guest_call" +path = "fuzz_targets/guest_call.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "host_call" +path = "fuzz_targets/host_call.rs" +test = false +doc = false +bench = false \ No newline at end of file diff --git a/src/hyperlight_host/fuzz/README.md b/fuzz/README.md similarity index 61% rename from src/hyperlight_host/fuzz/README.md rename to fuzz/README.md index 8e7fcf2bb..f18bc3b8c 100644 --- a/src/hyperlight_host/fuzz/README.md +++ b/fuzz/README.md @@ -4,23 +4,19 @@ This directory contains the fuzzing infrastructure for Hyperlight. We use `cargo You can run the fuzzers with: ```sh -cargo +nightly-2023-11-28-x86_64-unknown-linux-gnu fuzz run --release +just fuzz ``` - -> Note: Because nightly toolchains are not stable, we pin the nightly version to `2023-11-28`. To install this toolchain, run: -> ```sh -> rustup toolchain install nightly-2023-11-28-x86_64-unknown-linux-gnu -> ``` +which evaluates to the following command `cargo +nightly fuzz run host_print --release`. We use the release profile to make sure the release-optimized guest is used. The default fuzz profile which is release+debugsymbols would cause our debug guests to be loaded, since we currently determine which test guest to load based on whether debug symbols are present. As per Microsoft's Offensive Research & Security Engineering (MORSE) team, all host exposed functions that receive or interact with guest data must be continuously fuzzed for, at least, 500 million fuzz test cases without any crashes. Because `cargo-fuzz` doesn't support setting a maximum number of iterations; instead, we use the `--max_total_time` flag to set a maximum time to run the fuzzer. We have a GitHub action (acting like a CRON job) that runs the fuzzers for 24 hours every week. -Currently, we only fuzz the `PrintOutput` function. We plan to add more fuzzers in the future. +Currently, we fuzz the parameters and return type to a hardcoded `PrintOutput` guest function, and the `HostPrint` host function. We plan to add more fuzzers in the future. ## On Failure If you encounter a failure, you can re-run an entire seed (i.e., group of inputs) with: ```sh -cargo +nightly-2023-11-28-x86_64-unknown-linux-gnu fuzz run --release -- -seed= +cargo +nightly fuzz run -- -seed= ``` The seed number can be seed in a specific run, like: @@ -29,5 +25,5 @@ The seed number can be seed in a specific run, like: Or, if repro-ing a failure from CI, you can download the artifact from the fuzzing run, and run it like: ```sh -cargo +nightly-2023-11-28-x86_64-unknown-linux-gnu fuzz run --release -O +cargo +nightly fuzz run -O ``` \ No newline at end of file diff --git a/src/hyperlight_host/fuzz/doc-assets/image.png b/fuzz/doc-assets/image.png similarity index 100% rename from src/hyperlight_host/fuzz/doc-assets/image.png rename to fuzz/doc-assets/image.png diff --git a/src/hyperlight_host/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/guest_call.rs similarity index 50% rename from src/hyperlight_host/fuzz/fuzz_targets/fuzz_target_1.rs rename to fuzz/fuzz_targets/guest_call.rs index e500c6276..12142a660 100644 --- a/src/hyperlight_host/fuzz/fuzz_targets/fuzz_target_1.rs +++ b/fuzz/fuzz_targets/guest_call.rs @@ -16,35 +16,35 @@ limitations under the License. #![no_main] -use hyperlight_host::func::{ParameterValue, ReturnType, ReturnValue}; +use std::sync::{Mutex, OnceLock}; + +use hyperlight_host::func::{ParameterValue, ReturnType}; use hyperlight_host::sandbox::uninitialized::GuestBinary; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; use hyperlight_testing::simple_guest_as_string; use libfuzzer_sys::fuzz_target; - -fuzz_target!(|data: &[u8]| { - let u_sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), - None, - None, - None, - ) - .unwrap(); - - let mu_sbox: MultiUseSandbox = u_sbox.evolve(Noop::default()).unwrap(); - - let msg = String::from_utf8_lossy(data).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())]), +static SANDBOX: OnceLock> = 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 u_sbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), + None, + None, + None, ) .unwrap(); - assert_eq!(result, ReturnValue::Int(len)); -}); + let mu_sbox: MultiUseSandbox = u_sbox.evolve(Noop::default()).unwrap(); + SANDBOX.set(Mutex::new(mu_sbox)).unwrap(); + }, + + |data: (ReturnType, Option>)| { + let mut sandbox = SANDBOX.get().unwrap().lock().unwrap(); + let _ = sandbox.call_guest_function_by_name("PrintOutput", data.0, data.1); + } +); diff --git a/fuzz/fuzz_targets/host_call.rs b/fuzz/fuzz_targets/host_call.rs new file mode 100644 index 000000000..a16cbda33 --- /dev/null +++ b/fuzz/fuzz_targets/host_call.rs @@ -0,0 +1,59 @@ +/* +Copyright 2024 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#![no_main] + +use std::sync::{Mutex, OnceLock}; + +use hyperlight_host::func::{ParameterValue, ReturnType}; +use hyperlight_host::sandbox::uninitialized::GuestBinary; +use hyperlight_host::sandbox::SandboxConfiguration; +use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; +use hyperlight_host::sandbox_state::transition::Noop; +use hyperlight_host::{HyperlightError, MultiUseSandbox, UninitializedSandbox}; +use hyperlight_testing::simple_guest_as_string; +use libfuzzer_sys::fuzz_target; +static SANDBOX: OnceLock> = 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 u_sbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), + None, + None, + None, + ) + .unwrap(); + + let mu_sbox: MultiUseSandbox = u_sbox.evolve(Noop::default()).unwrap(); + SANDBOX.set(Mutex::new(mu_sbox)).unwrap(); + }, + + |data: (String, ReturnType, Vec)| { + let (host_func_name, host_func_return, mut host_func_params) = data; + let mut sandbox = SANDBOX.get().unwrap().lock().unwrap(); + host_func_params.insert(0, ParameterValue::String(host_func_name)); + match sandbox.call_guest_function_by_name("FuzzHostFunc", host_func_return, Some(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); + } + _ => {} + } + } +); diff --git a/fuzz/fuzz_targets/host_print.rs b/fuzz/fuzz_targets/host_print.rs new file mode 100644 index 000000000..c44124d2f --- /dev/null +++ b/fuzz/fuzz_targets/host_print.rs @@ -0,0 +1,51 @@ +#![no_main] + +use std::sync::{Mutex, OnceLock}; + +use hyperlight_host::func::{ParameterValue, ReturnType, ReturnValue}; +use hyperlight_host::sandbox::uninitialized::GuestBinary; +use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; +use hyperlight_host::sandbox_state::transition::Noop; +use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_testing::simple_guest_as_string; +use libfuzzer_sys::{fuzz_target, Corpus}; + +static SANDBOX: OnceLock> = OnceLock::new(); + +// This fuzz target is used to test the HostPrint host function. We generate +// an arbitrary ParameterValue::String, which is passed to the guest, which passes +// it without modification to the host function. +// For fuzzing efficiency, we create one Sandbox and reuse it for all fuzzing iterations. +fuzz_target!( + init: { + let u_sbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), + None, + None, + None, + ) + .unwrap(); + + let mu_sbox: MultiUseSandbox = u_sbox.evolve(Noop::default()).unwrap(); + SANDBOX.set(Mutex::new(mu_sbox)).unwrap(); + }, + + |data: ParameterValue| -> Corpus { + // only interested in String types + if !matches!(data, ParameterValue::String(_)) { + return Corpus::Reject; + } + + let mut sandbox = SANDBOX.get().unwrap().lock().unwrap(); + let res = sandbox.call_guest_function_by_name( + "PrintOutput", + ReturnType::Int, + Some(vec![data.clone()]), + ); + match res { + Ok(ReturnValue::Int(len)) => assert!(len >= 0), + _ => panic!("Unexpected return value: {:?}", res), + } + + Corpus::Keep +}); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 11ac5e51b..3cf2110e7 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,2 @@ [toolchain] -channel = "1.81.0" -# if you update this, don't forget to change the pinned version -# of nightly we use in the fuzzing workflow. \ No newline at end of file +channel = "1.81.0" \ No newline at end of file diff --git a/src/hyperlight_common/Cargo.toml b/src/hyperlight_common/Cargo.toml index 3fb04a1a5..7a0801c25 100644 --- a/src/hyperlight_common/Cargo.toml +++ b/src/hyperlight_common/Cargo.toml @@ -20,9 +20,11 @@ anyhow = { version = "1.0.96", default-features = false } log = "0.4.26" tracing = { version = "0.1.41", optional = true } strum = {version = "0.27", default-features = false, features = ["derive"]} +arbitrary = {version = "1.4.1", optional = true, features = ["derive"]} [features] default = ["tracing"] +fuzzing = ["dep:arbitrary"] [dev-dependencies] hyperlight-testing = { workspace = true } diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs b/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs index 6eda6b267..42a26c81c 100644 --- a/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs +++ b/src/hyperlight_common/src/flatbuffer_wrappers/function_types.rs @@ -32,6 +32,7 @@ use crate::flatbuffers::hyperlight::generated::{ }; /// Supported parameter types with values for function calling. +#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))] #[derive(Debug, Clone, PartialEq)] pub enum ParameterValue { /// i32 @@ -104,6 +105,7 @@ pub enum ReturnValue { } /// Supported return types from function calling. +#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[repr(C)] pub enum ReturnType { diff --git a/src/hyperlight_common/src/lib.rs b/src/hyperlight_common/src/lib.rs index a291508ba..e2e75cc9e 100644 --- a/src/hyperlight_common/src/lib.rs +++ b/src/hyperlight_common/src/lib.rs @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -#![no_std] +// We use Arbitrary during fuzzing, which requires std +#![cfg_attr(not(feature = "fuzzing"), no_std)] extern crate alloc; diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index c0592a2b1..057cae9b6 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -132,6 +132,7 @@ mshv3 = ["dep:mshv-bindings3", "dep:mshv-ioctls3"] inprocess = [] # This enables easy debug in the guest gdb = ["dep:gdbstub", "dep:gdbstub_arch"] +fuzzing = ["hyperlight-common/fuzzing"] [[bench]] name = "benchmarks" diff --git a/src/hyperlight_host/fuzz/Cargo.toml b/src/hyperlight_host/fuzz/Cargo.toml deleted file mode 100644 index aab002916..000000000 --- a/src/hyperlight_host/fuzz/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "hyperlight_host-fuzz" -version = "0.0.0" -publish = false -edition = "2021" - -[package.metadata] -cargo-fuzz = true - -[dependencies] -libfuzzer-sys = "0.4" -hyperlight-testing = { workspace = true } -hyperlight-host = { workspace = true, default-features = true } - -[[bin]] -name = "fuzz_target_1" -path = "fuzz_targets/fuzz_target_1.rs" -test = false -doc = false -bench = false diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index 882b1c127..fbbeff222 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -231,12 +231,6 @@ impl Hypervisor for HypervLinuxDriver { dbg_mem_access_fn, )?; - // reset RSP to what it was before initialise - self.vcpu_fd.set_regs(&StandardRegisters { - rsp: self.orig_rsp.absolute()?, - rflags: 2, //bit 1 of rlags is required to be set - ..Default::default() - })?; Ok(()) } @@ -249,11 +243,10 @@ impl Hypervisor for HypervLinuxDriver { hv_handler: Option, #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper, ) -> Result<()> { - // Reset general purpose registers except RSP, then set RIP - let rsp_before = self.vcpu_fd.get_regs()?.rsp; + // Reset general purpose registers, then set RIP and RSP let regs = StandardRegisters { rip: dispatch_func_addr.into(), - rsp: rsp_before, + rsp: self.orig_rsp.absolute()?, rflags: 2, //bit 1 of rlags is required to be set ..Default::default() }; @@ -278,12 +271,6 @@ impl Hypervisor for HypervLinuxDriver { dbg_mem_access_fn, )?; - // reset RSP to what it was before function call - self.vcpu_fd.set_regs(&StandardRegisters { - rsp: rsp_before, - rflags: 2, //bit 1 of rlags is required to be set - ..Default::default() - })?; Ok(()) } diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index acde5e8f8..6b7c621b5 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -333,12 +333,6 @@ impl Hypervisor for HypervWindowsDriver { dbg_mem_access_hdl, )?; - // reset RSP to what it was before initialise - self.processor - .set_general_purpose_registers(&WHvGeneralRegisters { - rsp: self.orig_rsp.absolute()?, - ..Default::default() - })?; Ok(()) } @@ -351,11 +345,10 @@ impl Hypervisor for HypervWindowsDriver { hv_handler: Option, #[cfg(gdb)] dbg_mem_access_hdl: DbgMemAccessHandlerWrapper, ) -> Result<()> { - // Reset general purpose registers except RSP, then set RIP - let rsp_before = self.processor.get_regs()?.rsp; + // Reset general purpose registers, then set RIP and RSP let regs = WHvGeneralRegisters { rip: dispatch_func_addr.into(), - rsp: rsp_before, + rsp: self.orig_rsp.absolute()?, rflags: 1 << 1, // eflags bit index 1 is reserved and always needs to be 1 ..Default::default() }; @@ -378,12 +371,6 @@ impl Hypervisor for HypervWindowsDriver { dbg_mem_access_hdl, )?; - // reset RSP to what it was before function call - self.processor - .set_general_purpose_registers(&WHvGeneralRegisters { - rsp: rsp_before, - ..Default::default() - })?; Ok(()) } diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index a09a23943..5d01ec8ac 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -729,11 +729,6 @@ impl Hypervisor for KVMDriver { dbg_mem_access_fn, )?; - // reset RSP to what it was before initialise - self.vcpu_fd.set_regs(&kvm_regs { - rsp: self.orig_rsp.absolute()?, - ..Default::default() - })?; Ok(()) } @@ -746,11 +741,10 @@ impl Hypervisor for KVMDriver { hv_handler: Option, #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper, ) -> Result<()> { - // Reset general purpose registers except RSP, then set RIP - let rsp_before = self.vcpu_fd.get_regs()?.rsp; + // Reset general purpose registers, then set RIP and RSP let regs = kvm_regs { rip: dispatch_func_addr.into(), - rsp: rsp_before, + rsp: self.orig_rsp.absolute()?, ..Default::default() }; self.vcpu_fd.set_regs(®s)?; @@ -774,11 +768,6 @@ impl Hypervisor for KVMDriver { dbg_mem_access_fn, )?; - // reset RSP to what it was before function call - self.vcpu_fd.set_regs(&kvm_regs { - rsp: rsp_before, - ..Default::default() - })?; Ok(()) } diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index 3813733c7..45dd558c7 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -1117,6 +1117,9 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { // If the stack is not working correctly, the input or output buffer will be // overwritten before the function call is serialized, and we will not be able // to verify that the function call name is "ThisIsNotARealFunctionButTheNameIsImportant" + if function_call.function_name == "FuzzHostFunc" { + return fuzz_host_function(function_call); + } let message = "Hi this is a log message that will overwrite the shared buffer if the stack is not working correctly"; @@ -1152,3 +1155,23 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { Ok(get_flatbuffer_result(99)) } + +// Interprets the given guest function call as a host function call and dispatches it to the host. +fn fuzz_host_function(func: FunctionCall) -> Result> { + let mut params = func.parameters.unwrap(); + // first parameter must be string (the name of the host function to call) + let host_func_name = match params.remove(0) { + // TODO use `swap_remove` instead of `remove` if performance is an issue, but left out + // to avoid confusion for replicating failure cases + ParameterValue::String(name) => name, + _ => { + return Err(HyperlightGuestError::new( + ErrorCode::GuestFunctionParameterTypeMismatch, + "Invalid parameters passed to fuzz_host_function".to_string(), + )) + } + }; + call_host_function(&host_func_name, Some(params), func.expected_return_type) + .expect("failed to call host function"); + Ok(get_flatbuffer_result(())) +}