diff --git a/.gitignore b/.gitignore index 841616ae4..bce853460 100644 --- a/.gitignore +++ b/.gitignore @@ -468,3 +468,6 @@ hyperlight_guest.h .mono !.gitkeep + +# gdb +.gdbinit \ No newline at end of file diff --git a/Justfile b/Justfile index 6d28c861e..518ef74e2 100644 --- a/Justfile +++ b/Justfile @@ -60,6 +60,22 @@ clean-rust: ### TESTING #### ################ +# run full CI test matrix +test-ci config=default-target hypervisor="kvm": + @# with default features + just test {{config}} {{ if hypervisor == "mshv3" {"mshv3"} else {""} }} + + @# with only one driver enabled + seccomp + inprocess + just test {{config}} inprocess,seccomp,{{ if hypervisor == "mshv" {"mshv2"} else if hypervisor == "mshv3" {"mshv3"} else {"kvm"} }} + + @# make sure certain cargo features compile + cargo check -p hyperlight-host --features crashdump + cargo check -p hyperlight-host --features print_debug + cargo check -p hyperlight-host --features gdb + + @# without any driver (should fail to compile) + just test-compilation-fail {{config}} + # Note: most testing recipes take an optional "features" comma separated list argument. If provided, these will be passed to cargo as **THE ONLY FEATURES**, i.e. default features will be disabled. # runs all tests diff --git a/README.md b/README.md index 7d60ca43f..f6d7fb3aa 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ fn main() -> hyperlight_host::Result<()> { )?; // Register a host function - fn sleep_5_secs() -> hyperlight_host::Result<()> { - thread::sleep(std::time::Duration::from_secs(5)); + fn host_add() -> hyperlight_host::Result<()> { + Ok(()) } @@ -92,41 +92,43 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ ParameterType, ParameterValue, ReturnType, }; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result_from_int; use hyperlight_guest::error::{HyperlightGuestError, Result}; use hyperlight_guest::guest_function_definition::GuestFunctionDefinition; use hyperlight_guest::guest_function_register::register_function; -use hyperlight_guest::host_function_call::{ - call_host_function, get_host_value_return_as_int, -}; +use hyperlight_common::host_calling::{call_host_function, get_host_return_value}; -fn print_output(function_call: &FunctionCall) -> Result> { - if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() { +fn add(function_call: &FunctionCall) -> Result> { + if let (ParameterValue::Int(a), ParameterValue::Int(b)) = ( + function_call.parameters.clone().unwrap()[0].clone(), + function_call.parameters.clone().unwrap()[1].clone(), + ) { call_host_function( - "HostPrint", - Some(Vec::from(&[ParameterValue::String(message.to_string())])), + "HostAdd", + Some(Vec::from(&[ParameterValue::Int(a), ParameterValue::Int(b)])), ReturnType::Int, )?; - let result = get_host_value_return_as_int()?; - Ok(get_flatbuffer_result_from_int(result)) + + let res = get_host_return_value::()?; + + Ok(get_flatbuffer_result(res)) } else { Err(HyperlightGuestError::new( ErrorCode::GuestFunctionParameterTypeMismatch, - "Invalid parameters passed to simple_print_output".to_string(), + "Invalid parameters passed to add".to_string(), )) } } #[no_mangle] pub extern "C" fn hyperlight_main() { - let print_output_def = GuestFunctionDefinition::new( - "PrintOutput".to_string(), - Vec::from(&[ParameterType::String]), + let add_def = GuestFunctionDefinition::new( + "Add".to_string(), + Vec::from(&[ParameterType::Int, ParameterType::Int]), ReturnType::Int, - print_output as i64, + add as usize, ); - register_function(print_output_def); + register_function(add_def); } #[no_mangle] diff --git a/docs/paging-development-notes.md b/docs/paging-development-notes.md index c025dd2ee..3ef3f73ad 100644 --- a/docs/paging-development-notes.md +++ b/docs/paging-development-notes.md @@ -1,43 +1,68 @@ # Paging in Hyperlight -Hyperlight uses paging, which means the all addresses inside a Hyperlight VM are treated as virtual addresses by the processor. Specifically, Hyperlight uses (ordinary) 4-level paging. 4-level paging is used because we set the following control registers on logical cores inside a VM: `CR0.PG = 1, CR4.PAE = 1, IA32_EFER.LME = 1, and CR4.LA57 = 0`. A Hyperlight VM is limited to 1GB of addressable memory, see below for more details. These control register settings have the following effects: +Hyperlight uses paging, which means the all addresses inside a Hyperlight VM are +treated as virtual addresses by the processor. Specifically, Hyperlight uses +(ordinary) 4-level paging. 4-level paging is used because we set the following +control registers on logical cores inside a VM: `CR0.PG = 1, CR4.PAE = 1, IA32_EFER, +LME = 1, and CR4.LA57 = 0`. A Hyperlight VM is limited to 1GB of addressable memory, +see below for more details. These control register settings have the following +effects: - `CR0.PG = 1`: Enables paging -- `CR4.PAE = 1`: Enables Physical Address Extension (PAE) mode (this is required for 4-level paging) +- `CR4.PAE = 1`: Enables Physical Address Extension (PAE) mode (this is required for +4-level paging) - `IA32_EFER.LME = 1`: Enables Long Mode (64-bit mode) - `CR4.LA57 = 0`: Makes sure 5-level paging is disabled ## Host-to-Guest memory mapping -Into each Hyperlight VM, memory from the host is mapped into the VM as physical memory. The physical memory inside the VM starts at address `0x200_000` and extends linearly to however much memory was mapped into the VM (depends on various parameters). +Into each Hyperlight VM, memory from the host is mapped into the VM as physical +memory. The physical memory inside the VM starts at address `0x0` and extends +linearly to however much memory was mapped into the VM (depends on various +parameters). ## Page table setup -The following page table structs are set up in memory before running a Hyperlight VM (See [Access Flags](#access-flags) for details on access flags that are also set on each entry) +The following page table structs are set up in memory before running a Hyperlight VM +(See [Access Flags](#access-flags) for details on access flags that are also set on each entry) ### PML4 (Page Map Level 4) Table -The PML4 table is located at physical address specified in CR3. In Hyperlight we set `CR3=0x200_000`, which means the PML4 table is located at physical address `0x200_000`. The PML4 table comprises 512 64-bit entries. +The PML4 table is located at physical address specified in CR3. In Hyperlight we set +`CR3=pml4_address`. The PML4 table comprises 512 64-bit entries. -In Hyperlight, we only initialize the first entry (at address `0x200_000`), with value `0x201_000`, implying that we only have a single PDPT. +In Hyperlight, we only initialize the first entry, with value `0x1_000`, implying that +we only have a single PDPT. ### PDPT (Page-directory-pointer Table) -The first and only PDPT is located at physical address `0x201_000`. The PDPT comprises 512 64-bit entries. In Hyperlight, we only initialize the first entry of the PDPT (at address `0x201_000`), with the value `0x202_000`, implying that we only have a single PD. +The first and only PDPT is located at physical address `0x1_000`. The PDPT comprises +512 64-bit entries. In Hyperlight, we only initialize the first entry of the PDPT +(at address `0x1_000`), with the value `0x2_000`, implying that we only have a +single PD. ### PD (Page Directory) -The first and only PD is located at physical address `0x202_000`. The PD comprises 512 64-bit entries, each entry `i` is set to the value `(i * 0x1000) + 0x203_000`. Thus, the first entry is `0x203_000`, the second entry is `0x204_000` and so on. +The first and only PD is located at physical address `0x2_000`. The PD comprises 512 +64-bit entries, each entry `i` is set to the value `(i * 0x1000) + 0x3_000`. Thus, +the first entry is `0x3_000`, the second entry is `0x4_000` and so on. ### PT (Page Table) -The page tables start at physical address `0x203_000`. Each page table has 512 64-bit entries. Each entry is set to the value `p << 21|i << 12` where `p` is the page table number and `i` is the index of the entry in the page table. Thus, the first entry of the first page table is `0x000_000`, the second entry is `0x000_000 + 0x1000`, and so on. The first entry of the second page table is `0x200_000 + 0x1000`, the second entry is `0x200_000 + 0x2000`, and so on. Enough page tables are created to cover the size of memory mapped into the VM. +The page tables start at physical address `0x3_000`. Each page table has 512 64-bit +entries. Each entry is set to the value `p << 21|i << 12` where `p` is the page +table number and `i` is the index of the entry in the page table. Thus, the first +entry of the first page table is `0x000_000`, the second entry is `0x000_000 + +0x1000`, and so on. The first entry of the second page table is `0x200_000 + +0x1000`, the second entry is `0x200_000 + 0x2000`, and so on. Enough page tables are +created to cover the size of memory mapped into the VM. ## Address Translation -Given a 64-bit virtual address X, the corresponding physical address is obtained as follows: +Given a 64-bit virtual address X, the corresponding physical address is obtained as +follows: -1. PML4 table's physical address is located using CR3 (CR3 is `0x200_000`). +1. PML4 table's physical address is located using CR3. 2. Bits 47:39 of X are used to index into PML4, giving us the address of the PDPT. 3. Bits 38:30 of X are used to index into PDPT, giving us the address of the PD. 4. Bits 29:21 of X are used to index into PD, giving us the address of the PT. @@ -45,36 +70,34 @@ Given a 64-bit virtual address X, the corresponding physical address is obtained 6. Bits 11:0 of X are treated as an offset. 7. The final physical address is the base address + the offset. -However, because we have only one PDPT4E and only one PDPT4E, bits 47:30 must always be zero. Each PDE points to a PT, and because each PTE with index `p,i` (where p is the page table number of i is the entry within that page) has value `p << 21|i << 12`, the base address received in step 5 above is always just bits 29:12 of X itself. **As bits 11:0 are an offset this means that translating a virtual address to a physical address is essentially a NO-OP**. +However, because we have only one PDPT4E and only one PDPT4E, bits 47:30 must always +be zero. Each PDE points to a PT, and because each PTE with index `p,i` (where p i +the page table number of i is the entry within that page) has value `p << 21|i << +12`, the base address received in step 5 above is always just bits 29:12 of X +itself. **As bits 11:0 are an offset this means that translating a virtual address +to a physical address is essentially a NO-OP**. -A diagram to describe how a linear (virtual) address is translated to physical address inside a Hyperlight VM: +A diagram to describe how a linear (virtual) address is translated to physical +address inside a Hyperlight VM: ![A diagram to describe how a linear (virtual) address is translated to physical](assets/linear-address-translation.png) -Diagram is taken from "The Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A: System Programming Guide" +Diagram is taken from "The Intel® 64 and IA-32 Architectures Software Developer’s +Manual, Volume 3A: System Programming Guide" ### Limitations -Since we only have 1 PML4E and only 1 PDPTE, bits 47:30 of a linear address must be zero. Thus, we have only 30 bits (bit 29:0) to work with, giving us access to (1 << 30) bytes of memory (1GB). +Since we only have 1 PML4E and only 1 PDPTE, bits 47:30 of a linear address must be +zero. Thus, we have only 30 bits (bit 29:0) to work with, giving us access to (1 << +30) bytes of memory (1GB). ## Access Flags -In addition to providing addresses, page table entries also contain access flags that describe how memory can be accessed, and whether it is present or not. The following access flags are set on each entry: +In addition to providing addresses, page table entries also contain access flags +that describe how memory can be accessed, and whether it is present or not. The +following access flags are set on each entry: -PML4E, PDPTE, and PD Entries have the present flag set to 1, and the rest of the flags are not set. +PML4E, PDPTE, and PD Entries have the present flag set to 1, and the rest of the +flags are not set. -PTE Entries all have the present flag set to 1, apart from those for the address range `0x000_000` to `0x1FF_000` which have the present flag set to 0 as we do not map memory below physical address `0x200_000`. - -In addition, the following flags are set according to the type of memory being mapped: - -For `Host Function Definitions` and `Host Exception Data` the NX flag is set to 1 meaning that the memory is not executable in the guest and is not accessible to guest code (ring 3) and is also read only even in ring 0. - -For `Input/Output Data`, `Page Table Data`, `PEB`, `PanicContext` and `GuestErrorData` the NX flag is set to 1 meaning that the memory is not executable in the guest and the RW flag is set to 1 meaning that the memory is read/write in ring 0, this means that this data is not accessible to guest code unless accessed via the Hyperlight Guest API (which will be in ring 0). - -For `Code` the NX flag is not set meaning that the memory is executable in the guest and the RW flag is set to 1 meaning the data is read/write, as the user/supervisor flag is set then the memory is also read/write accessible to user code. (The code section contains both code and data, so it is marked as read/write. In a future update we will parse the layout of the code and set the access flags accordingly). - -For `Stack` the NX flag is set to 1 meaning that the memory is not executable in the guest, the RW flag is set to 1 meaning the data is read/write, as the user/supervisor flag is set then the memory is also read/write accessible to user code. - -For `Heap` the RW flag is set to 1 meaning the data is read/write, as the user/supervisor flag is set then the memory is also read/write accessible to user code. The NX flag is not set if the feature `executable_heap` is enabled, otherwise the NX flag is set to 1 meaning that the memory is not executable in the guest. The `executable_heap` feature is disabled by default. It is required to allow data in the heap to be executable to when guests dynamically load or generate code, e.g. `hyperlight-wasm` supports loading of AOT compiled WebAssembly modules, these are loaded dynamically by the Wasm runtime and end up in the heap, therefore for this scenario the `executable_heap` feature must be enabled. In a future update we will implement a mechanism to allow the guest to request memory to be executable at runtime via the Hyperlight Guest API. - -For `Guard Pages` the NX flag is set to 1 meaning that the memory is not executable in the guest. The RW flag is set to 1 meaning the data is read/write, as the user/supervisor flag is set then the memory is also read/write accessible to user code. **Note that neither of these flags should really be set as the purpose of the guard pages is to cause a fault if accessed, however, as we deal with this fault in the host not in the guest we need to make the memory accessible to the guest, in a future update we will implement exception and interrupt handling in the guest and then change these flags.** +PTE Entries all have the present flag set to 1. diff --git a/src/hyperlight_common/src/host_calling.rs b/src/hyperlight_common/src/host_calling.rs new file mode 100644 index 000000000..2bbdc61d0 --- /dev/null +++ b/src/hyperlight_common/src/host_calling.rs @@ -0,0 +1,72 @@ +use alloc::string::ToString; +use alloc::vec::Vec; +use core::ffi::c_char; + +use anyhow::Result; + +use crate::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType}; +use crate::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType, ReturnValue}; +use crate::input_output::{InputDataSection, OutputDataSection}; +use crate::outb::{outb, OutBAction}; +use crate::PEB; + +/// Get a return value from a host function call. +/// This usually requires a host function to be called first using `call_host_function`. +pub fn get_host_return_value>() -> Result { + let input_data_section: InputDataSection = + unsafe { (*PEB).clone() }.get_input_data_region().into(); + let return_value = input_data_section + .try_pop_shared_input_data_into::() + .expect("Unable to deserialize a return value from host"); + + T::try_from(return_value).map_err(|_| { + anyhow::anyhow!( + "Host return value was not a {} as expected", + core::any::type_name::() + ) + }) +} + +/// Calls a host function. +// TODO: Make this generic, return a Result this should allow callers to call this function and get the result type they expect +// without having to do the conversion themselves +pub fn call_host_function( + function_name: &str, + parameters: Option>, + return_type: ReturnType, +) -> Result<()> { + let host_function_call = FunctionCall::new( + function_name.to_string(), + parameters, + FunctionCallType::Host, + return_type, + ); + + let host_function_call_buffer: Vec = host_function_call + .try_into() + .expect("Unable to serialize host function call"); + + let output_data_section: OutputDataSection = + unsafe { (*PEB).clone() }.get_output_data_region().into(); + output_data_section.push_shared_output_data(host_function_call_buffer)?; + + outb(OutBAction::CallFunction as u16, 0); + + Ok(()) +} + +/// Uses `hloutb` to issue multiple `DebugPrint` `OutBAction`s to print a message. +pub fn print(message: &str) { + for byte in message.bytes() { + outb(OutBAction::DebugPrint as u16, byte); + } +} + +/// Exposes a C API to allow the guest to print a string, byte by byte +/// +/// # Safety +/// This function is not thread safe and assumes `outb` is safe to call directly. +#[no_mangle] +pub unsafe extern "C" fn _putchar(c: c_char) { + outb(OutBAction::DebugPrint as u16, c as u8); +} diff --git a/src/hyperlight_common/src/input_output.rs b/src/hyperlight_common/src/input_output.rs new file mode 100644 index 000000000..a3d6f5c0f --- /dev/null +++ b/src/hyperlight_common/src/input_output.rs @@ -0,0 +1,165 @@ +/* +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. +*/ +use alloc::vec::Vec; +use core::slice::from_raw_parts_mut; + +use anyhow::{bail, Result}; + +/// The host will use the `InputDataSection` to pass data to the guest. This can be, for example, +/// the issuing of a function call or the result of calling a host function. +pub struct InputDataSection { + ptr: *mut u8, + len: u64, +} + +impl InputDataSection { + /// Creates a new `InputDataSection` with the given pointer and length. + pub fn new(ptr: *mut u8, len: u64) -> Self { + InputDataSection { ptr, len } + } + + /// Tries to pop shared input data into a type `T`. The type `T` must implement the `TryFrom` trait + pub fn try_pop_shared_input_data_into(&self) -> Result + where + T: for<'a> TryFrom<&'a [u8]>, + { + let shared_buffer_size = self.len as usize; + + let idb = unsafe { from_raw_parts_mut(self.ptr, shared_buffer_size) }; + + if idb.is_empty() { + bail!("Got a 0-size buffer in try_pop_shared_input_data_into"); + } + + // get relative offset to next free address + let stack_ptr_rel: u64 = u64::from_le_bytes(match idb[..8].try_into() { + Ok(bytes) => bytes, + Err(_) => bail!("shared input buffer too small"), + }); + + if stack_ptr_rel as usize > shared_buffer_size || stack_ptr_rel < 16 { + bail!( + "Invalid stack pointer: {} in try_pop_shared_input_data_into", + stack_ptr_rel + ); + } + + // go back 8 bytes and read. This is the offset to the element on top of stack + let last_element_offset_rel = u64::from_le_bytes( + match idb[stack_ptr_rel as usize - 8..stack_ptr_rel as usize].try_into() { + Ok(bytes) => bytes, + Err(_) => bail!("Invalid stack pointer in pop_shared_input_data_into"), + }, + ); + + let buffer = &idb[last_element_offset_rel as usize..]; + + // convert the buffer to T + let type_t = match T::try_from(buffer) { + Ok(t) => Ok(t), + Err(_e) => bail!("failed to convert buffer to type T in pop_shared_input_data_into"), + }; + + // update the stack pointer to point to the element we just popped of since that is now free + idb[..8].copy_from_slice(&last_element_offset_rel.to_le_bytes()); + + // zero out popped off buffer + idb[last_element_offset_rel as usize..stack_ptr_rel as usize].fill(0); + + type_t + } +} + +/// The guest will use the `OutputDataSection` to pass data back to the host. This can be, for example, +/// issuing a host function call or the result of a guest function call. +pub struct OutputDataSection { + pub ptr: *mut u8, + pub len: u64, +} + +impl OutputDataSection { + const STACK_PTR_SIZE: usize = size_of::(); + + /// Creates a new `OutputDataSection` with the given pointer and length. + pub fn new(ptr: *mut u8, len: u64) -> Self { + OutputDataSection { ptr, len } + } + + /// Pushes shared output data to the output buffer. + pub fn push_shared_output_data(&self, data: Vec) -> Result<()> { + let shared_buffer_size = self.len as usize; + let odb: &mut [u8] = unsafe { from_raw_parts_mut(self.ptr, shared_buffer_size) }; + + if odb.len() < Self::STACK_PTR_SIZE { + bail!("shared output buffer is too small"); + } + + // get offset to next free address on the stack + let mut stack_ptr_rel: u64 = + u64::from_le_bytes(match odb[..Self::STACK_PTR_SIZE].try_into() { + Ok(bytes) => bytes, + Err(_) => bail!("failed to get stack pointer in shared output buffer"), + }); + + // if stack_ptr_rel is 0, it means this is the first time we're using the output buffer, so + // we want to offset it by 8 as to not overwrite the stack_ptr location. + if stack_ptr_rel == 0 { + stack_ptr_rel = 8; + } + + // check if the stack pointer is within the bounds of the buffer. + // It can be equal to the size, but never greater + // It can never be less than 8. An empty buffer's stack pointer is 8 + if stack_ptr_rel as usize > shared_buffer_size { + bail!("invalid stack pointer in shared output buffer"); + } + + // check if there is enough space in the buffer + let size_required: usize = data.len() + 8; // the data plus the pointer pointing to the data + let size_available: usize = shared_buffer_size - stack_ptr_rel as usize; + if size_required > size_available { + bail!("not enough space in shared output buffer"); + } + + // write the actual data + odb[stack_ptr_rel as usize..stack_ptr_rel as usize + data.len()].copy_from_slice(&data); + + // write the offset to the newly written data, to the top of the stack + let bytes: [u8; Self::STACK_PTR_SIZE] = stack_ptr_rel.to_le_bytes(); + odb[stack_ptr_rel as usize + data.len() + ..stack_ptr_rel as usize + data.len() + Self::STACK_PTR_SIZE] + .copy_from_slice(&bytes); + + // update stack pointer to point to next free address + let new_stack_ptr_rel: u64 = + (stack_ptr_rel as usize + data.len() + Self::STACK_PTR_SIZE) as u64; + odb[0..Self::STACK_PTR_SIZE].copy_from_slice(&new_stack_ptr_rel.to_le_bytes()); + + Ok(()) + } +} + +impl From<(u64, u64)> for InputDataSection { + fn from((ptr, len): (u64, u64)) -> Self { + InputDataSection::new(ptr as *mut u8, len) + } +} + +impl From<(u64, u64)> for OutputDataSection { + fn from((ptr, len): (u64, u64)) -> Self { + OutputDataSection::new(ptr as *mut u8, len) + } +} diff --git a/src/hyperlight_common/src/lib.rs b/src/hyperlight_common/src/lib.rs index f05baa9f9..abb710aac 100644 --- a/src/hyperlight_common/src/lib.rs +++ b/src/hyperlight_common/src/lib.rs @@ -20,7 +20,10 @@ limitations under the License. // We use Arbitrary during fuzzing, which requires std #![cfg_attr(not(feature = "fuzzing"), no_std)] +pub const PAGE_SIZE: usize = 0x1_000; // 4KB + extern crate alloc; +extern crate core; pub mod flatbuffer_wrappers; /// cbindgen:ignore @@ -34,5 +37,44 @@ pub mod flatbuffer_wrappers; non_camel_case_types )] mod flatbuffers; -/// cbindgen:ignore -pub mod mem; + +/// The Hyperlight PEB is a structure configurable by host/guest that determines how +/// the two will communicate. For example, in the PEB, you can set the address for the +/// input and output data regions—these regions are imperative for the host and guest to +/// be able to communicate via function calls. +pub mod peb; + +/// We keep track of the PEB address in a global variable that references a region of +/// shared memory. +pub static mut PEB: *mut peb::HyperlightPEB = core::ptr::null_mut(); + +/// Hyperlight supports running in both hypervisor mode and in process mode. We keep track of that +/// state in this global variable. +pub static mut RUNNING_MODE: peb::RunMode = peb::RunMode::None; + +/// For in-process mode, we can't call the `outb` instruction directly because it is a privileged +/// instruction. Instead, we use a function pointer to call an `outb_handler` function. +/// For in-process mode, we can't call the `outb` instruction directly because it is a privileged +/// instruction. Instead, we use a function pointer to call an `outb_handler` function. +pub static mut OUTB_HANDLER: Option = None; + +pub static mut OUTB_HANDLER_CTX: Option = None; + +/// Hyperlight operates with a host-guest execution model. +/// +/// The host is who creates the hypervisor partition, and the guest is whatever runs +/// inside said hypervisor partition (i.e., in between a `VMENTER` and `VMEXIT`). +/// +/// The guest and host communicate through a shared memory region. In particular, the +/// input and output data sections. A guest can pop data from the input section, and +/// push data to the output section. On the other hand, the host can push data to the +/// input section and pop data from the output section. +pub mod input_output; + +/// `outb` is the mechanism that Hyperlight uses to cause a VM exit to execute host functions and +/// similar functionality. +pub mod outb; + +/// Hyperlight provides abstractions for performing a VM exits with the intent of calling +/// functionality in the host. +pub mod host_calling; diff --git a/src/hyperlight_common/src/mem/mod.rs b/src/hyperlight_common/src/mem/mod.rs deleted file mode 100644 index 8995f1d64..000000000 --- a/src/hyperlight_common/src/mem/mod.rs +++ /dev/null @@ -1,104 +0,0 @@ -/* -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. -*/ - -#![allow(non_snake_case)] - -pub const PAGE_SHIFT: u64 = 12; -pub const PAGE_SIZE: u64 = 1 << 12; -pub const PAGE_SIZE_USIZE: usize = 1 << 12; - -use core::ffi::{c_char, c_void}; - -#[repr(C)] -pub struct HostFunctionDefinitions { - pub fbHostFunctionDetailsSize: u64, - pub fbHostFunctionDetails: *mut c_void, -} - -#[repr(C)] -pub struct HostException { - pub hostExceptionSize: u64, -} - -#[repr(C)] -pub struct GuestErrorData { - pub guestErrorSize: u64, - pub guestErrorBuffer: *mut c_void, -} - -#[repr(u64)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum RunMode { - None = 0, - Hypervisor = 1, - InProcessWindows = 2, - InProcessLinux = 3, - Invalid = 4, -} - -#[repr(C)] -pub struct InputData { - pub inputDataSize: u64, - pub inputDataBuffer: *mut c_void, -} - -#[repr(C)] -pub struct OutputData { - pub outputDataSize: u64, - pub outputDataBuffer: *mut c_void, -} - -#[repr(C)] -pub struct GuestHeapData { - pub guestHeapSize: u64, - pub guestHeapBuffer: *mut c_void, -} - -#[repr(C)] -pub struct GuestStackData { - /// This is the top of the user stack - pub minUserStackAddress: u64, - /// This is the user stack pointer - pub userStackAddress: u64, - /// This is the stack pointer for the kernel mode stack - pub kernelStackAddress: u64, - /// This is the initial stack pointer when init is called its used before the TSS is set up - pub bootStackAddress: u64, -} - -#[repr(C)] -pub struct GuestPanicContextData { - pub guestPanicContextDataSize: u64, - pub guestPanicContextDataBuffer: *mut c_void, -} - -#[repr(C)] -pub struct HyperlightPEB { - pub security_cookie_seed: u64, - pub guest_function_dispatch_ptr: u64, - pub hostFunctionDefinitions: HostFunctionDefinitions, - pub hostException: HostException, - pub guestErrorData: GuestErrorData, - pub pCode: *mut c_char, - pub pOutb: *mut c_void, - pub pOutbContext: *mut c_void, - pub runMode: RunMode, - pub inputdata: InputData, - pub outputdata: OutputData, - pub guestPanicContextData: GuestPanicContextData, - pub guestheapData: GuestHeapData, - pub gueststackData: GuestStackData, -} diff --git a/src/hyperlight_common/src/outb.rs b/src/hyperlight_common/src/outb.rs new file mode 100644 index 000000000..6606b8b8f --- /dev/null +++ b/src/hyperlight_common/src/outb.rs @@ -0,0 +1,64 @@ +use core::arch; + +use anyhow::{bail, Result}; + +use crate::peb::RunMode; +use crate::{OUTB_HANDLER, OUTB_HANDLER_CTX, PEB, RUNNING_MODE}; + +/// Supported actions when issuing an OUTB actions by Hyperlight. +/// - Log: for logging, +/// - CallFunction: makes a call to a host function, +/// - Abort: aborts the execution of the guest, +/// - DebugPrint: prints a message to the host console. +pub enum OutBAction { + Log = 99, + CallFunction = 101, + Abort = 102, + DebugPrint = 103, +} + +impl TryFrom for OutBAction { + type Error = anyhow::Error; + fn try_from(val: u16) -> Result { + match val { + 99 => Ok(OutBAction::Log), + 101 => Ok(OutBAction::CallFunction), + 102 => Ok(OutBAction::Abort), + 103 => Ok(OutBAction::DebugPrint), + _ => bail!("Invalid OutB value: {}", val), + } + } +} + +/// Issues an OUTB instruction to the specified port with the given value. +fn hloutb(port: u16, val: u8) { + unsafe { + arch::asm!("out dx, al", in("dx") port, in("al") val, options(preserves_flags, nomem, nostack)); + } +} + +pub fn outb(port: u16, value: u8) { + unsafe { + match RUNNING_MODE { + RunMode::Hypervisor => { + hloutb(port, value); + } + RunMode::InProcessLinux | RunMode::InProcessWindows => { + if let Some(outb_func) = OUTB_HANDLER_CTX { + outb_func( + (*PEB).get_outb_ptr_ctx() as *mut core::ffi::c_void, + port, + value, + ); + } else if let Some(outb_func) = OUTB_HANDLER { + outb_func(port, value); + } else { + panic!("Tried to call outb without hypervisor and without outb function ptrs"); + } + } + _ => { + panic!("Tried to call outb in invalid runmode"); + } + } + } +} diff --git a/src/hyperlight_common/src/peb.rs b/src/hyperlight_common/src/peb.rs new file mode 100644 index 000000000..fe10e7c18 --- /dev/null +++ b/src/hyperlight_common/src/peb.rs @@ -0,0 +1,432 @@ +use crate::PAGE_SIZE; + +/// Hyperlight supports 2 primary modes: +/// 1. Hypervisor mode +/// 2. In-process mode +/// +/// When running in process, there's no hypervisor isolation. +/// In-process mode is primarily used for debugging and testing. +#[repr(u64)] +#[derive(Clone, Debug, PartialEq, Default)] +pub enum RunMode { + None = 0, + #[default] + Hypervisor = 1, + InProcessWindows = 2, + InProcessLinux = 3, + Invalid = 4, +} + +/// Represents a memory region with an offset and a size. +#[derive(Clone, Debug, Default)] +pub struct MemoryRegion { + pub offset: Option, + pub size: u64, +} + +#[repr(C)] +#[derive(Clone, Default)] +pub struct HyperlightPEB { + // - Host configured fields + /// Hyperlight supports two primary modes: + /// 1. Hypervisor mode + /// 2. In-process mode + /// + /// When running in process, there's no hypervisor isolation. + /// It's a mode primarily used for debugging and testing. + run_mode: RunMode, + + /// On Windows, Hyperlight supports in-process execution. + /// In-process execution means a guest is running in + /// Hyperlight, but with no hypervisor isolation. When we + /// run in-process, we can't rely on the usual mechanism for + /// host function calls (i.e., `outb`). Instead, we call a + /// function directly, which is represented by these pointers. + outb_ptr: u64, + outb_ptr_ctx: u64, + + /// The host base address for the custom guest memory region. + guest_memory_host_base_address: u64, + + /// The base address for the guest memory region. + guest_memory_base_address: u64, + + /// The size of the guest memory region. + guest_memory_size: u64, + + // - Guest configured fields + /// The guest function dispatch pointer is what allows + /// a host to call "guest functions". The host can + /// directly set the instruction pointer register to this + /// before re-entering the guest. + guest_function_dispatch_ptr: u64, + + /// The input data pointer is used to pass data from + /// the host to the guest. + input_data: Option, + + /// The output data pointer is used to pass data from + /// the guest to the host. + output_data: Option, + + /// The guest panic context pointer can be used to pass + /// panic context data from the guest to the host. + guest_panic_context: Option, + + /// The guest heap data pointer points to a region of + /// memory in the guest that is used for heap allocations. + guest_heap_data: Option, + + /// The guest stack data pointer points to a region of + /// memory in the guest that is used for stack allocations. + guest_stack_data: Option, +} + +impl HyperlightPEB { + /// Creates a new HyperlightPEB with the basic configuration based on the provided guest memory + /// layout and default guest heap/stack sizes. The guest can later fill additional fields. + pub fn new( + run_mode: RunMode, + guest_heap_size: u64, + guest_stack_size: u64, + guest_memory_host_base_address: u64, + guest_memory_base_address: u64, + guest_memory_size: u64, + ) -> Self { + Self { + run_mode, + outb_ptr: 0, + outb_ptr_ctx: 0, + guest_memory_host_base_address, + guest_memory_base_address, + guest_memory_size, + guest_function_dispatch_ptr: 0, + input_data: None, + output_data: None, + guest_panic_context: None, + guest_heap_data: Some(MemoryRegion { + offset: None, + size: guest_heap_size, + }), + guest_stack_data: Some(MemoryRegion { + offset: None, + size: guest_stack_size, + }), + } + } + + /// Convenience method that sets an arbitrary "default" memory layout for the guest. This layout + /// is used by guests built w/ the `hyperlight_guest` library. + /// - +--------------------------+ + /// - | Guest panic context data | 4KB + /// - +--------------------------+ + /// - | Output data | 16KB + /// - +--------------------------+ + /// - | Input data | 16KB + /// - +--------------------------+ + /// - | Guest heap data | (configurable size) + /// - +--------------------------+ + /// - | Guest stack data | (configurable size) + /// - +--------------------------+ + pub fn set_default_memory_layout(&mut self) { + // we set the guest stack at the start of the guest memory region to leverage + // the stack guard page before it + self.set_guest_stack_data_region( + 0x0, // start at base of custom guest memory region, + None, // don't override the stack size + ); + + let guest_stack_size = self.get_guest_stack_data_size(); + + self.set_guest_heap_data_region( + guest_stack_size, // start at the end of the stack + None, // don't override the heap size + ); + + let guest_heap_size = self.get_guest_heap_data_size(); + + self.set_input_data_region( + guest_stack_size + guest_heap_size, // start at the end of the heap + PAGE_SIZE as u64 * 4, // 16KB + ); + + self.set_output_data_region( + guest_stack_size + guest_heap_size + PAGE_SIZE as u64 * 4, // start at the end of the input data + PAGE_SIZE as u64 * 4, // 16KB + ); + + self.set_guest_panic_context_region( + guest_stack_size + guest_heap_size + PAGE_SIZE as u64 * 8, // start at the end of the output data + PAGE_SIZE as u64, // 4KB + ); + } + + /// Sets the guest stack data region. + /// - HyperlightPEB is always set with a default size for stack from the guest binary, there's an + /// option to override this size with the `size_override` parameter. + + pub fn set_guest_stack_data_region(&mut self, offset: u64, size_override: Option) { + let size = size_override.unwrap_or_else(|| { + self.guest_stack_data + .as_ref() + .expect("Guest stack region must be defined") + .size + }); + self.guest_stack_data = Some(MemoryRegion { + offset: Some(offset), + size, + }); + + if size == 0 { + panic!("Stack data size is 0 after setting guest stack data region"); + } + } + + /// Get guest stack data region (offset + size). + pub fn get_guest_stack_data_region(&self) -> Option { + self.guest_stack_data.clone() + } + + /// Gets the guest stack data region depending on the running mode (i.e., if `RunMode::Hypervisor` this + /// returns the stack data guest address. If `RunMode::InProcessWindows` or `RunMode::InProcessLinux`, it + /// returns the stack data host address). + pub fn get_stack_data_address(&self) -> u64 { + let region = self + .guest_stack_data + .as_ref() + .expect("Stack data region not set"); + match self.run_mode { + RunMode::Hypervisor => region.offset.unwrap() + self.guest_memory_base_address, + RunMode::InProcessWindows | RunMode::InProcessLinux => { + region.offset.unwrap() + self.guest_memory_host_base_address + } + _ => panic!("Invalid running mode"), + } + } + + /// Returns the size of the guest stack data region. Panics if region is not set. + pub fn get_guest_stack_data_size(&self) -> u64 { + self.guest_stack_data + .as_ref() + .expect("Stack data region is not set") + .size + } + + /// Gets the top of the guest stack data region (i.e., guest memory base address + guest stack + /// offset + guest stack size). + pub fn get_top_of_guest_stack_data(&self) -> u64 { + let region = self + .guest_stack_data + .as_ref() + .expect("Guest stack data region not set"); + region.offset.unwrap() + self.guest_memory_base_address + region.size + } + + /// Sets the guest heap data region. + /// - HyperlightPEB is always set with a default size for heap from the guest binary, there's an + /// option to override this size with the `size_override` parameter. + pub fn set_guest_heap_data_region(&mut self, offset: u64, size_override: Option) { + let size = size_override.unwrap_or_else(|| { + self.guest_heap_data + .as_ref() + .expect("Guest heap region must be defined") + .size + }); + self.guest_heap_data = Some(MemoryRegion { + offset: Some(offset), + size, + }); + + if size == 0 { + panic!("Heap data size is 0 after setting guest heap data region"); + } + } + + /// Gets the guest heap data region depending on the running mode (i.e., if `RunMode::Hypervisor` this + /// returns the heap data guest address. If `RunMode::InProcessWindows` or `RunMode::InProcessLinux`, it + /// returns the heap data host address). + pub fn get_heap_data_address(&self) -> u64 { + let region = self + .guest_heap_data + .as_ref() + .expect("Heap data region not set"); + match self.run_mode { + RunMode::Hypervisor => region.offset.unwrap() + self.guest_memory_base_address, + RunMode::InProcessWindows | RunMode::InProcessLinux => { + region.offset.unwrap() + self.guest_memory_host_base_address + } + _ => panic!("Invalid running mode"), + } + } + + /// Returns the size of the guest heap data region. Panics if region is not set. + pub fn get_guest_heap_data_size(&self) -> u64 { + self.guest_heap_data + .as_ref() + .expect("Heap data region is not set") + .size + } + + /// Sets the input data region. + pub fn set_input_data_region(&mut self, offset: u64, size: u64) { + self.input_data = Some(MemoryRegion { + offset: Some(offset), + size, + }); + } + + /// Gets the input data region with guest addresses. + pub fn get_input_data_guest_region(&self) -> (u64, u64) { + let region = self.input_data.as_ref().expect("Input data region not set"); + ( + region.offset.unwrap() + self.guest_memory_base_address, + region.size, + ) + } + + /// Gets the input data region with host addresses. + pub fn get_input_data_host_region(&self) -> (u64, u64) { + let region = self.input_data.as_ref().expect("Input data region not set"); + ( + region.offset.unwrap() + self.guest_memory_host_base_address, + region.size, + ) + } + + /// Gets the input data region based on the running mode (i.e., if `RunMode::Hypervisor` this + /// function outputs the same as `get_input_data_guest_region`. If `RunMode::InProcessWindows` + /// or `RunMode::InProcessLinux`, it outputs the same as `get_input_data_host_region`). + pub fn get_input_data_region(&self) -> (u64, u64) { + match self.run_mode { + RunMode::Hypervisor => self.get_input_data_guest_region(), + RunMode::InProcessWindows | RunMode::InProcessLinux => { + self.get_input_data_host_region() + } + _ => panic!("Invalid running mode"), + } + } + + /// Sets the output data region. + pub fn set_output_data_region(&mut self, offset: u64, size: u64) { + self.output_data = Some(MemoryRegion { + offset: Some(offset), + size, + }); + } + + /// Gets the output data region with guest addresses. + pub fn get_output_data_guest_region(&self) -> (u64, u64) { + let region = self + .output_data + .as_ref() + .expect("Output data region not set"); + ( + region.offset.unwrap() + self.guest_memory_base_address, + region.size, + ) + } + + /// Gets the output data region with host addresses. + pub fn get_output_data_host_region(&self) -> (u64, u64) { + let region = self + .output_data + .as_ref() + .expect("Output data region not set"); + ( + region.offset.unwrap() + self.guest_memory_host_base_address, + region.size, + ) + } + + /// Gets the output data region based on the running mode (i.e., if `RunMode::Hypervisor` this + /// returns the same as `get_output_data_guest_region`. If `RunMode::InProcessWindows` + /// or `RunMode::InProcessLinux`, it returns the same as `get_output_data_host_region`). + pub fn get_output_data_region(&self) -> (u64, u64) { + match self.run_mode { + RunMode::Hypervisor => self.get_output_data_guest_region(), + RunMode::InProcessWindows | RunMode::InProcessLinux => { + self.get_output_data_host_region() + } + _ => panic!("Invalid running mode"), + } + } + + /// Sets the guest panic context region. + pub fn set_guest_panic_context_region(&mut self, offset: u64, size: u64) { + self.guest_panic_context = Some(MemoryRegion { + offset: Some(offset), + size, + }); + } + + /// Gets the guest panic context region depending on the running mode (i.e., if `RunMode::Hypervisor` this + /// returns the same as `get_guest_panic_context_guest_address`. If `RunMode::InProcessWindows` or `RunMode::InProcessLinux`, it + /// returns the panic context host address). + pub fn get_guest_panic_context_address(&self) -> u64 { + let region = self + .guest_panic_context + .as_ref() + .expect("Guest panic context region not set"); + match self.run_mode { + RunMode::Hypervisor => self.get_guest_panic_context_guest_address(), + RunMode::InProcessWindows | RunMode::InProcessLinux => { + region.offset.unwrap() + self.guest_memory_host_base_address + } + _ => panic!("Invalid running mode"), + } + } + + /// Gets the guest panic context region with guest addresses. + pub fn get_guest_panic_context_guest_address(&self) -> u64 { + self.guest_panic_context + .as_ref() + .expect("Guest panic context region not set") + .offset + .unwrap() + + self.guest_memory_base_address + } + + /// Gets the guest panic context size. + pub fn get_guest_panic_context_size(&self) -> u64 { + self.guest_panic_context + .as_ref() + .expect("Guest panic context region not set") + .size + } + + /// Sets the pointer to the outb handler function used to simulate the outb instruction when running + /// in-process. + pub fn set_outb_ptr(&mut self, ptr: u64) { + self.outb_ptr = ptr; + } + + /// Gets the pointer to the outb handler function. + pub fn get_outb_ptr(&self) -> u64 { + self.outb_ptr + } + + /// Sets the outb pointer context used together with the outb ptr when running in-process. + pub fn set_outb_ptr_ctx(&mut self, ptr: u64) { + self.outb_ptr_ctx = ptr; + } + + /// Gets the outb pointer context. + pub fn get_outb_ptr_ctx(&self) -> u64 { + self.outb_ptr_ctx + } + + /// Gets the run mode. + pub fn get_run_mode(&self) -> RunMode { + self.run_mode.clone() + } + + /// Sets the guest function dispatch pointer. + pub fn set_guest_function_dispatch_ptr(&mut self, ptr: u64) { + self.guest_function_dispatch_ptr = ptr; + } + + /// Gets the guest function dispatch pointer. + pub fn get_guest_function_dispatch_ptr(&self) -> u64 { + self.guest_function_dispatch_ptr + } +} diff --git a/src/hyperlight_guest/src/chkstk.rs b/src/hyperlight_guest/src/chkstk.rs index e3e0dc4fc..e691fb04f 100644 --- a/src/hyperlight_guest/src/chkstk.rs +++ b/src/hyperlight_guest/src/chkstk.rs @@ -17,10 +17,11 @@ limitations under the License. use core::arch::global_asm; use core::mem::size_of; -use hyperlight_common::mem::RunMode; +use hyperlight_common::peb::RunMode; +use hyperlight_common::RUNNING_MODE; use crate::guest_error::{set_invalid_runmode_error, set_stack_allocate_error}; -use crate::{MIN_STACK_ADDRESS, RUNNING_MODE}; +use crate::MIN_STACK_ADDRESS; extern "win64" { fn __chkstk(); diff --git a/src/hyperlight_guest/src/entrypoint.rs b/src/hyperlight_guest/src/entrypoint.rs index 2a6f6c562..5b3ab9407 100644 --- a/src/hyperlight_guest/src/entrypoint.rs +++ b/src/hyperlight_guest/src/entrypoint.rs @@ -13,25 +13,21 @@ 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. */ - use core::arch::asm; -use core::ffi::{c_char, c_void, CStr}; +use core::ffi::{c_char, CStr}; use core::ptr::copy_nonoverlapping; -use hyperlight_common::mem::{HyperlightPEB, RunMode}; +use hyperlight_common::outb::{outb, OutBAction}; +use hyperlight_common::peb::{HyperlightPEB, RunMode}; +use hyperlight_common::{OUTB_HANDLER, OUTB_HANDLER_CTX, PEB, RUNNING_MODE}; use log::LevelFilter; use spin::Once; use crate::gdt::load_gdt; -use crate::guest_error::reset_error; use crate::guest_function_call::dispatch_function; use crate::guest_logger::init_logger; -use crate::host_function_call::{outb, OutBAction}; use crate::idtr::load_idt; -use crate::{ - __security_cookie, HEAP_ALLOCATOR, MIN_STACK_ADDRESS, OS_PAGE_SIZE, OUTB_PTR, - OUTB_PTR_WITH_CONTEXT, P_PEB, RUNNING_MODE, -}; +use crate::{__security_cookie, HEAP_ALLOCATOR, MIN_STACK_ADDRESS}; #[inline(never)] pub fn halt() { @@ -57,10 +53,9 @@ pub fn abort_with_code(code: i32) -> ! { /// # Safety /// This function is unsafe because it dereferences a raw pointer. pub unsafe fn abort_with_code_and_message(code: i32, message_ptr: *const c_char) -> ! { - let peb_ptr = P_PEB.unwrap(); copy_nonoverlapping( message_ptr, - (*peb_ptr).guestPanicContextData.guestPanicContextDataBuffer as *mut c_char, + (*PEB).get_guest_panic_context_address() as *mut c_char, CStr::from_ptr(message_ptr).count_bytes() + 1, // +1 for null terminator ); outb(OutBAction::Abort as u16, code as u8); @@ -77,79 +72,76 @@ static INIT: Once = Once::new(); // Note: entrypoint cannot currently have a stackframe >4KB, as that will invoke __chkstk on msvc // target without first having setup global `RUNNING_MODE` variable, which __chkstk relies on. #[no_mangle] -pub extern "win64" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_level: u64) { - if peb_address == 0 { - panic!("PEB address is null"); - } - - INIT.call_once(|| { - unsafe { - P_PEB = Some(peb_address as *mut HyperlightPEB); - let peb_ptr = P_PEB.unwrap(); - __security_cookie = peb_address ^ seed; - - let srand_seed = ((peb_address << 8 ^ seed >> 4) >> 32) as u32; - - // Set the seed for the random number generator for C code using rand; - srand(srand_seed); - - // set up the logger - let max_log_level = LevelFilter::iter() - .nth(max_log_level as usize) - .expect("Invalid log level"); - init_logger(max_log_level); - - match (*peb_ptr).runMode { - RunMode::Hypervisor => { - RUNNING_MODE = RunMode::Hypervisor; - // This static is to make it easier to implement the __chkstk function in assembly. - // It also means that should we change the layout of the struct in the future, we - // don't have to change the assembly code. - MIN_STACK_ADDRESS = (*peb_ptr).gueststackData.minUserStackAddress; - - // Setup GDT and IDT - load_gdt(); - load_idt(); - } - RunMode::InProcessLinux | RunMode::InProcessWindows => { - RUNNING_MODE = (*peb_ptr).runMode; - - OUTB_PTR = { - let outb_ptr: extern "win64" fn(u16, u8) = - core::mem::transmute((*peb_ptr).pOutb); - Some(outb_ptr) - }; - - if (*peb_ptr).pOutbContext.is_null() { - panic!("OutbContext is null"); - } - - OUTB_PTR_WITH_CONTEXT = { - let outb_ptr_with_context: extern "win64" fn(*mut c_void, u16, u8) = - core::mem::transmute((*peb_ptr).pOutb); - Some(outb_ptr_with_context) - }; - } - _ => { - panic!("Invalid runmode in PEB"); - } +pub extern "win64" fn entrypoint(peb_address: u64, seed: u64, max_log_level: u64) { + INIT.call_once(|| unsafe { + PEB = peb_address as *mut HyperlightPEB; + RUNNING_MODE = (*PEB).clone().get_run_mode(); + + // The guest receives an undifferentiated block of memory that it can address as it sees fit. + // This 'addressing' is done by writing to the PEB the guest's memory layout via this function, + // or by directly altering the PEB. `set_default_memory_layout` will configure the PEB to + // with a memory layout that is compatible with the expectations of guests that use the + // `hyperlight_guest` library (e.g., simpleguest, and callbackguest). + (*PEB).set_default_memory_layout(); + + // The guest sets the address to a "guest function dispatch" function, which is a function + // that is called by the host to dispatch calls to guest functions. + (*PEB).set_guest_function_dispatch_ptr(dispatch_function as u64); + + // Set up the guest heap + HEAP_ALLOCATOR + .try_lock() + .expect("Failed to access HEAP_ALLOCATOR") + .init( + (*PEB).get_heap_data_address() as usize, + (*PEB).get_guest_heap_data_size() as usize, + ); + + __security_cookie = peb_address ^ seed; + + // Set the seed for the random number generator for C code using rand; + let srand_seed = ((peb_address << 8 ^ seed >> 4) >> 32) as u32; + srand(srand_seed); + + // Set up the logger + let max_log_level = LevelFilter::iter() + .nth(max_log_level as usize) + .expect("Invalid log level"); + init_logger(max_log_level); + + match RUNNING_MODE { + RunMode::Hypervisor => { + // This static is to make it easier to implement the __chkstk function in assembly. + // It also means that, should we change the layout of the struct in the future, we + // don't have to change the assembly code. Plus, while this could be accessible via + // the PEB, we don't want to expose it entirely to user code. + MIN_STACK_ADDRESS = (*PEB).get_stack_data_address(); + + // Setup GDT and IDT + load_gdt(); + load_idt(); } + RunMode::InProcessLinux | RunMode::InProcessWindows => { + OUTB_HANDLER = { + let outb_handler: extern "sysv64" fn(u16, u8) = + core::mem::transmute((*PEB).get_outb_ptr()); + Some(outb_handler) + }; + + if (*PEB).get_outb_ptr_ctx() == 0 { + panic!("outb_ptr_ctx is null"); + } - let heap_start = (*peb_ptr).guestheapData.guestHeapBuffer as usize; - let heap_size = (*peb_ptr).guestheapData.guestHeapSize as usize; - HEAP_ALLOCATOR - .try_lock() - .expect("Failed to access HEAP_ALLOCATOR") - .init(heap_start, heap_size); - - OS_PAGE_SIZE = ops as u32; - - (*peb_ptr).guest_function_dispatch_ptr = dispatch_function as usize as u64; - - reset_error(); - - hyperlight_main(); + OUTB_HANDLER_CTX = { + let outb_handler_ctx: extern "sysv64" fn(*mut core::ffi::c_void, u16, u8) = + core::mem::transmute((*PEB).get_outb_ptr()); + Some(outb_handler_ctx) + }; + } + _ => panic!("Invalid runmode in PEB"), } + + hyperlight_main(); }); halt(); diff --git a/src/hyperlight_guest/src/gdt.rs b/src/hyperlight_guest/src/gdt.rs index 62f70e44c..aa0e05641 100644 --- a/src/hyperlight_guest/src/gdt.rs +++ b/src/hyperlight_guest/src/gdt.rs @@ -72,6 +72,9 @@ struct GdtPointer { } /// Load the GDT +/// +/// # Safety +/// TODO pub unsafe fn load_gdt() { let gdt_ptr = GdtPointer { size: (core::mem::size_of::<[GdtEntry; 3]>() - 1) as u16, diff --git a/src/hyperlight_guest/src/guest_error.rs b/src/hyperlight_guest/src/guest_error.rs index c255789e3..55ca0b95e 100644 --- a/src/hyperlight_guest/src/guest_error.rs +++ b/src/hyperlight_guest/src/guest_error.rs @@ -14,73 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -use alloc::string::{String, ToString}; +use alloc::string::ToString; use alloc::vec::Vec; use core::ffi::{c_char, CStr}; use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError}; -use log::error; +use hyperlight_common::input_output::OutputDataSection; +use hyperlight_common::outb::{outb, OutBAction}; +use hyperlight_common::PEB; use crate::entrypoint::halt; -use crate::host_function_call::{outb, OutBAction}; -use crate::P_PEB; pub(crate) fn write_error(error_code: ErrorCode, message: Option<&str>) { + let peb = unsafe { (*PEB).clone() }; + let output_data: OutputDataSection = peb.get_output_data_region().into(); + let guest_error = GuestError::new( error_code.clone(), message.map_or("".to_string(), |m| m.to_string()), ); - let mut guest_error_buffer: Vec = (&guest_error) + + let guest_error_buffer: Vec = (&guest_error) .try_into() .expect("Invalid guest_error_buffer, could not be converted to a Vec"); - unsafe { - assert!(!(*P_PEB.unwrap()).guestErrorData.guestErrorBuffer.is_null()); - let len = guest_error_buffer.len(); - if guest_error_buffer.len() > (*P_PEB.unwrap()).guestErrorData.guestErrorSize as usize { - error!( - "Guest error buffer is too small to hold the error message: size {} buffer size {} message may be truncated", - guest_error_buffer.len(), - (*P_PEB.unwrap()).guestErrorData.guestErrorSize as usize - ); - // get the length of the message - let message_len = message.map_or("".to_string(), |m| m.to_string()).len(); - // message is too long, truncate it - let truncate_len = message_len - - (guest_error_buffer.len() - - (*P_PEB.unwrap()).guestErrorData.guestErrorSize as usize); - let truncated_message = message - .map_or("".to_string(), |m| m.to_string()) - .chars() - .take(truncate_len) - .collect::(); - let guest_error = GuestError::new(error_code, truncated_message); - guest_error_buffer = (&guest_error) - .try_into() - .expect("Invalid guest_error_buffer, could not be converted to a Vec"); - } - - // Optimally, we'd use copy_from_slice here, but, because - // p_guest_error_buffer is a *mut c_void, we can't do that. - // Instead, we do the copying manually using pointer arithmetic. - // Plus; before, we'd do an assert w/ the result from copy_from_slice, - // but, because copy_nonoverlapping doesn't return anything, we can't do that. - // Instead, we do the prior asserts/checks to check the destination pointer isn't null - // and that there is enough space in the destination buffer for the copy. - let dest_ptr = (*P_PEB.unwrap()).guestErrorData.guestErrorBuffer as *mut u8; - core::ptr::copy_nonoverlapping(guest_error_buffer.as_ptr(), dest_ptr, len); - } -} - -pub(crate) fn reset_error() { - unsafe { - let peb_ptr = P_PEB.unwrap(); - core::ptr::write_bytes( - (*peb_ptr).guestErrorData.guestErrorBuffer, - 0, - (*peb_ptr).guestErrorData.guestErrorSize as usize, - ); - } + output_data + .push_shared_output_data(guest_error_buffer) + .expect("Failed to push shared output data"); } pub(crate) fn set_error(error_code: ErrorCode, message: &str) { diff --git a/src/hyperlight_guest/src/guest_function_call.rs b/src/hyperlight_guest/src/guest_function_call.rs index 60d5548db..c1226c223 100644 --- a/src/hyperlight_guest/src/guest_function_call.rs +++ b/src/hyperlight_guest/src/guest_function_call.rs @@ -20,13 +20,12 @@ use alloc::vec::Vec; use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType}; use hyperlight_common::flatbuffer_wrappers::function_types::ParameterType; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; +use hyperlight_common::input_output::{InputDataSection, OutputDataSection}; use crate::entrypoint::halt; use crate::error::{HyperlightGuestError, Result}; -use crate::guest_error::{reset_error, set_error}; -use crate::shared_input_data::try_pop_shared_input_data_into; -use crate::shared_output_data::push_shared_output_data; -use crate::REGISTERED_GUEST_FUNCTIONS; +use crate::guest_error::set_error; +use crate::{PEB, REGISTERED_GUEST_FUNCTIONS}; type GuestFunc = fn(&FunctionCall) -> Result>; @@ -81,19 +80,26 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result #[no_mangle] #[inline(never)] fn internal_dispatch_function() -> Result<()> { - reset_error(); - #[cfg(debug_assertions)] log::trace!("internal_dispatch_function"); - let function_call = try_pop_shared_input_data_into::() + let peb = unsafe { (*PEB).clone() }; + + let input_data_section: InputDataSection = peb.get_input_data_region().into(); + let output_data_section: OutputDataSection = peb.get_output_data_region().into(); + + let function_call = input_data_section + .try_pop_shared_input_data_into::() .expect("Function call deserialization failed"); let result_vec = call_guest_function(function_call).inspect_err(|e| { set_error(e.kind.clone(), e.message.as_str()); })?; - push_shared_output_data(result_vec) + output_data_section + .push_shared_output_data(result_vec) + .unwrap(); + Ok(()) } // This is implemented as a separate function to make sure that epilogue in the internal_dispatch_function is called before the halt() diff --git a/src/hyperlight_guest/src/host_error.rs b/src/hyperlight_guest/src/host_error.rs deleted file mode 100644 index 048c44089..000000000 --- a/src/hyperlight_guest/src/host_error.rs +++ /dev/null @@ -1,43 +0,0 @@ -/* -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. -*/ - -use core::ffi::c_void; -use core::slice::from_raw_parts; - -use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError}; - -use crate::P_PEB; - -pub(crate) fn check_for_host_error() { - unsafe { - let peb_ptr = P_PEB.unwrap(); - let guest_error_buffer_ptr = (*peb_ptr).guestErrorData.guestErrorBuffer as *mut u8; - let guest_error_buffer_size = (*peb_ptr).guestErrorData.guestErrorSize as usize; - - let guest_error_buffer = from_raw_parts(guest_error_buffer_ptr, guest_error_buffer_size); - - if !guest_error_buffer.is_empty() { - let guest_error = GuestError::try_from(guest_error_buffer).expect("Invalid GuestError"); - if guest_error.code != ErrorCode::NoError { - (*peb_ptr).outputdata.outputDataBuffer = usize::MAX as *mut c_void; - panic!( - "Guest Error: {:?} - {}", - guest_error.code, guest_error.message - ); - } - } - } -} diff --git a/src/hyperlight_guest/src/host_function_call.rs b/src/hyperlight_guest/src/host_function_call.rs deleted file mode 100644 index c5a496c8c..000000000 --- a/src/hyperlight_guest/src/host_function_call.rs +++ /dev/null @@ -1,143 +0,0 @@ -/* -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. -*/ - -use alloc::format; -use alloc::string::ToString; -use alloc::vec::Vec; -use core::arch::global_asm; - -use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType}; -use hyperlight_common::flatbuffer_wrappers::function_types::{ - ParameterValue, ReturnType, ReturnValue, -}; -use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; -use hyperlight_common::mem::RunMode; - -use crate::error::{HyperlightGuestError, Result}; -use crate::host_error::check_for_host_error; -use crate::host_functions::validate_host_function_call; -use crate::shared_input_data::try_pop_shared_input_data_into; -use crate::shared_output_data::push_shared_output_data; -use crate::{OUTB_PTR, OUTB_PTR_WITH_CONTEXT, P_PEB, RUNNING_MODE}; - -pub enum OutBAction { - Log = 99, - CallFunction = 101, - Abort = 102, -} - -/// Get a return value from a host function call. -/// This usually requires a host function to be called first using `call_host_function`. -pub fn get_host_return_value>() -> Result { - let return_value = try_pop_shared_input_data_into::() - .expect("Unable to deserialize a return value from host"); - T::try_from(return_value).map_err(|_| { - HyperlightGuestError::new( - ErrorCode::GuestError, - format!( - "Host return value was not a {} as expected", - core::any::type_name::() - ), - ) - }) -} - -// TODO: Make this generic, return a Result this should allow callers to call this function and get the result type they expect -// without having to do the conversion themselves - -pub fn call_host_function( - function_name: &str, - parameters: Option>, - return_type: ReturnType, -) -> Result<()> { - let host_function_call = FunctionCall::new( - function_name.to_string(), - parameters, - FunctionCallType::Host, - return_type, - ); - - validate_host_function_call(&host_function_call)?; - - let host_function_call_buffer: Vec = host_function_call - .try_into() - .expect("Unable to serialize host function call"); - - push_shared_output_data(host_function_call_buffer)?; - - outb(OutBAction::CallFunction as u16, 0); - - Ok(()) -} - -pub fn outb(port: u16, value: u8) { - unsafe { - match RUNNING_MODE { - RunMode::Hypervisor => { - hloutb(port, value); - } - RunMode::InProcessLinux | RunMode::InProcessWindows => { - if let Some(outb_func) = OUTB_PTR_WITH_CONTEXT { - if let Some(peb_ptr) = P_PEB { - outb_func((*peb_ptr).pOutbContext, port, value); - } - } else if let Some(outb_func) = OUTB_PTR { - outb_func(port, value); - } else { - panic!("Tried to call outb without hypervisor and without outb function ptrs"); - } - } - _ => { - panic!("Tried to call outb in invalid runmode"); - } - } - - check_for_host_error(); - } -} - -extern "win64" { - fn hloutb(port: u16, value: u8); -} - -pub fn print_output_as_guest_function(function_call: &FunctionCall) -> Result> { - if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() { - call_host_function( - "HostPrint", - Some(Vec::from(&[ParameterValue::String(message.to_string())])), - ReturnType::Int, - )?; - let res_i = get_host_return_value::()?; - Ok(get_flatbuffer_result(res_i)) - } else { - Err(HyperlightGuestError::new( - ErrorCode::GuestError, - "Wrong Parameters passed to print_output_as_guest_function".to_string(), - )) - } -} - -// port: RCX(cx), value: RDX(dl) -global_asm!( - ".global hloutb - hloutb: - xor rax, rax - mov al, dl - mov dx, cx - out dx, al - ret" -); diff --git a/src/hyperlight_guest/src/host_functions.rs b/src/hyperlight_guest/src/host_functions.rs deleted file mode 100644 index e35fa6d2e..000000000 --- a/src/hyperlight_guest/src/host_functions.rs +++ /dev/null @@ -1,112 +0,0 @@ -/* -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. -*/ - -use alloc::format; -use alloc::string::ToString; -use alloc::vec::Vec; -use core::slice::from_raw_parts; - -use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall; -use hyperlight_common::flatbuffer_wrappers::function_types::ParameterType; -use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails; - -use crate::error::{HyperlightGuestError, Result}; -use crate::P_PEB; - -pub(crate) fn validate_host_function_call(function_call: &FunctionCall) -> Result<()> { - // get host function details - let host_function_details = get_host_function_details(); - - // check if there are any host functions - if host_function_details.host_functions.is_none() { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - "No host functions found".to_string(), - )); - } - - // check if function w/ given name exists - let host_function = if let Some(host_function) = - host_function_details.find_by_function_name(&function_call.function_name) - { - host_function - } else { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - format!( - "Host Function Not Found: {}", - function_call.function_name.clone() - ), - )); - }; - - let function_call_fparameters = if let Some(parameters) = function_call.parameters.clone() { - parameters - } else { - if host_function.parameter_types.is_some() { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - format!( - "Incorrect parameter count for function: {}", - function_call.function_name.clone() - ), - )); - } - - Vec::new() // if no parameters (and no mismatches), return empty vector - }; - - let function_call_parameter_types = function_call_fparameters - .iter() - .map(|p| p.into()) - .collect::>(); - - // Verify that the function call has the correct parameter types. - host_function - .verify_equal_parameter_types(&function_call_parameter_types) - .map_err(|_| { - HyperlightGuestError::new( - ErrorCode::GuestError, - format!( - "Incorrect parameter type for function: {}", - function_call.function_name.clone() - ), - ) - })?; - - Ok(()) -} - -pub fn get_host_function_details() -> HostFunctionDetails { - let peb_ptr = unsafe { P_PEB.unwrap() }; - - let host_function_details_buffer = - unsafe { (*peb_ptr).hostFunctionDefinitions.fbHostFunctionDetails } as *const u8; - let host_function_details_size = - unsafe { (*peb_ptr).hostFunctionDefinitions.fbHostFunctionDetailsSize }; - - let host_function_details_slice: &[u8] = unsafe { - from_raw_parts( - host_function_details_buffer, - host_function_details_size as usize, - ) - }; - - host_function_details_slice - .try_into() - .expect("Failed to convert buffer to HostFunctionDetails") -} diff --git a/src/hyperlight_guest/src/idtr.rs b/src/hyperlight_guest/src/idtr.rs index 598e62ff0..c572279cc 100644 --- a/src/hyperlight_guest/src/idtr.rs +++ b/src/hyperlight_guest/src/idtr.rs @@ -11,11 +11,19 @@ pub struct Idtr { static mut IDTR: Idtr = Idtr { limit: 0, base: 0 }; impl Idtr { + /// Initializes the IDTR with the given base address and size. + /// + /// # Safety + /// TODO pub unsafe fn init(&mut self, base: u64, size: u16) { self.limit = size - 1; self.base = base; } + /// Loads the IDTR with the current IDT. + /// + /// # Safety + /// TODO pub unsafe fn load(&self) { core::arch::asm!("lidt [{}]", in(reg) self, options(readonly, nostack, preserves_flags)); } diff --git a/src/hyperlight_guest/src/lib.rs b/src/hyperlight_guest/src/lib.rs index 181274c1e..49e659e38 100644 --- a/src/hyperlight_guest/src/lib.rs +++ b/src/hyperlight_guest/src/lib.rs @@ -23,28 +23,20 @@ use core::ptr::copy_nonoverlapping; use buddy_system_allocator::LockedHeap; use guest_function_register::GuestFunctionRegister; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use hyperlight_common::mem::{HyperlightPEB, RunMode}; +use hyperlight_common::outb::{outb, OutBAction}; +use hyperlight_common::PEB; -use crate::host_function_call::{outb, OutBAction}; extern crate alloc; // Modules pub mod entrypoint; -pub mod shared_input_data; -pub mod shared_output_data; - pub mod guest_error; pub mod guest_function_call; pub mod guest_function_definition; pub mod guest_function_register; -pub mod host_error; -pub mod host_function_call; -pub mod host_functions; - pub(crate) mod guest_logger; pub mod memory; -pub mod print; pub(crate) mod security_check; pub mod setjmp; @@ -75,11 +67,10 @@ pub(crate) static _fltused: i32 = 0; #[allow(dead_code)] fn panic(info: &core::panic::PanicInfo) -> ! { unsafe { - let peb_ptr = P_PEB.unwrap(); copy_nonoverlapping( info.to_string().as_ptr(), - (*peb_ptr).guestPanicContextData.guestPanicContextDataBuffer as *mut u8, - (*peb_ptr).guestPanicContextData.guestPanicContextDataSize as usize, + (*PEB).get_guest_panic_context_address() as *mut u8, + (*PEB).get_guest_panic_context_size() as usize, ); } outb(OutBAction::Abort as u16, ErrorCode::UnknownError as u8); @@ -94,15 +85,7 @@ pub(crate) static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::empty(); #[no_mangle] pub(crate) static mut __security_cookie: u64 = 0; -pub(crate) static mut P_PEB: Option<*mut HyperlightPEB> = None; pub static mut MIN_STACK_ADDRESS: u64 = 0; -pub static mut OS_PAGE_SIZE: u32 = 0; -pub(crate) static mut OUTB_PTR: Option = None; -pub(crate) static mut OUTB_PTR_WITH_CONTEXT: Option< - extern "win64" fn(*mut core::ffi::c_void, u16, u8), -> = None; -pub static mut RUNNING_MODE: RunMode = RunMode::None; - pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister = GuestFunctionRegister::new(); diff --git a/src/hyperlight_guest/src/logging.rs b/src/hyperlight_guest/src/logging.rs index 45cee65dd..4277010db 100644 --- a/src/hyperlight_guest/src/logging.rs +++ b/src/hyperlight_guest/src/logging.rs @@ -19,9 +19,9 @@ use alloc::vec::Vec; use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData; use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel; - -use crate::host_function_call::{outb, OutBAction}; -use crate::shared_output_data::push_shared_output_data; +use hyperlight_common::input_output::OutputDataSection; +use hyperlight_common::outb::{outb, OutBAction}; +use hyperlight_common::PEB; fn write_log_data( log_level: LogLevel, @@ -44,7 +44,13 @@ fn write_log_data( .try_into() .expect("Failed to convert GuestLogData to bytes"); - push_shared_output_data(bytes).expect("Unable to push log data to shared output data"); + let output_data_section: OutputDataSection = unsafe { (*PEB).clone() } + .get_output_data_guest_region() + .into(); + + output_data_section + .push_shared_output_data(bytes) + .expect("Unable to push log data to shared output data"); } pub fn log_message( diff --git a/src/hyperlight_guest/src/print.rs b/src/hyperlight_guest/src/print.rs deleted file mode 100644 index b7dd8404f..000000000 --- a/src/hyperlight_guest/src/print.rs +++ /dev/null @@ -1,68 +0,0 @@ -/* -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. -*/ - -use alloc::string::String; -use alloc::vec::Vec; -use core::ffi::{c_char, CStr}; -use core::mem; - -use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; - -use crate::host_function_call::call_host_function; - -const BUFFER_SIZE: usize = 1000; - -static mut MESSAGE_BUFFER: Vec = Vec::new(); - -/// Exposes a C API to allow the guest to print a string -/// -/// # Safety -/// This function is not thread safe -#[no_mangle] -#[allow(static_mut_refs)] -pub unsafe extern "C" fn _putchar(c: c_char) { - let char = c as u8; - - // Extend buffer capacity if it's empty (like `with_capacity` in lazy_static). - // TODO: replace above Vec::new() with Vec::with_capacity once it's stable in const contexts. - if MESSAGE_BUFFER.capacity() == 0 { - MESSAGE_BUFFER.reserve(BUFFER_SIZE); - } - - MESSAGE_BUFFER.push(char); - - if MESSAGE_BUFFER.len() == BUFFER_SIZE || char == b'\0' { - let str = if char == b'\0' { - CStr::from_bytes_until_nul(&MESSAGE_BUFFER) - .expect("No null byte in buffer") - .to_string_lossy() - .into_owned() - } else { - String::from_utf8(mem::take(&mut MESSAGE_BUFFER)) - .expect("Failed to convert buffer to string") - }; - - call_host_function( - "HostPrint", - Some(Vec::from(&[ParameterValue::String(str)])), - ReturnType::Void, - ) - .expect("Failed to call HostPrint"); - - // Clear the buffer after sending - MESSAGE_BUFFER.clear(); - } -} diff --git a/src/hyperlight_guest/src/shared_input_data.rs b/src/hyperlight_guest/src/shared_input_data.rs deleted file mode 100644 index 8d943db07..000000000 --- a/src/hyperlight_guest/src/shared_input_data.rs +++ /dev/null @@ -1,90 +0,0 @@ -/* -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. -*/ - -use alloc::format; -use alloc::string::ToString; -use core::any::type_name; -use core::slice::from_raw_parts_mut; - -use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; - -use crate::error::{HyperlightGuestError, Result}; -use crate::P_PEB; - -// Pops the top element from the shared input data buffer and returns it as a T -pub fn try_pop_shared_input_data_into() -> Result -where - T: for<'a> TryFrom<&'a [u8]>, -{ - let peb_ptr = unsafe { P_PEB.unwrap() }; - let shared_buffer_size = unsafe { (*peb_ptr).inputdata.inputDataSize as usize }; - - let idb = unsafe { - from_raw_parts_mut( - (*peb_ptr).inputdata.inputDataBuffer as *mut u8, - shared_buffer_size, - ) - }; - - if idb.is_empty() { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - "Got a 0-size buffer in pop_shared_input_data_into".to_string(), - )); - } - - // get relative offset to next free address - let stack_ptr_rel: usize = - usize::from_le_bytes(idb[..8].try_into().expect("Shared input buffer too small")); - - if stack_ptr_rel > shared_buffer_size || stack_ptr_rel < 16 { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - format!( - "Invalid stack pointer: {} in pop_shared_input_data_into", - stack_ptr_rel - ), - )); - } - - // go back 8 bytes and read. This is the offset to the element on top of stack - let last_element_offset_rel = usize::from_le_bytes( - idb[stack_ptr_rel - 8..stack_ptr_rel] - .try_into() - .expect("Invalid stack pointer in pop_shared_input_data_into"), - ); - - let buffer = &idb[last_element_offset_rel..]; - - // convert the buffer to T - let type_t = match T::try_from(buffer) { - Ok(t) => Ok(t), - Err(_e) => { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - format!("Unable to convert buffer to {}", type_name::()), - )); - } - }; - - // update the stack pointer to point to the element we just popped of since that is now free - idb[..8].copy_from_slice(&last_element_offset_rel.to_le_bytes()); - - // zero out popped off buffer - idb[last_element_offset_rel..stack_ptr_rel].fill(0); - - type_t -} diff --git a/src/hyperlight_guest/src/shared_output_data.rs b/src/hyperlight_guest/src/shared_output_data.rs deleted file mode 100644 index 520a369f3..000000000 --- a/src/hyperlight_guest/src/shared_output_data.rs +++ /dev/null @@ -1,86 +0,0 @@ -/* -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. -*/ - -use alloc::format; -use alloc::string::ToString; -use alloc::vec::Vec; -use core::slice::from_raw_parts_mut; - -use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; - -use crate::error::{HyperlightGuestError, Result}; -use crate::P_PEB; - -pub fn push_shared_output_data(data: Vec) -> Result<()> { - let peb_ptr = unsafe { P_PEB.unwrap() }; - let shared_buffer_size = unsafe { (*peb_ptr).outputdata.outputDataSize as usize }; - let odb = unsafe { - from_raw_parts_mut( - (*peb_ptr).outputdata.outputDataBuffer as *mut u8, - shared_buffer_size, - ) - }; - - if odb.is_empty() { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - "Got a 0-size buffer in push_shared_output_data".to_string(), - )); - } - - // get offset to next free address on the stack - let stack_ptr_rel: usize = - usize::from_le_bytes(odb[..8].try_into().expect("Shared output buffer too small")); - - // check if the stack pointer is within the bounds of the buffer. - // It can be equal to the size, but never greater - // It can never be less than 8. An empty buffer's stack pointer is 8 - if stack_ptr_rel > shared_buffer_size || stack_ptr_rel < 8 { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - format!( - "Invalid stack pointer: {} in push_shared_output_data", - stack_ptr_rel - ), - )); - } - - // check if there is enough space in the buffer - let size_required = data.len() + 8; // the data plus the pointer pointing to the data - let size_available = shared_buffer_size - stack_ptr_rel; - if size_required > size_available { - return Err(HyperlightGuestError::new( - ErrorCode::GuestError, - format!( - "Not enough space in shared output buffer. Required: {}, Available: {}", - size_required, size_available - ), - )); - } - - // write the actual data - odb[stack_ptr_rel..stack_ptr_rel + data.len()].copy_from_slice(&data); - - // write the offset to the newly written data, to the top of the stack - let bytes = stack_ptr_rel.to_le_bytes(); - odb[stack_ptr_rel + data.len()..stack_ptr_rel + data.len() + 8].copy_from_slice(&bytes); - - // update stack pointer to point to next free address - let new_stack_ptr_rel = stack_ptr_rel + data.len() + 8; - odb[0..8].copy_from_slice(&(new_stack_ptr_rel).to_le_bytes()); - - Ok(()) -} diff --git a/src/hyperlight_host/examples/hello-world/main.rs b/src/hyperlight_host/examples/hello-world/main.rs index 3f2b7fc63..e4bc4ce55 100644 --- a/src/hyperlight_host/examples/hello-world/main.rs +++ b/src/hyperlight_host/examples/hello-world/main.rs @@ -15,48 +15,43 @@ limitations under the License. */ use std::sync::{Arc, Mutex}; -use std::thread; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; -use hyperlight_host::func::HostFunction0; +use hyperlight_host::func::HostFunction2; +use hyperlight_host::sandbox::sandbox_builder::SandboxBuilder; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; -use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::{GuestBinary, MultiUseSandbox}; +use hyperlight_testing::simple_guest_as_string; fn main() -> hyperlight_host::Result<()> { // Create an uninitialized sandbox with a guest binary - let mut uninitialized_sandbox = UninitializedSandbox::new( - hyperlight_host::GuestBinary::FilePath( - hyperlight_testing::simple_guest_as_string().unwrap(), - ), - None, // default configuration - None, // default run options - None, // default host print function - )?; + let sandbox_builder = SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + let mut uninitialized_sandbox = sandbox_builder.build()?; - // Register a host functions - fn sleep_5_secs() -> hyperlight_host::Result<()> { - thread::sleep(std::time::Duration::from_secs(5)); - Ok(()) + // Register a host function + fn add(a: i32, b: i32) -> hyperlight_host::Result { + Ok(a + b) } + let host_function = Arc::new(Mutex::new(add)); + host_function.register(&mut uninitialized_sandbox, "HostAdd")?; - let host_function = Arc::new(Mutex::new(sleep_5_secs)); + let host_function = Arc::new(Mutex::new(add)); - host_function.register(&mut uninitialized_sandbox, "Sleep5Secs")?; - // Note: This function is unused, it's just here for demonstration purposes + host_function.register(&mut uninitialized_sandbox, "HostAdd")?; // Initialize sandbox to be able to call host functions let mut multi_use_sandbox: MultiUseSandbox = uninitialized_sandbox.evolve(Noop::default())?; // Call guest function - let message = "Hello, World! I am executing inside of a VM :)\n".to_string(); let result = multi_use_sandbox.call_guest_function_by_name( - "PrintOutput", // function must be defined in the guest binary + "Add", // function must be defined in the guest binary ReturnType::Int, - Some(vec![ParameterValue::String(message.clone())]), - ); + Some(vec![ParameterValue::Int(1), ParameterValue::Int(41)]), + )?; - assert!(result.is_ok()); + println!("Guest function result: 1 + 41 = {:?}", result); Ok(()) } diff --git a/src/hyperlight_host/src/error.rs b/src/hyperlight_host/src/error.rs index b2ea080ce..0c34b9737 100644 --- a/src/hyperlight_host/src/error.rs +++ b/src/hyperlight_host/src/error.rs @@ -35,20 +35,13 @@ use crossbeam_channel::{RecvError, SendError}; use flatbuffers::InvalidFlatbuffer; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue}; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use serde::{Deserialize, Serialize}; use serde_yaml; use thiserror::Error; #[cfg(target_os = "windows")] use crate::hypervisor::wrappers::HandleWrapper; -use crate::mem::memory_region::MemoryRegionFlags; use crate::mem::ptr::RawPtr; - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] -pub(crate) struct HyperlightHostError { - pub(crate) message: String, - pub(crate) source: String, -} +use crate::sandbox::sandbox_builder::MemoryRegionFlags; /// The error type for Hyperlight operations #[derive(Error, Debug)] @@ -226,8 +219,8 @@ pub enum HyperlightError { NoMemorySnapshot, /// An error occurred handling an outb message - #[error("An error occurred handling an outb message {0:?}: {1}")] - OutBHandlingError(String, String), + #[error("An error occurred handling an outb message {0:?}")] + OutBHandlingError(String), /// Failed to get value from parameter value #[error("Failed To Convert Parameter Value {0:?} to {1:?}")] diff --git a/src/hyperlight_host/src/func/call_ctx.rs b/src/hyperlight_host/src/func/call_ctx.rs index bc48e249c..3c2f8b963 100644 --- a/src/hyperlight_host/src/func/call_ctx.rs +++ b/src/hyperlight_host/src/func/call_ctx.rs @@ -72,7 +72,13 @@ impl MultiUseGuestCallContext { // !Send (and !Sync), we also don't need to worry about // synchronization - call_function_on_guest(&mut self.sbox, func_name, func_ret_type, args) + call_function_on_guest( + &mut self.sbox.hv_handler, + &mut self.sbox.mem_mgr, + func_name, + func_ret_type, + args, + ) } /// Close out the context and get back the internally-stored @@ -93,7 +99,6 @@ impl MultiUseGuestCallContext { /// and is not intended to be called publicly. It allows the state of the guest to be altered /// during the evolution of one sandbox state to another, enabling the new state created /// to be captured and stored in the Sandboxes state stack. - /// pub(crate) fn finish_no_reset(self) -> MultiUseSandbox { self.sbox } @@ -109,15 +114,16 @@ mod tests { }; use hyperlight_testing::simple_guest_as_string; + use crate::sandbox::sandbox_builder::SandboxBuilder; use crate::sandbox_state::sandbox::EvolvableSandbox; use crate::sandbox_state::transition::Noop; - use crate::{GuestBinary, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox}; + use crate::{GuestBinary, MultiUseSandbox, Result, UninitializedSandbox}; fn new_uninit() -> Result { - let path = simple_guest_as_string().map_err(|e| { - HyperlightError::Error(format!("failed to get simple guest path ({e:?})")) - })?; - UninitializedSandbox::new(GuestBinary::FilePath(path), None, None, None) + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + sandbox_builder.build() } /// Test to create a `MultiUseSandbox`, then call several guest functions diff --git a/src/hyperlight_host/src/func/guest_dispatch.rs b/src/hyperlight_host/src/func/guest_dispatch.rs index 4462a8a66..ed53d191d 100644 --- a/src/hyperlight_host/src/func/guest_dispatch.rs +++ b/src/hyperlight_host/src/func/guest_dispatch.rs @@ -21,20 +21,22 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ use tracing::{instrument, Span}; use super::guest_err::check_for_guest_error; -use crate::hypervisor::hypervisor_handler::HypervisorHandlerAction; -use crate::sandbox::WrapperGetter; +use crate::hypervisor::hypervisor_handler::{HypervisorHandler, HypervisorHandlerAction}; +use crate::mem::mgr::SandboxMemoryManager; +use crate::mem::shared_mem::HostSharedMemory; use crate::HyperlightError::GuestExecutionHungOnHostFunctionCall; use crate::{HyperlightError, Result}; /// Call a guest function by name, using the given `wrapper_getter`. #[instrument( err(Debug), - skip(wrapper_getter, args), + skip(hv_handler, mem_mgr, args), parent = Span::current(), level = "Trace" )] -pub(crate) fn call_function_on_guest( - wrapper_getter: &mut WrapperGetterT, +pub(crate) fn call_function_on_guest( + hv_handler: &mut HypervisorHandler, + mem_mgr: &mut SandboxMemoryManager, function_name: &str, return_type: ReturnType, args: Option>, @@ -52,12 +54,13 @@ pub(crate) fn call_function_on_guest( .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 input_data_region = mem_mgr + .memory_sections + .read_hyperlight_peb()? + .get_input_data_guest_region(); + + mem_mgr.write_guest_function_call(input_data_region, &buffer)?; - let mut hv_handler = wrapper_getter.get_hv_handler().clone(); match hv_handler.execute_hypervisor_handler_action( HypervisorHandlerAction::DispatchCallFromHost(function_name.to_string()), ) { @@ -65,9 +68,7 @@ pub(crate) fn call_function_on_guest( 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(), - )? { + match hv_handler.terminate_hypervisor_handler_execution_and_reinitialise(mem_mgr)? { HyperlightError::HypervisorHandlerExecutionCancelAttemptOnFinishedExecution() => {} // ^^^ do nothing, we just want to actually get the Flatbuffer return value @@ -79,13 +80,16 @@ pub(crate) fn call_function_on_guest( }, }; - let mem_mgr = wrapper_getter.get_mgr_wrapper_mut(); mem_mgr.check_stack_guard()?; // <- wrapper around mem_mgr `check_for_stack_guard` check_for_guest_error(mem_mgr)?; + let output_data_region = mem_mgr + .memory_sections + .read_hyperlight_peb()? + .get_output_data_guest_region(); + mem_mgr - .as_mut() - .get_guest_function_call_result() + .get_guest_function_call_result(output_data_region) .map_err(|e| { if timedout { // if we timed-out, but still got here @@ -103,414 +107,414 @@ pub(crate) fn call_function_on_guest( }) } -#[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; - 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, UninitializedSandbox}; - - // simple function - fn test_function0(_: MultiUseGuestCallContext) -> Result { - Ok(42) - } - - struct GuestStruct; - - // function that return type unsupported by the host - fn test_function1(_: MultiUseGuestCallContext) -> Result { - Ok(GuestStruct) - } - - // function that takes a parameter - fn test_function2(_: MultiUseGuestCallContext, param: i32) -> Result { - Ok(param) - } - - #[test] - // TODO: Investigate why this test fails with an incorrect error when run alongside other tests - #[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 { - let pid = unsafe { libc::syscall(libc::SYS_getpid) }; - Ok(pid as u64) - } - - // First, run to make sure it fails. - { - 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), - } - } - - // Second, run with allowing `SYS_getpid` - #[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], - )?; - // ^^^ note, we are allowing 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() - }; - - // test_function0 - { - 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); - } - - // test_function1 - { - let usbox = uninitialized_sandbox(); - let sandbox: MultiUseSandbox = usbox - .evolve(Noop::default()) - .expect("Failed to initialize sandbox"); - let result = test_function1(sandbox.new_call_context()); - assert!(result.is_ok()); - } - - // test_function2 - { - 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); - } - - // test concurrent calls with a local closure that returns current count - { - 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 || { - // we're not actually using the context, but we're calling - // it here to test the mutual exclusion - 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(); - } - - // Check if the order of operations is sequential - let order = order - .try_lock() - .map_err(|_| new_error!("Error locking")) - .unwrap(); - for i in 0..10 { - assert_eq!(order[i], i + 1); - } - } - - // TODO: Add tests to ensure State has been reset. - } - - #[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() { - // in-hypervisor mode - let u_sbox = UninitializedSandbox::new( - guest_bin(), - // for now, we're using defaults. In the future, we should get - // variability below - None, - // by default, the below represents in-hypervisor mode - None, - // just use the built-in host print function - 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<()> { - // This test relies upon a Hypervisor being present so for now - // we will skip it if there isn't one. - 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 that we can terminate a VCPU that has been running the VCPU for too long. - #[test] - fn test_terminate_vcpu_spinning_cpu() -> Result<()> { - terminate_vcpu_after_1000ms()?; - Ok(()) - } - - // Test that we can terminate a VCPU that has been running the VCPU for too long and then call a guest function on the same host thread. - #[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(()) - } - - // This test is to capture the case where the guest execution is running a host function when cancelled and that host function - // is never going to return. - // The host function that is called will end after 5 seconds, but by this time the cancellation will have given up - // (using default timeout settings) , so this tests looks for the error "Failed to cancel guest execution". - - #[test] - fn test_terminate_vcpu_calling_host_spinning_cpu() { - // This test relies upon a Hypervisor being present so for now - // we will skip it if there isn't one. - 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(); - - // Make this host call run for 5 seconds - - 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 - ), - } - } - - #[test] - #[cfg(not(inprocess))] - fn test_trigger_exception_on_guest() { - let usbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), - None, - None, - None, - ) - .unwrap(); - - let mut multi_use_sandbox: MultiUseSandbox = usbox.evolve(Noop::default()).unwrap(); - - let res = multi_use_sandbox.call_guest_function_by_name( - "TriggerException", - ReturnType::Void, - None, - ); - - assert!(res.is_err()); - - match res.unwrap_err() { - HyperlightError::GuestAborted(_, msg) => { - // msg should indicate we got an invalid opcode exception - assert!(msg.contains("EXCEPTION: 0x6")); - } - e => panic!( - "Expected HyperlightError::GuestExecutionError but got {:?}", - e - ), - } - } -} +// TODO(danbugs:297): bring back +// #[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; +// use crate::func::host_functions::HostFunction0; +// use crate::sandbox::is_hypervisor_present; +// use crate::sandbox_state::sandbox::EvolvableSandbox; +// use crate::sandbox_state::transition::Noop; +// use crate::{new_error, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox}; +// +// // simple function +// fn test_function0(_: MultiUseGuestCallContext) -> Result { +// Ok(42) +// } +// +// struct GuestStruct; +// +// // function that return type unsupported by the host +// fn test_function1(_: MultiUseGuestCallContext) -> Result { +// Ok(GuestStruct) +// } +// +// // function that takes a parameter +// fn test_function2(_: MultiUseGuestCallContext, param: i32) -> Result { +// Ok(param) +// } +// +// #[test] +// // TODO: Investigate why this test fails with an incorrect error when run alongside other tests +// #[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 { +// let pid = unsafe { libc::syscall(libc::SYS_getpid) }; +// Ok(pid as u64) +// } +// +// // First, run to make sure it fails. +// { +// 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), +// } +// } +// +// // Second, run with allowing `SYS_getpid` +// #[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], +// )?; +// // ^^^ note, we are allowing 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() +// }; +// +// // test_function0 +// { +// 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); +// } +// +// // test_function1 +// { +// let usbox = uninitialized_sandbox(); +// let sandbox: MultiUseSandbox = usbox +// .evolve(Noop::default()) +// .expect("Failed to initialize sandbox"); +// let result = test_function1(sandbox.new_call_context()); +// assert!(result.is_ok()); +// } +// +// // test_function2 +// { +// 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); +// } +// +// // test concurrent calls with a local closure that returns current count +// { +// 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 || { +// // we're not actually using the context, but we're calling +// // it here to test the mutual exclusion +// 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(); +// } +// +// // Check if the order of operations is sequential +// let order = order +// .try_lock() +// .map_err(|_| new_error!("Error locking")) +// .unwrap(); +// for i in 0..10 { +// assert_eq!(order[i], i + 1); +// } +// } +// +// // TODO: Add tests to ensure State has been reset. +// } +// +// #[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() { +// // in-hypervisor mode +// let u_sbox = UninitializedSandbox::new( +// guest_bin(), +// // for now, we're using defaults. In the future, we should get +// // variability below +// None, +// // by default, the below represents in-hypervisor mode +// None, +// // just use the built-in host print function +// 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<()> { +// // This test relies upon a Hypervisor being present so for now +// // we will skip it if there isn't one. +// 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 that we can terminate a VCPU that has been running the VCPU for too long. +// #[test] +// fn test_terminate_vcpu_spinning_cpu() -> Result<()> { +// terminate_vcpu_after_1000ms()?; +// Ok(()) +// } +// +// // Test that we can terminate a VCPU that has been running the VCPU for too long and then call a guest function on the same host thread. +// #[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(()) +// } +// +// // This test is to capture the case where the guest execution is running a host function when cancelled and that host function +// // is never going to return. +// // The host function that is called will end after 5 seconds, but by this time the cancellation will have given up +// // (using default timeout settings) , so this tests looks for the error "Failed to cancel guest execution". +// +// #[test] +// fn test_terminate_vcpu_calling_host_spinning_cpu() { +// // This test relies upon a Hypervisor being present so for now +// // we will skip it if there isn't one. +// 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(); +// +// // Make this host call run for 5 seconds +// +// 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 +// ), +// } +// } +// +// #[test] +// #[cfg(not(inprocess))] +// fn test_trigger_exception_on_guest() { +// let usbox = UninitializedSandbox::new( +// GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), +// None, +// None, +// None, +// ) +// .unwrap(); +// +// let mut multi_use_sandbox: MultiUseSandbox = usbox.evolve(Noop::default()).unwrap(); +// +// let res = multi_use_sandbox.call_guest_function_by_name( +// "TriggerException", +// ReturnType::Void, +// None, +// ); +// +// assert!(res.is_err()); +// +// match res.unwrap_err() { +// HyperlightError::GuestAborted(_, msg) => { +// // msg should indicate we got an invalid opcode exception +// assert!(msg.contains("EXCEPTION: 0x6")); +// } +// e => panic!( +// "Expected HyperlightError::GuestExecutionError but got {:?}", +// e +// ), +// } +// } +// } diff --git a/src/hyperlight_host/src/func/guest_err.rs b/src/hyperlight_host/src/func/guest_err.rs index 9e67ef536..02c966c68 100644 --- a/src/hyperlight_host/src/func/guest_err.rs +++ b/src/hyperlight_host/src/func/guest_err.rs @@ -14,31 +14,39 @@ See the License for the specific language governing permissions and limitations under the License. */ -use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; +use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError as FbGuestError}; use crate::error::HyperlightError::{GuestError, OutBHandlingError, StackOverflow}; +use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; use crate::metrics::{METRIC_GUEST_ERROR, METRIC_GUEST_ERROR_LABEL_CODE}; -use crate::sandbox::mem_mgr::MemMgrWrapper; use crate::{log_then_return, Result}; + /// Check for a guest error and return an `Err` if one was found, /// and `Ok` if one was not found. -pub(crate) fn check_for_guest_error(mgr: &MemMgrWrapper) -> Result<()> { - let guest_err = mgr.as_ref().get_guest_error()?; +pub(crate) fn check_for_guest_error( + mgr: &mut SandboxMemoryManager, +) -> Result<()> { + let peb = mgr.memory_sections.read_hyperlight_peb()?; + let (odr_buffer, odr_size) = peb.get_output_data_guest_region(); + let maybe_guest_err = mgr + .shared_mem + .try_pop_buffer_into::(odr_buffer as usize, odr_size as usize); + + let guest_err = if let Ok(err) = maybe_guest_err { + err + } else { + // No guest error found, return Ok + return Ok(()); + }; + match guest_err.code { ErrorCode::NoError => Ok(()), - ErrorCode::OutbError => match mgr.as_ref().get_host_error()? { - Some(host_err) => { - metrics::counter!(METRIC_GUEST_ERROR, METRIC_GUEST_ERROR_LABEL_CODE => (guest_err.code as u64).to_string()).increment(1); - - log_then_return!(OutBHandlingError( - host_err.source.clone(), - guest_err.message.clone() - )); - } - // TODO: Not sure this is correct behavior. We should probably return error here - None => Ok(()), - }, + ErrorCode::OutbError => { + metrics::counter!(METRIC_GUEST_ERROR, METRIC_GUEST_ERROR_LABEL_CODE => (guest_err.code as u64).to_string()).increment(1); + + log_then_return!(OutBHandlingError(guest_err.message.clone())); + } ErrorCode::StackOverflow => { metrics::counter!(METRIC_GUEST_ERROR, METRIC_GUEST_ERROR_LABEL_CODE => (guest_err.code as u64).to_string()).increment(1); log_then_return!(StackOverflow()); diff --git a/src/hyperlight_host/src/func/host_functions.rs b/src/hyperlight_host/src/func/host_functions.rs index ce30d9c45..f0580032f 100644 --- a/src/hyperlight_host/src/func/host_functions.rs +++ b/src/hyperlight_host/src/func/host_functions.rs @@ -109,7 +109,7 @@ macro_rules! host_function { .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? .register_host_function_with_syscalls( - sandbox.mgr.as_mut(), + &mut sandbox.mem_mgr, &HostFunctionDefinition::new(name.to_string(), None, R::get_hyperlight_type()), HyperlightFunction::new(func), _eas, @@ -126,7 +126,7 @@ macro_rules! host_function { .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? .register_host_function( - sandbox.mgr.as_mut(), + &mut sandbox.mem_mgr, &HostFunctionDefinition::new(name.to_string(), None, R::get_hyperlight_type()), HyperlightFunction::new(func), )?; @@ -236,7 +236,7 @@ macro_rules! host_function { .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? .register_host_function_with_syscalls( - sandbox.mgr.as_mut(), + &mut sandbox.mem_mgr, &HostFunctionDefinition::new( name.to_string(), parameter_types, @@ -257,7 +257,7 @@ macro_rules! host_function { .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? .register_host_function( - sandbox.mgr.as_mut(), + &mut sandbox.mem_mgr, &HostFunctionDefinition::new( name.to_string(), parameter_types, diff --git a/src/hyperlight_host/src/hypervisor/crashdump.rs b/src/hyperlight_host/src/hypervisor/crashdump.rs index a70dc34c1..9c06269c6 100644 --- a/src/hyperlight_host/src/hypervisor/crashdump.rs +++ b/src/hyperlight_host/src/hypervisor/crashdump.rs @@ -16,15 +16,15 @@ pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> { temp_file.write_all(b"================ MEMORY DUMP =================\n")?; // write the raw memory dump for each memory region - for region in hv.get_memory_regions() { - if region.host_region.start == 0 || region.host_region.is_empty() { + for (_, region) in hv.get_memory_sections().iter() { + if region.page_aligned_guest_offset == 0 { continue; } // SAFETY: we got this memory region from the hypervisor so should never be invalid let region_slice = unsafe { std::slice::from_raw_parts( - region.host_region.start as *const u8, - region.host_region.len(), + region.host_address.ok_or("Failed to get host address")? as *const u8, + region.page_aligned_size, ) }; temp_file.write_all(region_slice)?; diff --git a/src/hyperlight_host/src/hypervisor/gdb/mod.rs b/src/hyperlight_host/src/hypervisor/gdb/mod.rs index 95b81ed6a..b27597d47 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/mod.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/mod.rs @@ -31,7 +31,7 @@ use event_loop::event_loop_thread; use gdbstub::conn::ConnectionExt; use gdbstub::stub::GdbStub; use gdbstub::target::TargetError; -use hyperlight_common::mem::PAGE_SIZE; +use hyperlight_common::PAGE_SIZE; #[cfg(kvm)] pub(crate) use kvm_debug::KvmDebug; #[cfg(mshv)] @@ -40,7 +40,7 @@ use thiserror::Error; use x86_64_target::HyperlightSandboxTarget; use crate::hypervisor::handlers::DbgMemAccessHandlerCaller; -use crate::mem::layout::SandboxMemoryLayout; +use crate::sandbox::sandbox_builder::BASE_ADDRESS; use crate::{new_error, HyperlightError}; /// Software Breakpoint size in memory @@ -252,14 +252,16 @@ pub(crate) trait GuestDebug { let read_len = std::cmp::min( data.len(), - (PAGE_SIZE - (gpa & (PAGE_SIZE - 1))).try_into().unwrap(), + (PAGE_SIZE as u64 - (gpa & (PAGE_SIZE as u64 - 1))) + .try_into() + .unwrap(), ); let offset = (gpa as usize) - .checked_sub(SandboxMemoryLayout::BASE_ADDRESS) + .checked_sub(BASE_ADDRESS) .ok_or_else(|| { log::warn!( "gva=0x{:#X} causes subtract with underflow: \"gpa - BASE_ADDRESS={:#X}-{:#X}\"", - gva, gpa, SandboxMemoryLayout::BASE_ADDRESS); + gva, gpa, BASE_ADDRESS); HyperlightError::TranslateGuestAddress(gva) })?; @@ -324,14 +326,16 @@ pub(crate) trait GuestDebug { let write_len = std::cmp::min( data.len(), - (PAGE_SIZE - (gpa & (PAGE_SIZE - 1))).try_into().unwrap(), + (PAGE_SIZE as u64 - (gpa & (PAGE_SIZE as u64 - 1))) + .try_into() + .unwrap(), ); let offset = (gpa as usize) - .checked_sub(SandboxMemoryLayout::BASE_ADDRESS) + .checked_sub(BASE_ADDRESS) .ok_or_else(|| { log::warn!( "gva=0x{:#X} causes subtract with underflow: \"gpa - BASE_ADDRESS={:#X}-{:#X}\"", - gva, gpa, SandboxMemoryLayout::BASE_ADDRESS); + gva, gpa, BASE_ADDRESS); HyperlightError::TranslateGuestAddress(gva) })?; diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index 8419cb073..4719a7522 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -38,8 +38,8 @@ use mshv_bindings::{ use mshv_bindings::{ hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA, hv_message_type_HVMSG_X64_HALT, hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT, hv_register_assoc, - hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_user_mem_region, - FloatingPointUnit, SegmentRegister, SpecialRegisters, StandardRegisters, + hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, FloatingPointUnit, SegmentRegister, + SpecialRegisters, StandardRegisters, }; #[cfg(mshv3)] use mshv_bindings::{ @@ -61,8 +61,8 @@ use super::{ }; use crate::hypervisor::hypervisor_handler::HypervisorHandler; use crate::hypervisor::HyperlightExit; -use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::ptr::{GuestPtr, RawPtr}; +use crate::sandbox::sandbox_builder::{MemoryRegionFlags, SandboxMemorySections}; #[cfg(gdb)] use crate::HyperlightError; use crate::{log_then_return, new_error, Result}; @@ -287,7 +287,7 @@ pub(super) struct HypervLinuxDriver { vm_fd: VmFd, vcpu_fd: VcpuFd, entrypoint: u64, - mem_regions: Vec, + mem_sections: SandboxMemorySections, orig_rsp: GuestPtr, #[cfg(gdb)] @@ -307,7 +307,7 @@ impl HypervLinuxDriver { /// `initialise` to do it for you. #[instrument(skip_all, parent = Span::current(), level = "Trace")] pub(super) fn new( - mem_regions: Vec, + mem_sections: SandboxMemorySections, entrypoint_ptr: GuestPtr, rsp_ptr: GuestPtr, pml4_ptr: GuestPtr, @@ -372,7 +372,7 @@ impl HypervLinuxDriver { (None, None) }; - mem_regions.iter().try_for_each(|region| { + mem_sections.iter().try_for_each(|(_, region)| { let mshv_region = region.to_owned().into(); vm_fd.map_user_memory(mshv_region) })?; @@ -383,7 +383,7 @@ impl HypervLinuxDriver { _mshv: mshv, vm_fd, vcpu_fd, - mem_regions, + mem_sections, entrypoint: entrypoint_ptr.absolute()?, orig_rsp: rsp_ptr, @@ -428,7 +428,7 @@ impl Debug for HypervLinuxDriver { f.field("Entrypoint", &self.entrypoint) .field("Original RSP", &self.orig_rsp); - for region in &self.mem_regions { + for region in self.mem_sections.iter() { f.field("Memory Region", ®ion); } @@ -452,9 +452,8 @@ impl Hypervisor for HypervLinuxDriver { #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] fn initialise( &mut self, - peb_addr: RawPtr, + hyperlight_peb_guest_memory_region_address: u64, seed: u64, - page_size: u32, outb_hdl: OutBHandlerWrapper, mem_access_hdl: MemAccessHandlerWrapper, hv_handler: Option, @@ -469,13 +468,12 @@ impl Hypervisor for HypervLinuxDriver { let regs = StandardRegisters { rip: self.entrypoint, rsp: self.orig_rsp.absolute()?, - rflags: 2, //bit 1 of rlags is required to be set + rflags: 2, //bit 1 of rflags is required to be set // function args - rcx: peb_addr.into(), + rcx: hyperlight_peb_guest_memory_region_address, rdx: seed, - r8: page_size.into(), - r9: max_guest_log_level, + r8: max_guest_log_level, ..Default::default() }; @@ -490,6 +488,17 @@ impl Hypervisor for HypervLinuxDriver { dbg_mem_access_fn, )?; + // The guest may have chosen a different stack region. If so, we drop usage of our tmp stack. + let hyperlight_peb = self.mem_sections.read_hyperlight_peb()?; + + if let Some(guest_stack_data) = &hyperlight_peb.get_guest_stack_data_region() { + if guest_stack_data.offset.is_some() { + // If we got here, it means the guest has set up a new stack + let rsp = hyperlight_peb.get_top_of_guest_stack_data(); + self.orig_rsp = GuestPtr::try_from(RawPtr::from(rsp))?; + } + } + Ok(()) } @@ -506,7 +515,7 @@ impl Hypervisor for HypervLinuxDriver { let regs = StandardRegisters { rip: dispatch_func_addr.into(), rsp: self.orig_rsp.absolute()?, - rflags: 2, //bit 1 of rlags is required to be set + rflags: 2, //bit 1 of rflags is required to be set ..Default::default() }; self.vcpu_fd.set_regs(®s)?; @@ -616,9 +625,10 @@ impl Hypervisor for HypervLinuxDriver { gpa, &self ); + match self.get_memory_access_violation( gpa as usize, - &self.mem_regions, + &self.mem_sections, access_info, ) { Some(access_info_violation) => access_info_violation, @@ -658,8 +668,8 @@ impl Hypervisor for HypervLinuxDriver { } #[cfg(crashdump)] - fn get_memory_regions(&self) -> &[MemoryRegion] { - &self.mem_regions + fn get_memory_sections(&self) -> &SandboxMemorySections { + &self.mem_sections } #[cfg(gdb)] @@ -711,8 +721,8 @@ impl Hypervisor for HypervLinuxDriver { impl Drop for HypervLinuxDriver { #[instrument(skip_all, parent = Span::current(), level = "Trace")] fn drop(&mut self) { - for region in &self.mem_regions { - let mshv_region: mshv_user_mem_region = region.to_owned().into(); + for region in self.mem_sections.iter() { + let mshv_region: mshv_bindings::mshv_user_mem_region = region.1.to_owned().into(); match self.vm_fd.unmap_user_memory(mshv_region) { Ok(_) => (), Err(e) => error!("Failed to unmap user memory in HyperVOnLinux ({:?})", e), @@ -720,66 +730,3 @@ impl Drop for HypervLinuxDriver { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::memory_region::MemoryRegionVecBuilder; - use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory}; - - #[rustfmt::skip] - const CODE: [u8; 12] = [ - 0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ - 0x00, 0xd8, /* add %bl, %al */ - 0x04, b'0', /* add $'0', %al */ - 0xee, /* out %al, (%dx) */ - /* send a 0 to indicate we're done */ - 0xb0, b'\0', /* mov $'\0', %al */ - 0xee, /* out %al, (%dx) */ - 0xf4, /* HLT */ - ]; - - fn shared_mem_with_code( - code: &[u8], - mem_size: usize, - load_offset: usize, - ) -> Result> { - if load_offset > mem_size { - log_then_return!( - "code load offset ({}) > memory size ({})", - load_offset, - mem_size - ); - } - let mut shared_mem = ExclusiveSharedMemory::new(mem_size)?; - shared_mem.copy_from_slice(code, load_offset)?; - Ok(Box::new(shared_mem)) - } - - #[test] - fn create_driver() { - if !super::is_hypervisor_present() { - return; - } - const MEM_SIZE: usize = 0x3000; - let gm = shared_mem_with_code(CODE.as_slice(), MEM_SIZE, 0).unwrap(); - let rsp_ptr = GuestPtr::try_from(0).unwrap(); - let pml4_ptr = GuestPtr::try_from(0).unwrap(); - let entrypoint_ptr = GuestPtr::try_from(0).unwrap(); - let mut regions = MemoryRegionVecBuilder::new(0, gm.base_addr()); - regions.push_page_aligned( - MEM_SIZE, - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE, - crate::mem::memory_region::MemoryRegionType::Code, - ); - super::HypervLinuxDriver::new( - regions.build(), - entrypoint_ptr, - rsp_ptr, - pml4_ptr, - #[cfg(gdb)] - None, - ) - .unwrap(); - } -} diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index 47fbaaa9d..4d1400c00 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -19,7 +19,7 @@ use std::fmt; use std::fmt::{Debug, Formatter}; use std::string::String; -use hyperlight_common::mem::PAGE_SIZE_USIZE; +use hyperlight_common::PAGE_SIZE; use log::LevelFilter; use tracing::{instrument, Span}; use windows::Win32::System::Hypervisor::{ @@ -43,8 +43,8 @@ use super::{ use crate::hypervisor::fpu::FP_CONTROL_WORD_DEFAULT; use crate::hypervisor::hypervisor_handler::HypervisorHandler; use crate::hypervisor::wrappers::WHvGeneralRegisters; -use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::ptr::{GuestPtr, RawPtr}; +use crate::sandbox::sandbox_builder::{MemoryRegionFlags, SandboxMemorySections}; use crate::{debug, new_error, Result}; /// A Hypervisor driver for HyperV-on-Windows. @@ -55,7 +55,7 @@ pub(crate) struct HypervWindowsDriver { source_address: *mut c_void, // this points into the first guard page entrypoint: u64, orig_rsp: GuestPtr, - mem_regions: Vec, + mem_sections: SandboxMemorySections, } /* This does not automatically impl Send/Sync because the host * address of the shared memory region is a raw pointer, which are @@ -68,7 +68,7 @@ unsafe impl Sync for HypervWindowsDriver {} impl HypervWindowsDriver { #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn new( - mem_regions: Vec, + mem_sections: SandboxMemorySections, raw_size: usize, raw_source_address: *mut c_void, pml4_address: u64, @@ -86,14 +86,14 @@ impl HypervWindowsDriver { mgr.get_surrogate_process(raw_size, raw_source_address, mmap_file_handle) }?; - partition.map_gpa_range(&mem_regions, surrogate_process.process_handle)?; + partition.map_gpa_range(&mem_sections, surrogate_process.process_handle)?; let mut proc = VMProcessor::new(partition)?; Self::setup_initial_sregs(&mut proc, pml4_address)?; // subtract 2 pages for the guard pages, since when we copy memory to and from surrogate process, // we don't want to copy the guard pages themselves (that would cause access violation) - let mem_size = raw_size - 2 * PAGE_SIZE_USIZE; + let mem_size = raw_size - 2 * PAGE_SIZE; Ok(Self { size: mem_size, processor: proc, @@ -101,7 +101,7 @@ impl HypervWindowsDriver { source_address: raw_source_address, entrypoint, orig_rsp: GuestPtr::try_from(RawPtr::from(rsp))?, - mem_regions, + mem_sections, }) } @@ -167,7 +167,7 @@ impl Debug for HypervWindowsDriver { .field("Entrypoint", &self.entrypoint) .field("Original RSP", &self.orig_rsp); - for region in &self.mem_regions { + for (_, region) in self.mem_sections.iter() { fs.field("Memory Region", ®ion); } @@ -302,9 +302,8 @@ impl Hypervisor for HypervWindowsDriver { #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] fn initialise( &mut self, - peb_address: RawPtr, + hyperlight_peb_guest_memory_region_address: u64, seed: u64, - page_size: u32, outb_hdl: OutBHandlerWrapper, mem_access_hdl: MemAccessHandlerWrapper, hv_handler: Option, @@ -321,9 +320,8 @@ impl Hypervisor for HypervWindowsDriver { rsp: self.orig_rsp.absolute()?, // function args - rcx: peb_address.into(), + rcx: hyperlight_peb_guest_memory_region_address, rdx: seed, - r8: page_size.into(), r9: max_guest_log_level, rflags: 1 << 1, // eflags bit index 1 is reserved and always needs to be 1 @@ -340,6 +338,17 @@ impl Hypervisor for HypervWindowsDriver { dbg_mem_access_hdl, )?; + // The guest may have chosen a different stack region. If so, we drop usage of our tmp stack. + let hyperlight_peb = self.mem_sections.read_hyperlight_peb()?; + + if let Some(guest_stack_data) = &hyperlight_peb.get_guest_stack_data_region() { + if guest_stack_data.offset.is_some() { + // If we got here, it means the guest has set up a new stack + let rsp = hyperlight_peb.get_top_of_guest_stack_data(); + self.orig_rsp = GuestPtr::try_from(RawPtr::from(rsp))?; + } + } + Ok(()) } @@ -449,8 +458,11 @@ impl Hypervisor for HypervWindowsDriver { gpa, access_info, &self ); - match self.get_memory_access_violation(gpa as usize, &self.mem_regions, access_info) - { + match self.get_memory_access_violation( + gpa as usize, + &self.mem_sections, + access_info, + ) { Some(access_info) => access_info, None => HyperlightExit::Mmio(gpa), } @@ -487,8 +499,8 @@ impl Hypervisor for HypervWindowsDriver { } #[cfg(crashdump)] - fn get_memory_regions(&self) -> &[MemoryRegion] { - &self.mem_regions + fn get_memory_sections(&self) -> &SandboxMemorySections { + &self.mem_sections } } diff --git a/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs b/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs index 591f43a07..5c0abb907 100644 --- a/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs +++ b/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs @@ -43,11 +43,12 @@ use crate::hypervisor::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper}; #[cfg(target_os = "windows")] use crate::hypervisor::wrappers::HandleWrapper; use crate::hypervisor::Hypervisor; -use crate::mem::layout::SandboxMemoryLayout; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::{GuestPtr, RawPtr}; use crate::mem::ptr_offset::Offset; -use crate::mem::shared_mem::{GuestSharedMemory, HostSharedMemory, SharedMemory}; +#[cfg(target_os = "windows")] +use crate::mem::shared_mem::SharedMemory; +use crate::mem::shared_mem::{GuestSharedMemory, HostSharedMemory}; #[cfg(gdb)] use crate::sandbox::config::DebugInfo; use crate::sandbox::hypervisor::{get_available_hypervisor, HypervisorType}; @@ -178,10 +179,8 @@ struct HvHandlerCommChannels { #[derive(Clone)] pub(crate) struct HvHandlerConfig { - pub(crate) peb_addr: RawPtr, + pub(crate) hyperlight_peb_guest_memory_region_address: u64, pub(crate) seed: u64, - pub(crate) page_size: u32, - pub(crate) dispatch_function_addr: Arc>>, pub(crate) max_init_time: Duration, pub(crate) max_exec_time: Duration, pub(crate) outb_handler: OutBHandlerWrapper, @@ -351,9 +350,8 @@ impl HypervisorHandler { .try_read(); let res = hv.initialise( - configuration.peb_addr.clone(), + configuration.hyperlight_peb_guest_memory_region_address, configuration.seed, - configuration.page_size, configuration.outb_handler.clone(), configuration.mem_access_handler.clone(), Some(hv_handler_clone.clone()), @@ -394,20 +392,22 @@ impl HypervisorHandler { info!("Dispatching call from host: {}", function_name); - let dispatch_function_addr = configuration - .dispatch_function_addr - .clone() + let dispatch_function_addr = RawPtr(execution_variables + .shm .try_lock() - .map_err(|e| { - new_error!( - "Error locking at {}:{}: {}", - file!(), - line!(), - e - ) - })? - .clone() - .ok_or_else(|| new_error!("Hypervisor not initialized"))?; + .unwrap() + .as_mut() + .ok_or_else(|| { + new_error!("guest shm lock: {}:{}", file!(), line!()) + })?.memory_sections.read_hyperlight_peb()?.get_guest_function_dispatch_ptr()); + + if dispatch_function_addr == RawPtr(0) { + log_then_return!( + "Dispatch function address is null: {}:{}", + file!(), + line!() + ); + } let mut evar_lock_guard = execution_variables.shm.try_lock().map_err(|e| { @@ -715,20 +715,6 @@ impl HypervisorHandler { res } - pub(crate) fn set_dispatch_function_addr( - &mut self, - dispatch_function_addr: RawPtr, - ) -> Result<()> { - *self - .configuration - .dispatch_function_addr - .try_lock() - .map_err(|_| new_error!("Failed to set_dispatch_function_addr"))? = - Some(dispatch_function_addr); - - Ok(()) - } - #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn terminate_execution(&self) -> Result<()> { error!( @@ -836,50 +822,34 @@ fn set_up_hypervisor_partition( outb_handler: OutBHandlerWrapper, #[cfg(gdb)] debug_info: &Option, ) -> Result> { - let mem_size = u64::try_from(mgr.shared_mem.mem_size())?; - let mut regions = mgr.layout.get_memory_regions(&mgr.shared_mem)?; - let rsp_ptr = { - let rsp_u64 = mgr.set_up_shared_memory(mem_size, &mut regions)?; - let rsp_raw = RawPtr::from(rsp_u64); - GuestPtr::try_from(rsp_raw) - }?; - let base_ptr = GuestPtr::try_from(Offset::from(0))?; - let pml4_ptr = { - let pml4_offset_u64 = u64::try_from(SandboxMemoryLayout::PML4_OFFSET)?; - base_ptr + Offset::from(pml4_offset_u64) - }; + mgr.set_up_shared_memory()?; + let memory_sections = mgr.memory_sections.clone(); + + let rsp_ptr = GuestPtr::try_from(RawPtr::from(mgr.init_rsp))?; + + let pml4_ptr = GuestPtr::try_from(Offset::from( + mgr.memory_sections + .get_paging_structures_offset() + .ok_or("PML4 offset not found")? as u64, + ))?; let entrypoint_ptr = { let entrypoint_total_offset = mgr.load_addr.clone() + mgr.entrypoint_offset; GuestPtr::try_from(entrypoint_total_offset) }?; - if base_ptr != pml4_ptr { - log_then_return!( - "Error: base_ptr ({:#?}) does not equal pml4_ptr ({:#?})", - base_ptr, - pml4_ptr - ); - } - if entrypoint_ptr <= pml4_ptr { - log_then_return!( - "Error: entrypoint_ptr ({:#?}) is not greater than pml4_ptr ({:#?})", - entrypoint_ptr, - pml4_ptr - ); - } if mgr.is_in_process() { cfg_if::cfg_if! { if #[cfg(inprocess)] { // in-process feature + debug build use super::inprocess::InprocessArgs; - use crate::sandbox::leaked_outb::LeakedOutBWrapper; use super::inprocess::InprocessDriver; + use crate::sandbox::leaked_outb::LeakedOutBWrapper; let leaked_outb_wrapper = LeakedOutBWrapper::new(mgr, outb_handler)?; let hv = InprocessDriver::new(InprocessArgs { entrypoint_raw: u64::from(mgr.load_addr.clone() + mgr.entrypoint_offset), - peb_ptr_raw: mgr - .get_in_process_peb_address(mgr.shared_mem.base_addr() as u64)?, + peb_ptr_raw: mgr.memory_sections.get_hyperlight_peb_section_host_address().ok_or( + "Hyperlight PEB section not found")? as u64, leaked_outb_wrapper, })?; Ok(Box::new(hv)) @@ -918,7 +888,7 @@ fn set_up_hypervisor_partition( #[cfg(mshv)] Some(HypervisorType::Mshv) => { let hv = crate::hypervisor::hyperv_linux::HypervLinuxDriver::new( - regions, + memory_sections, entrypoint_ptr, rsp_ptr, pml4_ptr, @@ -931,7 +901,7 @@ fn set_up_hypervisor_partition( #[cfg(kvm)] Some(HypervisorType::Kvm) => { let hv = crate::hypervisor::kvm::KVMDriver::new( - regions, + memory_sections, pml4_ptr.absolute()?, entrypoint_ptr.absolute()?, rsp_ptr.absolute()?, @@ -947,7 +917,7 @@ fn set_up_hypervisor_partition( .shared_mem .with_exclusivity(|e| e.get_mmap_file_handle())?; let hv = crate::hypervisor::hyperv_windows::HypervWindowsDriver::new( - regions, + memory_sections, mgr.shared_mem.raw_mem_size(), // we use raw_* here because windows driver requires 64K aligned addresses, mgr.shared_mem.raw_ptr() as *mut c_void, // and instead convert it to base_addr where needed in the driver itself pml4_ptr.absolute()?, @@ -965,166 +935,167 @@ fn set_up_hypervisor_partition( } } -#[cfg(test)] -mod tests { - use std::sync::{Arc, Barrier}; - use std::thread; - - use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; - use hyperlight_testing::simple_guest_as_string; - - #[cfg(target_os = "windows")] - use crate::sandbox::SandboxConfiguration; - use crate::sandbox::WrapperGetter; - use crate::sandbox_state::sandbox::EvolvableSandbox; - use crate::sandbox_state::transition::Noop; - use crate::HyperlightError::HypervisorHandlerExecutionCancelAttemptOnFinishedExecution; - use crate::{ - is_hypervisor_present, GuestBinary, HyperlightError, MultiUseSandbox, Result, - UninitializedSandbox, - }; - - fn create_multi_use_sandbox() -> MultiUseSandbox { - if !is_hypervisor_present() { - panic!("Panic on create_multi_use_sandbox because no hypervisor is present"); - } - - // Tests that use this function seem to fail with timeouts sporadically on windows so timeouts are raised here - - let cfg = { - #[cfg(target_os = "windows")] - { - let mut cfg = SandboxConfiguration::default(); - cfg.set_max_initialization_time(std::time::Duration::from_secs(10)); - cfg.set_max_execution_time(std::time::Duration::from_secs(3)); - Some(cfg) - } - #[cfg(not(target_os = "windows"))] - { - None - } - }; - - let usbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), - cfg, - None, - None, - ) - .unwrap(); - - usbox.evolve(Noop::default()).unwrap() - } - - #[test] - #[ignore] // this test runs by itself because it uses a lot of system resources - fn create_1000_sandboxes() { - let barrier = Arc::new(Barrier::new(21)); - - let mut handles = vec![]; - - for _ in 0..20 { - let c = barrier.clone(); - - let handle = thread::spawn(move || { - c.wait(); - - for _ in 0..50 { - create_multi_use_sandbox(); - } - }); - - handles.push(handle); - } - - barrier.wait(); - - for handle in handles { - handle.join().unwrap(); - } - } - - #[test] - fn create_10_sandboxes() { - for _ in 0..10 { - create_multi_use_sandbox(); - } - } - - #[test] - fn hello_world() -> Result<()> { - let mut sandbox = create_multi_use_sandbox(); - - let msg = "Hello, World!\n".to_string(); - let res = sandbox.call_guest_function_by_name( - "PrintOutput", - ReturnType::Int, - Some(vec![ParameterValue::String(msg.clone())]), - ); - - assert!(res.is_ok()); - - Ok(()) - } - - #[test] - fn terminate_execution_then_call_another_function() -> Result<()> { - let mut sandbox = create_multi_use_sandbox(); - - let res = sandbox.call_guest_function_by_name("Spin", ReturnType::Void, None); - - assert!(res.is_err()); - - match res.err().unwrap() { - HyperlightError::ExecutionCanceledByHost() => {} - _ => panic!("Expected ExecutionTerminated error"), - } - - let res = sandbox.call_guest_function_by_name( - "Echo", - ReturnType::String, - Some(vec![ParameterValue::String("a".to_string())]), - ); - - assert!(res.is_ok()); - - Ok(()) - } - - #[test] - fn terminate_execution_of_an_already_finished_function_then_call_another_function() -> Result<()> - { - let call_print_output = |sandbox: &mut MultiUseSandbox| { - let msg = "Hello, World!\n".to_string(); - let res = sandbox.call_guest_function_by_name( - "PrintOutput", - ReturnType::Int, - Some(vec![ParameterValue::String(msg.clone())]), - ); - - assert!(res.is_ok()); - }; - - let mut sandbox = create_multi_use_sandbox(); - call_print_output(&mut sandbox); - - // this simulates what would happen if a function actually successfully - // finished while we attempted to terminate execution - { - match sandbox - .get_hv_handler() - .clone() - .terminate_hypervisor_handler_execution_and_reinitialise( - sandbox.get_mgr_wrapper_mut().unwrap_mgr_mut(), - )? { - HypervisorHandlerExecutionCancelAttemptOnFinishedExecution() => {} - _ => panic!("Expected error demonstrating execution wasn't cancelled properly"), - } - } - - call_print_output(&mut sandbox); - call_print_output(&mut sandbox); - - Ok(()) - } -} +// TODO(danbugs:297): bring back +// #[cfg(test)] +// mod tests { +// use std::sync::{Arc, Barrier}; +// use std::thread; +// +// use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; +// use hyperlight_testing::simple_guest_as_string; +// +// #[cfg(target_os = "windows")] +// use crate::sandbox::SandboxConfiguration; +// use crate::sandbox::WrapperGetter; +// use crate::sandbox_state::sandbox::EvolvableSandbox; +// use crate::sandbox_state::transition::Noop; +// use crate::HyperlightError::HypervisorHandlerExecutionCancelAttemptOnFinishedExecution; +// use crate::{ +// is_hypervisor_present, GuestBinary, HyperlightError, MultiUseSandbox, Result, +// UninitializedSandbox, +// }; +// +// fn create_multi_use_sandbox() -> MultiUseSandbox { +// if !is_hypervisor_present() { +// panic!("Panic on create_multi_use_sandbox because no hypervisor is present"); +// } +// +// // Tests that use this function seem to fail with timeouts sporadically on windows so timeouts are raised here +// +// let cfg = { +// #[cfg(target_os = "windows")] +// { +// let mut cfg = SandboxConfiguration::default(); +// cfg.set_max_initialization_time(std::time::Duration::from_secs(10)); +// cfg.set_max_execution_time(std::time::Duration::from_secs(3)); +// Some(cfg) +// } +// #[cfg(not(target_os = "windows"))] +// { +// None +// } +// }; +// +// let usbox = UninitializedSandbox::new( +// GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), +// cfg, +// None, +// None, +// ) +// .unwrap(); +// +// usbox.evolve(Noop::default()).unwrap() +// } +// +// #[test] +// #[ignore] // this test runs by itself because it uses a lot of system resources +// fn create_1000_sandboxes() { +// let barrier = Arc::new(Barrier::new(21)); +// +// let mut handles = vec![]; +// +// for _ in 0..20 { +// let c = barrier.clone(); +// +// let handle = thread::spawn(move || { +// c.wait(); +// +// for _ in 0..50 { +// create_multi_use_sandbox(); +// } +// }); +// +// handles.push(handle); +// } +// +// barrier.wait(); +// +// for handle in handles { +// handle.join().unwrap(); +// } +// } +// +// #[test] +// fn create_10_sandboxes() { +// for _ in 0..10 { +// create_multi_use_sandbox(); +// } +// } +// +// #[test] +// fn hello_world() -> Result<()> { +// let mut sandbox = create_multi_use_sandbox(); +// +// let msg = "Hello, World!\n".to_string(); +// let res = sandbox.call_guest_function_by_name( +// "PrintOutput", +// ReturnType::Int, +// Some(vec![ParameterValue::String(msg.clone())]), +// ); +// +// assert!(res.is_ok()); +// +// Ok(()) +// } +// +// #[test] +// fn terminate_execution_then_call_another_function() -> Result<()> { +// let mut sandbox = create_multi_use_sandbox(); +// +// let res = sandbox.call_guest_function_by_name("Spin", ReturnType::Void, None); +// +// assert!(res.is_err()); +// +// match res.err().unwrap() { +// HyperlightError::ExecutionCanceledByHost() => {} +// _ => panic!("Expected ExecutionTerminated error"), +// } +// +// let res = sandbox.call_guest_function_by_name( +// "Echo", +// ReturnType::String, +// Some(vec![ParameterValue::String("a".to_string())]), +// ); +// +// assert!(res.is_ok()); +// +// Ok(()) +// } +// +// #[test] +// fn terminate_execution_of_an_already_finished_function_then_call_another_function() -> Result<()> +// { +// let call_print_output = |sandbox: &mut MultiUseSandbox| { +// let msg = "Hello, World!\n".to_string(); +// let res = sandbox.call_guest_function_by_name( +// "PrintOutput", +// ReturnType::Int, +// Some(vec![ParameterValue::String(msg.clone())]), +// ); +// +// assert!(res.is_ok()); +// }; +// +// let mut sandbox = create_multi_use_sandbox(); +// call_print_output(&mut sandbox); +// +// // this simulates what would happen if a function actually successfully +// // finished while we attempted to terminate execution +// { +// match sandbox +// .get_hv_handler() +// .clone() +// .terminate_hypervisor_handler_execution_and_reinitialise( +// sandbox.get_mgr_wrapper_mut().unwrap_mgr_mut(), +// )? { +// HypervisorHandlerExecutionCancelAttemptOnFinishedExecution() => {} +// _ => panic!("Expected error demonstrating execution wasn't cancelled properly"), +// } +// } +// +// call_print_output(&mut sandbox); +// call_print_output(&mut sandbox); +// +// Ok(()) +// } +// } diff --git a/src/hyperlight_host/src/hypervisor/inprocess.rs b/src/hyperlight_host/src/hypervisor/inprocess.rs index 0a87c30d2..464fb07e7 100644 --- a/src/hyperlight_host/src/hypervisor/inprocess.rs +++ b/src/hyperlight_host/src/hypervisor/inprocess.rs @@ -22,9 +22,9 @@ use log::LevelFilter; #[cfg(gdb)] use super::handlers::DbgMemAccessHandlerWrapper; use super::{HyperlightExit, Hypervisor}; -#[cfg(crashdump)] -use crate::mem::memory_region::MemoryRegion; use crate::sandbox::leaked_outb::LeakedOutBWrapper; +#[cfg(crashdump)] +use crate::sandbox::sandbox_builder::SandboxMemorySections; use crate::Result; /// Arguments passed to inprocess driver @@ -71,24 +71,18 @@ impl Debug for InprocessDriver<'_> { impl<'a> Hypervisor for InprocessDriver<'a> { fn initialise( &mut self, - _peb_addr: crate::mem::ptr::RawPtr, + _peb_addr: u64, seed: u64, - page_size: u32, _outb_handle_fn: super::handlers::OutBHandlerWrapper, _mem_access_fn: super::handlers::MemAccessHandlerWrapper, _hv_handler: Option, _guest_max_log_level: Option, #[cfg(gdb)] _dbg_mem_access_fn: DbgMemAccessHandlerWrapper, ) -> crate::Result<()> { - let entrypoint_fn: extern "win64" fn(u64, u64, u64, u64) = + let entrypoint_fn: extern "win64" fn(u64, u64, u64) = unsafe { std::mem::transmute(self.args.entrypoint_raw as *const c_void) }; - entrypoint_fn( - self.args.peb_ptr_raw, - seed, - page_size as u64, - log::max_level() as u64, - ); + entrypoint_fn(self.args.peb_ptr_raw, seed, log::max_level() as u64); Ok(()) } @@ -134,7 +128,7 @@ impl<'a> Hypervisor for InprocessDriver<'a> { } #[cfg(crashdump)] - fn get_memory_regions(&self) -> &[MemoryRegion] { - unimplemented!("get_memory_regions is not supported since we are in in-process mode") + fn get_memory_sections(&self) -> &SandboxMemorySections { + unimplemented!("get_memory_sections is not supported since we are in in-process mode") } } diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index 967555ffc..451534dc5 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -use std::convert::TryFrom; use std::fmt::Debug; #[cfg(gdb)] use std::sync::{Arc, Mutex}; @@ -36,8 +35,8 @@ use super::{ CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX, EFER_SCE, }; use crate::hypervisor::hypervisor_handler::HypervisorHandler; -use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::ptr::{GuestPtr, RawPtr}; +use crate::sandbox::sandbox_builder::{MemoryRegionFlags, SandboxMemorySections}; #[cfg(gdb)] use crate::HyperlightError; use crate::{log_then_return, new_error, Result}; @@ -280,7 +279,7 @@ pub(super) struct KVMDriver { vcpu_fd: VcpuFd, entrypoint: u64, orig_rsp: GuestPtr, - mem_regions: Vec, + mem_sections: SandboxMemorySections, #[cfg(gdb)] debug: Option, @@ -294,7 +293,7 @@ impl KVMDriver { /// be called to do so. #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub(super) fn new( - mem_regions: Vec, + mem_sections: SandboxMemorySections, pml4_addr: u64, entrypoint: u64, rsp: u64, @@ -307,20 +306,23 @@ impl KVMDriver { let perm_flags = MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE; - mem_regions.iter().enumerate().try_for_each(|(i, region)| { - let perm_flags = perm_flags.intersection(region.flags); - let kvm_region = kvm_userspace_memory_region { - slot: i as u32, - guest_phys_addr: region.guest_region.start as u64, - memory_size: (region.guest_region.end - region.guest_region.start) as u64, - userspace_addr: region.host_region.start as u64, - flags: match perm_flags { - MemoryRegionFlags::READ => KVM_MEM_READONLY, - _ => 0, // normal, RWX - }, - }; - unsafe { vm_fd.set_user_memory_region(kvm_region) } - })?; + mem_sections + .iter() + .enumerate() + .try_for_each(|(i, region)| { + let perm_flags = perm_flags.intersection(region.1.flags); + let kvm_region = kvm_userspace_memory_region { + slot: i as u32, + guest_phys_addr: region.1.page_aligned_guest_offset as u64, + memory_size: region.1.page_aligned_size as u64, + userspace_addr: region.1.host_address.unwrap() as u64, + flags: match perm_flags { + MemoryRegionFlags::READ => KVM_MEM_READONLY, + _ => 0, // normal, RWX + }, + }; + unsafe { vm_fd.set_user_memory_region(kvm_region) } + })?; let mut vcpu_fd = vm_fd.create_vcpu(0)?; Self::setup_initial_sregs(&mut vcpu_fd, pml4_addr)?; @@ -344,7 +346,7 @@ impl KVMDriver { vcpu_fd, entrypoint, orig_rsp: rsp_gp, - mem_regions, + mem_sections, #[cfg(gdb)] debug, @@ -374,7 +376,7 @@ impl Debug for KVMDriver { let mut f = f.debug_struct("KVM Driver"); // Output each memory region - for region in &self.mem_regions { + for region in self.mem_sections.sections.iter() { f.field("Memory Region", ®ion); } let regs = self.vcpu_fd.get_regs(); @@ -401,9 +403,8 @@ impl Hypervisor for KVMDriver { #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] fn initialise( &mut self, - peb_addr: RawPtr, + hyperlight_peb_guest_memory_region_address: u64, seed: u64, - page_size: u32, outb_hdl: OutBHandlerWrapper, mem_access_hdl: MemAccessHandlerWrapper, hv_handler: Option, @@ -420,10 +421,9 @@ impl Hypervisor for KVMDriver { rsp: self.orig_rsp.absolute()?, // function args - rcx: peb_addr.into(), + rcx: hyperlight_peb_guest_memory_region_address, rdx: seed, - r8: page_size.into(), - r9: max_guest_log_level, + r8: max_guest_log_level, ..Default::default() }; @@ -438,6 +438,17 @@ impl Hypervisor for KVMDriver { dbg_mem_access_fn, )?; + // The guest may have chosen a different stack region. If so, we drop usage of our tmp stack. + let hyperlight_peb = self.mem_sections.read_hyperlight_peb()?; + + if let Some(guest_stack_data) = &hyperlight_peb.get_guest_stack_data_region() { + if guest_stack_data.offset.is_some() { + // If we got here, it means the guest has set up a new stack + let rsp = hyperlight_peb.get_top_of_guest_stack_data(); + self.orig_rsp = GuestPtr::try_from(RawPtr::from(rsp))?; + } + } + Ok(()) } @@ -526,7 +537,7 @@ impl Hypervisor for KVMDriver { match self.get_memory_access_violation( addr as usize, - &self.mem_regions, + &self.mem_sections, MemoryRegionFlags::READ, ) { Some(access_violation_exit) => access_violation_exit, @@ -538,7 +549,7 @@ impl Hypervisor for KVMDriver { match self.get_memory_access_violation( addr as usize, - &self.mem_regions, + &self.mem_sections, MemoryRegionFlags::WRITE, ) { Some(access_violation_exit) => access_violation_exit, @@ -582,8 +593,8 @@ impl Hypervisor for KVMDriver { } #[cfg(crashdump)] - fn get_memory_regions(&self) -> &[MemoryRegion] { - &self.mem_regions + fn get_memory_sections(&self) -> &SandboxMemorySections { + &self.mem_sections } #[cfg(gdb)] @@ -593,7 +604,9 @@ impl Hypervisor for KVMDriver { stop_reason: VcpuStopReason, ) -> Result<()> { self.send_dbg_msg(DebugResponse::VcpuStopped(stop_reason)) - .map_err(|e| new_error!("Couldn't signal vCPU stopped event to GDB thread: {:?}", e))?; + .map_err(|e| { + crate::new_error!("Couldn't signal vCPU stopped event to GDB thread: {:?}", e) + })?; loop { log::debug!("Debug wait for event to resume vCPU"); diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index bf85d3956..bc98876ca 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -18,8 +18,8 @@ use log::LevelFilter; use tracing::{instrument, Span}; use crate::error::HyperlightError::ExecutionCanceledByHost; -use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::metrics::METRIC_GUEST_CANCELLATION; +use crate::sandbox::sandbox_builder::{MemoryRegionFlags, SandboxMemorySections}; use crate::{log_then_return, new_error, HyperlightError, Result}; /// Util for handling x87 fpu state @@ -124,10 +124,9 @@ pub(crate) trait Hypervisor: Debug + Sync + Send { #[allow(clippy::too_many_arguments)] fn initialise( &mut self, - peb_addr: RawPtr, + hyperlight_peb_guest_memory_region_address: u64, seed: u64, - page_size: u32, - outb_handle_fn: OutBHandlerWrapper, + outb_hdl: OutBHandlerWrapper, mem_access_fn: MemAccessHandlerWrapper, hv_handler: Option, guest_max_log_level: Option, @@ -168,15 +167,17 @@ pub(crate) trait Hypervisor: Debug + Sync + Send { fn get_memory_access_violation( &self, gpa: usize, - mem_regions: &[MemoryRegion], + mem_sections: &SandboxMemorySections, access_info: MemoryRegionFlags, ) -> Option { // find the region containing the given gpa - let region = mem_regions - .iter() - .find(|region| region.guest_region.contains(&gpa)); + let region = mem_sections.iter().find(|(_, region)| { + let offset = region.page_aligned_guest_offset; + let size = region.page_aligned_size; + gpa >= offset && gpa < (offset + size) + }); - if let Some(region) = region { + if let Some((_, region)) = region { if !region.flags.contains(access_info) || region.flags.contains(MemoryRegionFlags::STACK_GUARD) { @@ -235,7 +236,7 @@ pub(crate) trait Hypervisor: Debug + Sync + Send { fn get_partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE; #[cfg(crashdump)] - fn get_memory_regions(&self) -> &[MemoryRegion]; + fn get_memory_sections(&self) -> &SandboxMemorySections; #[cfg(gdb)] /// handles the cases when the vCPU stops due to a Debug event @@ -337,7 +338,6 @@ impl VirtualCPU { #[cfg(all(test, any(target_os = "windows", kvm)))] pub(crate) mod tests { use std::path::Path; - use std::sync::{Arc, Mutex}; use std::time::Duration; use hyperlight_testing::dummy_guest_as_string; @@ -348,10 +348,9 @@ pub(crate) mod tests { use crate::hypervisor::hypervisor_handler::{ HvHandlerConfig, HypervisorHandler, HypervisorHandlerAction, }; - use crate::mem::ptr::RawPtr; - use crate::sandbox::uninitialized::GuestBinary; - use crate::sandbox::{SandboxConfiguration, UninitializedSandbox}; - use crate::{new_error, Result}; + use crate::sandbox::config::SandboxConfiguration; + use crate::sandbox::sandbox_builder::SandboxBuilder; + use crate::{new_error, GuestBinary, Result}; pub(crate) fn test_initialise( outb_hdl: OutBHandlerWrapper, @@ -366,20 +365,21 @@ pub(crate) mod tests { )); } - let sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(filename.clone()), None, None, None)?; - let (hshm, gshm) = sandbox.mgr.build(); + let sandbox_builder = SandboxBuilder::new(GuestBinary::FilePath(filename))?; + + let sandbox = sandbox_builder.build()?; + + let (hshm, gshm) = sandbox.mem_mgr.build(); drop(hshm); let hv_handler_config = HvHandlerConfig { + hyperlight_peb_guest_memory_region_address: 0x230000, + seed: 1234567890, + max_guest_log_level: None, outb_handler: outb_hdl, mem_access_handler: mem_access_hdl, #[cfg(gdb)] dbg_mem_access_handler: dbg_mem_access_fn, - seed: 1234567890, - page_size: 4096, - peb_addr: RawPtr::from(0x230000), - dispatch_function_addr: Arc::new(Mutex::new(None)), max_init_time: Duration::from_millis( SandboxConfiguration::DEFAULT_MAX_INITIALIZATION_TIME as u64, ), @@ -389,7 +389,6 @@ pub(crate) mod tests { max_wait_for_cancellation: Duration::from_millis( SandboxConfiguration::DEFAULT_MAX_WAIT_FOR_CANCELLATION as u64, ), - max_guest_log_level: None, }; let mut hv_handler = HypervisorHandler::new(hv_handler_config); diff --git a/src/hyperlight_host/src/hypervisor/surrogate_process_manager.rs b/src/hyperlight_host/src/hypervisor/surrogate_process_manager.rs index 4f3291034..65ab82895 100644 --- a/src/hyperlight_host/src/hypervisor/surrogate_process_manager.rs +++ b/src/hyperlight_host/src/hypervisor/surrogate_process_manager.rs @@ -21,7 +21,7 @@ use std::mem::size_of; use std::path::{Path, PathBuf}; use crossbeam_channel::{unbounded, Receiver, Sender}; -use hyperlight_common::mem::PAGE_SIZE_USIZE; +use hyperlight_common::PAGE_SIZE; use rust_embed::RustEmbed; use tracing::{error, info, instrument, Span}; use windows::core::{s, PCSTR}; @@ -195,7 +195,7 @@ impl SurrogateProcessManager { VirtualProtectEx( surrogate_process_handle, first_guard_page_start, - PAGE_SIZE_USIZE, + PAGE_SIZE, PAGE_NOACCESS, &mut unused_out_old_prot_flags, ) @@ -204,12 +204,12 @@ impl SurrogateProcessManager { } // the last page of the raw_size is the guard page - let last_guard_page_start = unsafe { raw_source_address.add(raw_size - PAGE_SIZE_USIZE) }; + let last_guard_page_start = unsafe { raw_source_address.add(raw_size - PAGE_SIZE) }; if let Err(e) = unsafe { VirtualProtectEx( surrogate_process_handle, last_guard_page_start, - PAGE_SIZE_USIZE, + PAGE_SIZE, PAGE_NOACCESS, &mut unused_out_old_prot_flags, ) @@ -429,7 +429,7 @@ mod tests { use std::thread; use std::time::{Duration, Instant}; - use hyperlight_common::mem::PAGE_SIZE_USIZE; + use hyperlight_common::PAGE_SIZE; use rand::{rng, Rng}; use serial_test::serial; use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}; @@ -456,7 +456,7 @@ mod tests { let thread_handle = thread::spawn(move || -> Result<()> { let surrogate_process_manager_res = get_surrogate_process_manager(); let mut rng = rng(); - let size = PAGE_SIZE_USIZE * 3; + let size = PAGE_SIZE * 3; assert!(surrogate_process_manager_res.is_ok()); let surrogate_process_manager = surrogate_process_manager_res.unwrap(); let job_handle = surrogate_process_manager.job_handle; diff --git a/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs b/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs index 47e200d38..c7382dc96 100644 --- a/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs +++ b/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs @@ -25,7 +25,7 @@ use windows_result::HRESULT; use super::wrappers::HandleWrapper; use crate::hypervisor::wrappers::{WHvFPURegisters, WHvGeneralRegisters, WHvSpecialRegisters}; -use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; +use crate::sandbox::sandbox_builder::{MemoryRegionFlags, SandboxMemorySections}; use crate::{new_error, Result}; // We need to pass in a primitive array of register names/values @@ -92,7 +92,7 @@ impl VMPartition { #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(super) fn map_gpa_range( &mut self, - regions: &[MemoryRegion], + regions: &SandboxMemorySections, process_handle: HandleWrapper, ) -> Result<()> { let process_handle: HANDLE = process_handle.into(); @@ -106,7 +106,7 @@ impl VMPartition { } }; - regions.iter().try_for_each(|region| unsafe { + regions.iter().try_for_each(|(_, region)| unsafe { let flags = region .flags .iter() @@ -125,9 +125,9 @@ impl VMPartition { let res = whvmapgparange2_func( self.0, process_handle, - region.host_region.start as *const c_void, - region.guest_region.start as u64, - (region.guest_region.end - region.guest_region.start) as u64, + region.host_address.ok_or("No host address mapping")? as *const c_void, + region.page_aligned_guest_offset as u64, + region.page_aligned_size as u64, flags, ); if res.is_err() { diff --git a/src/hyperlight_host/src/lib.rs b/src/hyperlight_host/src/lib.rs index 4716eb00c..204c146b9 100644 --- a/src/hyperlight_host/src/lib.rs +++ b/src/hyperlight_host/src/lib.rs @@ -24,6 +24,8 @@ limitations under the License. use std::sync::Once; +/// This crate contains an SDK that is used to execute specially +/// compiled binaries within a very lightweight hypervisor environment. use log::info; /// The `built` crate is used to generate a `built.rs` file that contains /// information about the build environment. This information is used to @@ -39,32 +41,7 @@ pub mod func; pub mod hypervisor; /// Functionality to establish and manage an individual sandbox's /// memory. -/// -/// The following structs are not used other than to calculate the size of the memory needed -/// and also to illustrate the layout of the memory: -/// -/// - `HostFunctionDefinitions` -/// - `HostExceptionData` -/// - `GuestError` -/// - `CodeAndOutBPointers` -/// - `InputData` -/// - `OutputData` -/// - `GuestHeap` -/// - `GuestStack` -/// -/// the start of the guest memory contains the page tables and is always located at the Virtual Address 0x00200000 when -/// running in a Hypervisor: -/// -/// Virtual Address -/// -/// 0x200000 PML4 -/// 0x201000 PDPT -/// 0x202000 PD -/// 0x203000 The guest PE code (When the code has been loaded using LoadLibrary to debug the guest this will not be -/// present and code length will be zero; -/// -/// The pointer passed to the Entrypoint in the Guest application is the 0x200000 + size of page table + size of code, -/// at this address structs below are laid out in this order +#[deny(dead_code, missing_docs, unused_mut)] pub mod mem; /// Metric definitions and helpers pub mod metrics; @@ -75,6 +52,7 @@ pub mod sandbox; /// `trait`s and other functionality for dealing with defining sandbox /// states and moving between them pub mod sandbox_state; +/// Utilities for dealing with seccomp on Linux #[cfg(all(feature = "seccomp", target_os = "linux"))] pub(crate) mod seccomp; /// Signal handling for Linux @@ -90,9 +68,7 @@ pub use error::HyperlightError; /// The re-export for the `is_hypervisor_present` type pub use sandbox::is_hypervisor_present; /// The re-export for the `GuestBinary` type -pub use sandbox::uninitialized::GuestBinary; -/// Re-export for `HypervisorWrapper` trait -/// Re-export for `MemMgrWrapper` type +pub use sandbox::sandbox_builder::GuestBinary; /// A sandbox that can call be used to make multiple calls to guest functions, /// and otherwise reused multiple times pub use sandbox::MultiUseSandbox; diff --git a/src/hyperlight_host/src/mem/layout.rs b/src/hyperlight_host/src/mem/layout.rs deleted file mode 100644 index 3a13879c4..000000000 --- a/src/hyperlight_host/src/mem/layout.rs +++ /dev/null @@ -1,1332 +0,0 @@ -/* -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. -*/ -use std::fmt::Debug; -use std::mem::{offset_of, size_of}; - -use hyperlight_common::mem::{GuestStackData, HyperlightPEB, RunMode, PAGE_SIZE_USIZE}; -use paste::paste; -use rand::{rng, RngCore}; -use tracing::{instrument, Span}; - -use super::memory_region::MemoryRegionType::{ - BootStack, Code, GuardPage, GuestErrorData, Heap, HostExceptionData, HostFunctionDefinitions, - InputData, KernelStack, OutputData, PageTables, PanicContext, Peb, Stack, -}; -use super::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionVecBuilder}; -use super::mgr::AMOUNT_OF_MEMORY_PER_PT; -use super::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, SharedMemory}; -use crate::error::HyperlightError::{GuestOffsetIsInvalid, MemoryRequestTooBig}; -use crate::sandbox::SandboxConfiguration; -use crate::{log_then_return, new_error, Result}; - -// +-------------------------------------------+ -// | Boot Stack (4KiB) | -// +-------------------------------------------+ -// | Kernel Stack Guard Page (4KiB) | -// +-------------------------------------------+ -// | Kernel Stack | -// +-------------------------------------------+ -// | Guest Stack Guard Page (4KiB) | -// +-------------------------------------------+ -// | Guest (User) Stack | -// +-------------------------------------------+ -// | Guard Page (4KiB) | -// +-------------------------------------------+ -// | Guest Heap | -// +-------------------------------------------+ -// | Guest Panic Context | -// +-------------------------------------------+ -// | Output Data | -// +-------------------------------------------+ -// | Input Data | -// +-------------------------------------------+ -// | Guest Error Log | -// +-------------------------------------------+ -// | Host Exception Handlers | -// +-------------------------------------------+ -// | Host Function Definitions | -// +-------------------------------------------+ -// | PEB Struct (0x98) | -// +-------------------------------------------+ -// | Guest Code | -// +-------------------------------------------+ -// | PT | -// +-------------------------------------------+ 0x203_000 -// | PD | -// +-------------------------------------------+ 0x202_000 -// | PDPT | -// +-------------------------------------------+ 0x201_000 -// | PML4 | -// +-------------------------------------------+ 0x200_000 -// | ⋮ | -// | Unmapped | -// | ⋮ | -// +-------------------------------------------+ 0x0 - -/// -/// - `HostDefinitions` - the length of this is the `HostFunctionDefinitionSize` -/// field from `SandboxConfiguration` -/// -/// - `HostExceptionData` - memory that contains details of any Host Exception that -/// occurred in outb function. it contains a 32 bit length following by a json -/// serialisation of any error that occurred. the length of this field is -/// `HostExceptionSize` from` `SandboxConfiguration` -/// -/// - `GuestError` - contains a buffer for any guest error that occurred. -/// the length of this field is `GuestErrorBufferSize` from `SandboxConfiguration` -/// -/// - `InputData` - this is a buffer that is used for input data to the host program. -/// the length of this field is `InputDataSize` from `SandboxConfiguration` -/// -/// - `OutputData` - this is a buffer that is used for output data from host program. -/// the length of this field is `OutputDataSize` from `SandboxConfiguration` -/// -/// - `GuestHeap` - this is a buffer that is used for heap data in the guest. the length -/// of this field is returned by the `heap_size()` method of this struct -/// -/// - `GuestStack` - this is a buffer that is used for stack data in the guest. the length -/// of this field is returned by the `stack_size()` method of this struct. in reality, -/// the stack might be slightly bigger or smaller than this value since total memory -/// size is rounded up to the nearest 4K, and there is a 16-byte stack guard written -/// to the top of the stack. (see below for more details) -/// -/// - `GuestPanicContext` - contains a buffer for context associated with any guest -/// panic that occurred. -/// the length of this field is returned by the `guest_panic_context_size()` fn of this struct. -/// -/// Boot Stack - this is the stack that is used before the TSS is set up. It is fixed to 4K -/// Kernel Stack Guard Page is to Guard against boot stack overflow so we dont corrupt the kernel stack -/// Kernel Stack - this is the stack that is used for kernel mode operations we switch to this early in the initialization function -/// Guest Stack Guard Page is to Guard against kernel stack overflow so we dont corrupt the user stack - -#[derive(Copy, Clone)] -pub(crate) struct SandboxMemoryLayout { - pub(super) sandbox_memory_config: SandboxConfiguration, - /// The total stack size of this sandbox. - pub(super) stack_size: usize, - /// The heap size of this sandbox. - pub(super) heap_size: usize, - - /// The following fields are offsets to the actual PEB struct fields. - /// They are used when writing the PEB struct itself - peb_offset: usize, - peb_security_cookie_seed_offset: usize, - peb_guest_dispatch_function_ptr_offset: usize, // set by guest in guest entrypoint - pub(super) peb_host_function_definitions_offset: usize, - pub(crate) peb_host_exception_offset: usize, - peb_guest_error_offset: usize, - peb_code_and_outb_pointer_offset: usize, - peb_runmode_offset: usize, - peb_input_data_offset: usize, - peb_output_data_offset: usize, - peb_guest_panic_context_offset: usize, - peb_heap_data_offset: usize, - peb_guest_stack_data_offset: usize, - - // The following are the actual values - // that are written to the PEB struct - pub(crate) host_function_definitions_buffer_offset: usize, - pub(crate) host_exception_buffer_offset: usize, - pub(super) guest_error_buffer_offset: usize, - pub(super) input_data_buffer_offset: usize, - pub(super) output_data_buffer_offset: usize, - guest_panic_context_buffer_offset: usize, - guest_heap_buffer_offset: usize, - guard_page_offset: usize, - guest_user_stack_buffer_offset: usize, // the lowest address of the user stack - user_stack_guard_page_offset: usize, - kernel_stack_buffer_offset: usize, - kernel_stack_guard_page_offset: usize, - #[allow(dead_code)] - pub(super) kernel_stack_size_rounded: usize, - boot_stack_buffer_offset: usize, - - // other - pub(crate) peb_address: usize, - code_size: usize, - // The total size of the page tables - total_page_table_size: usize, - // The offset in the sandbox memory where the code starts - guest_code_offset: usize, -} - -impl Debug for SandboxMemoryLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SandboxMemoryLayout") - .field( - "Total Memory Size", - &format_args!("{:#x}", self.get_memory_size().unwrap_or(0)), - ) - .field("Stack Size", &format_args!("{:#x}", self.stack_size)) - .field("Heap Size", &format_args!("{:#x}", self.heap_size)) - .field("PEB Address", &format_args!("{:#x}", self.peb_address)) - .field("PEB Offset", &format_args!("{:#x}", self.peb_offset)) - .field("Code Size", &format_args!("{:#x}", self.code_size)) - .field( - "Security Cookie Seed Offset", - &format_args!("{:#x}", self.peb_security_cookie_seed_offset), - ) - .field( - "Guest Dispatch Function Pointer Offset", - &format_args!("{:#x}", self.peb_guest_dispatch_function_ptr_offset), - ) - .field( - "Host Function Definitions Offset", - &format_args!("{:#x}", self.peb_host_function_definitions_offset), - ) - .field( - "Host Exception Offset", - &format_args!("{:#x}", self.peb_host_exception_offset), - ) - .field( - "Guest Error Offset", - &format_args!("{:#x}", self.peb_guest_error_offset), - ) - .field( - "Code and OutB Pointer Offset", - &format_args!("{:#x}", self.peb_code_and_outb_pointer_offset), - ) - .field( - "Input Data Offset", - &format_args!("{:#x}", self.peb_input_data_offset), - ) - .field( - "Output Data Offset", - &format_args!("{:#x}", self.peb_output_data_offset), - ) - .field( - "Guest Panic Context Offset", - &format_args!("{:#x}", self.peb_guest_panic_context_offset), - ) - .field( - "Guest Heap Offset", - &format_args!("{:#x}", self.peb_heap_data_offset), - ) - .field( - "Guest Stack Offset", - &format_args!("{:#x}", self.peb_guest_stack_data_offset), - ) - .field( - "Host Function Definitions Buffer Offset", - &format_args!("{:#x}", self.host_function_definitions_buffer_offset), - ) - .field( - "Host Exception Buffer Offset", - &format_args!("{:#x}", self.host_exception_buffer_offset), - ) - .field( - "Guest Error Buffer Offset", - &format_args!("{:#x}", self.guest_error_buffer_offset), - ) - .field( - "Input Data Buffer Offset", - &format_args!("{:#x}", self.input_data_buffer_offset), - ) - .field( - "Output Data Buffer Offset", - &format_args!("{:#x}", self.output_data_buffer_offset), - ) - .field( - "Guest Panic Context Buffer Offset", - &format_args!("{:#x}", self.guest_panic_context_buffer_offset), - ) - .field( - "Guest Heap Buffer Offset", - &format_args!("{:#x}", self.guest_heap_buffer_offset), - ) - .field( - "Guard Page Offset", - &format_args!("{:#x}", self.guard_page_offset), - ) - .field( - "Guest User Stack Buffer Offset", - &format_args!("{:#x}", self.guest_user_stack_buffer_offset), - ) - .field( - "Page Table Size", - &format_args!("{:#x}", self.total_page_table_size), - ) - .field( - "Guest Code Offset", - &format_args!("{:#x}", self.guest_code_offset), - ) - .field( - "User Stack Guard Page Offset", - &format_args!("{:#x}", self.user_stack_guard_page_offset), - ) - .field( - "Kernel Stack Buffer Offset", - &format_args!("{:#x}", self.kernel_stack_buffer_offset), - ) - .field( - "Kernel Stack Guard Page Offset", - &format_args!("{:#x}", self.kernel_stack_guard_page_offset), - ) - .field( - "Boot Stack Buffer Offset", - &format_args!("{:#x}", self.boot_stack_buffer_offset), - ) - .finish() - } -} - -impl SandboxMemoryLayout { - /// The offset into the sandbox's memory where the PML4 Table is located. - /// See https://www.pagetable.com/?p=14 for more information. - pub(crate) const PML4_OFFSET: usize = 0x0000; - /// The offset into the sandbox's memory where the Page Directory Pointer - /// Table starts. - pub(super) const PDPT_OFFSET: usize = 0x1000; - /// The offset into the sandbox's memory where the Page Directory starts. - pub(super) const PD_OFFSET: usize = 0x2000; - /// The offset into the sandbox's memory where the Page Tables start. - pub(super) const PT_OFFSET: usize = 0x3000; - /// The address (not the offset) to the start of the page directory - pub(super) const PD_GUEST_ADDRESS: usize = Self::BASE_ADDRESS + Self::PD_OFFSET; - /// The address (not the offset) into sandbox memory where the Page - /// Directory Pointer Table starts - pub(super) const PDPT_GUEST_ADDRESS: usize = Self::BASE_ADDRESS + Self::PDPT_OFFSET; - /// The address (not the offset) into sandbox memory where the Page - /// Tables start - pub(super) const PT_GUEST_ADDRESS: usize = Self::BASE_ADDRESS + Self::PT_OFFSET; - /// The maximum amount of memory a single sandbox will be allowed. - /// The addressable virtual memory with current paging setup is virtual address 0x0 - 0x40000000 (excl.), - /// However, the memory up to Self::BASE_ADDRESS is not used. - const MAX_MEMORY_SIZE: usize = 0x40000000 - Self::BASE_ADDRESS; - - /// The base address of the sandbox's memory. - pub(crate) const BASE_ADDRESS: usize = 0x0200000; - - // the offset into a sandbox's input/output buffer where the stack starts - const STACK_POINTER_SIZE_BYTES: u64 = 8; - - /// Create a new `SandboxMemoryLayout` with the given - /// `SandboxConfiguration`, code size and stack/heap size. - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn new( - cfg: SandboxConfiguration, - code_size: usize, - stack_size: usize, - heap_size: usize, - ) -> Result { - let total_page_table_size = - Self::get_total_page_table_size(cfg, code_size, stack_size, heap_size); - let guest_code_offset = total_page_table_size; - // The following offsets are to the fields of the PEB struct itself! - let peb_offset = total_page_table_size + round_up_to(code_size, PAGE_SIZE_USIZE); - let peb_security_cookie_seed_offset = - peb_offset + offset_of!(HyperlightPEB, security_cookie_seed); - let peb_guest_dispatch_function_ptr_offset = - peb_offset + offset_of!(HyperlightPEB, guest_function_dispatch_ptr); - let peb_host_function_definitions_offset = - peb_offset + offset_of!(HyperlightPEB, hostFunctionDefinitions); - let peb_host_exception_offset = peb_offset + offset_of!(HyperlightPEB, hostException); - let peb_guest_error_offset = peb_offset + offset_of!(HyperlightPEB, guestErrorData); - let peb_code_and_outb_pointer_offset = peb_offset + offset_of!(HyperlightPEB, pCode); - let peb_runmode_offset = peb_offset + offset_of!(HyperlightPEB, runMode); - let peb_input_data_offset = peb_offset + offset_of!(HyperlightPEB, inputdata); - let peb_output_data_offset = peb_offset + offset_of!(HyperlightPEB, outputdata); - let peb_guest_panic_context_offset = - peb_offset + offset_of!(HyperlightPEB, guestPanicContextData); - let peb_heap_data_offset = peb_offset + offset_of!(HyperlightPEB, guestheapData); - let peb_guest_stack_data_offset = peb_offset + offset_of!(HyperlightPEB, gueststackData); - - // The following offsets are the actual values that relate to memory layout, - // which are written to PEB struct - let peb_address = Self::BASE_ADDRESS + peb_offset; - // make sure host function definitions buffer starts at 4K boundary - let host_function_definitions_buffer_offset = round_up_to( - peb_guest_stack_data_offset + size_of::(), - PAGE_SIZE_USIZE, - ); - // make sure host exception buffer starts at 4K boundary - let host_exception_buffer_offset = round_up_to( - host_function_definitions_buffer_offset + cfg.get_host_function_definition_size(), - PAGE_SIZE_USIZE, - ); - let guest_error_buffer_offset = round_up_to( - host_exception_buffer_offset + cfg.get_host_exception_size(), - PAGE_SIZE_USIZE, - ); - let input_data_buffer_offset = round_up_to( - guest_error_buffer_offset + cfg.get_guest_error_buffer_size(), - PAGE_SIZE_USIZE, - ); - let output_data_buffer_offset = round_up_to( - input_data_buffer_offset + cfg.get_input_data_size(), - PAGE_SIZE_USIZE, - ); - let guest_panic_context_buffer_offset = round_up_to( - output_data_buffer_offset + cfg.get_output_data_size(), - PAGE_SIZE_USIZE, - ); - // make sure heap buffer starts at 4K boundary - let guest_heap_buffer_offset = round_up_to( - guest_panic_context_buffer_offset + cfg.get_guest_panic_context_buffer_size(), - PAGE_SIZE_USIZE, - ); - // make sure guard page starts at 4K boundary - let guard_page_offset = round_up_to(guest_heap_buffer_offset + heap_size, PAGE_SIZE_USIZE); - let guest_user_stack_buffer_offset = guard_page_offset + PAGE_SIZE_USIZE; - // round up stack size to page size. This is needed for MemoryRegion - let stack_size_rounded = round_up_to(stack_size, PAGE_SIZE_USIZE); - - let user_stack_guard_page_offset = guest_user_stack_buffer_offset + stack_size_rounded; - let kernel_stack_buffer_offset = user_stack_guard_page_offset + PAGE_SIZE_USIZE; - let kernel_stack_size_rounded = round_up_to(cfg.get_kernel_stack_size(), PAGE_SIZE_USIZE); - let kernel_stack_guard_page_offset = kernel_stack_buffer_offset + kernel_stack_size_rounded; - let boot_stack_buffer_offset = kernel_stack_guard_page_offset + PAGE_SIZE_USIZE; - - Ok(Self { - peb_offset, - stack_size: stack_size_rounded, - heap_size, - peb_security_cookie_seed_offset, - peb_guest_dispatch_function_ptr_offset, - peb_host_function_definitions_offset, - peb_host_exception_offset, - peb_guest_error_offset, - peb_code_and_outb_pointer_offset, - peb_runmode_offset, - peb_input_data_offset, - peb_output_data_offset, - peb_guest_panic_context_offset, - peb_heap_data_offset, - peb_guest_stack_data_offset, - guest_error_buffer_offset, - sandbox_memory_config: cfg, - code_size, - host_function_definitions_buffer_offset, - host_exception_buffer_offset, - input_data_buffer_offset, - output_data_buffer_offset, - guest_heap_buffer_offset, - guest_user_stack_buffer_offset, - peb_address, - guest_panic_context_buffer_offset, - guard_page_offset, - total_page_table_size, - guest_code_offset, - user_stack_guard_page_offset, - kernel_stack_buffer_offset, - kernel_stack_guard_page_offset, - kernel_stack_size_rounded, - boot_stack_buffer_offset, - }) - } - - /// Gets the offset in guest memory to the RunMode field in the PEB struct. - pub fn get_run_mode_offset(&self) -> usize { - self.peb_runmode_offset - } - - /// Get the offset in guest memory to the size field in the - /// `HostExceptionData` structure. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_host_exception_size_offset(&self) -> usize { - // The size field is the first field in the `HostExceptionData` struct - self.peb_host_exception_offset - } - - /// Get the offset in guest memory to the max size of the guest error buffer - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_guest_error_buffer_size_offset(&self) -> usize { - self.peb_guest_error_offset - } - - /// Get the offset in guest memory to the error message buffer pointer - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_guest_error_buffer_pointer_offset(&self) -> usize { - self.peb_guest_error_offset + size_of::() - } - - /// Get the offset in guest memory to the output data size - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_output_data_size_offset(&self) -> usize { - // The size field is the first field in the `OutputData` struct - self.peb_output_data_offset - } - - /// Get the offset in guest memory to the host function definitions - /// size - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_host_function_definitions_size_offset(&self) -> usize { - // The size field is the first field in the `HostFunctions` struct - self.peb_host_function_definitions_offset - } - - /// Get the offset in guest memory to the host function definitions - /// pointer. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_host_function_definitions_pointer_offset(&self) -> usize { - // The size field is the field after the size field in the `HostFunctions` struct which is a u64 - self.peb_host_function_definitions_offset + size_of::() - } - - /// Get the offset in guest memory to the minimum guest stack address. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_min_guest_stack_address_offset(&self) -> usize { - // The minimum guest user stack address is the start of the guest stack - self.peb_guest_stack_data_offset - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_guest_stack_size(&self) -> usize { - self.stack_size - } - - /// Get the offset in guest memory to the start of host errors - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_host_exception_offset(&self) -> usize { - self.host_exception_buffer_offset - } - - /// Get the offset in guest memory to the OutB pointer. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_outb_pointer_offset(&self) -> usize { - // The outb pointer is immediately after the code pointer - // in the `CodeAndOutBPointers` struct which is a u64 - self.peb_code_and_outb_pointer_offset + size_of::() - } - - /// Get the offset in guest memory to the OutB context. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_outb_context_offset(&self) -> usize { - // The outb context is immediately after the outb pointer - // in the `CodeAndOutBPointers` struct which is a u64 - self.get_outb_pointer_offset() + size_of::() - } - - /// Get the offset in guest memory to the output data pointer. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_output_data_pointer_offset(&self) -> usize { - // This field is immediately after the output data size field, - // which is a `u64`. - self.get_output_data_size_offset() + size_of::() - } - - /// Get the offset in guest memory to the start of output data. - /// - /// This function exists to accommodate the macro that generates C API - /// compatible functions. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_output_data_offset(&self) -> usize { - self.output_data_buffer_offset - } - - /// Get the offset in guest memory to the input data size. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_input_data_size_offset(&self) -> usize { - // The input data size is the first field in the `InputData` struct - self.peb_input_data_offset - } - - /// Get the offset in guest memory to the input data pointer. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_input_data_pointer_offset(&self) -> usize { - // The input data pointer is immediately after the input - // data size field in the `InputData` struct which is a `u64`. - self.get_input_data_size_offset() + size_of::() - } - - /// Get the offset in guest memory to the code pointer - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_code_pointer_offset(&self) -> usize { - // The code pointer is the first field - // in the `CodeAndOutBPointers` struct which is a u64 - self.peb_code_and_outb_pointer_offset - } - - /// Get the offset in guest memory to where the guest dispatch function - /// pointer is written - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_dispatch_function_pointer_offset(&self) -> usize { - self.peb_guest_dispatch_function_ptr_offset - } - - /// Get the offset in guest memory to the PEB address - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_in_process_peb_offset(&self) -> usize { - self.peb_offset - } - - /// Get the offset in guest memory to the heap size - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_heap_size_offset(&self) -> usize { - self.peb_heap_data_offset - } - - /// Get the offset of the heap pointer in guest memory, - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_heap_pointer_offset(&self) -> usize { - // The heap pointer is immediately after the - // heap size field in the `GuestHeap` struct which is a `u64`. - self.get_heap_size_offset() + size_of::() - } - - /// Get the offset to the top of the stack in guest memory - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_top_of_user_stack_offset(&self) -> usize { - self.guest_user_stack_buffer_offset - } - - /// Get the offset of the user stack pointer in guest memory, - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_user_stack_pointer_offset(&self) -> usize { - // The userStackAddress is immediately after the - // minUserStackAddress (top of user stack) field in the `GuestStackData` struct which is a `u64`. - self.get_min_guest_stack_address_offset() + size_of::() - } - - /// Get the offset of the kernel stack pointer in guest memory, - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_kernel_stack_pointer_offset(&self) -> usize { - // The kernelStackAddress is immediately after the - // userStackAddress in the `GuestStackData` struct which is a `u64`. - self.get_user_stack_pointer_offset() + size_of::() - } - - /// Get the offset of the boot stack pointer in guest memory, - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_boot_stack_pointer_offset(&self) -> usize { - // The bootStackAddress is immediately after the - // kernelStackAddress in the `GuestStackData` struct which is a `u64`. - self.get_kernel_stack_pointer_offset() + size_of::() - } - - // Get the offset in guest memory to the start of the guest panic context data - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_panic_context_offset(&self) -> usize { - self.peb_guest_panic_context_offset - } - - // Get the offset to the guest panic context buffer size - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_panic_context_size_offset(&self) -> usize { - // The size field is the first field in the `GuestPanicContext` data - self.peb_guest_panic_context_offset - } - - /// Get the offset to the guest panic context buffer pointer - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_panic_context_buffer_pointer_offset(&self) -> usize { - // The guest panic data pointer is immediately after the guest - // panic data size field in the `GuestPanicContext` data which is a `u64` - self.get_guest_panic_context_size_offset() + size_of::() - } - - /// Get the offset to the guest panic context buffer pointer - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_panic_context_buffer_offset(&self) -> usize { - self.guest_panic_context_buffer_offset - } - - /// Get the offset to the guest guard page - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn get_guard_page_offset(&self) -> usize { - self.guard_page_offset - } - - /// Get the total size of guest memory in `self`'s memory - /// layout. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_unaligned_memory_size(&self) -> usize { - self.get_boot_stack_buffer_offset() + PAGE_SIZE_USIZE - } - - /// get the code offset - /// This is the offset in the sandbox memory where the code starts - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_guest_code_offset(&self) -> usize { - self.guest_code_offset - } - - /// Get the guest address of the code section in the sandbox - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_code_address(&self) -> usize { - Self::BASE_ADDRESS + self.guest_code_offset - } - - /// Get the offset in guest memory to the user stack guard page - /// This is the offset in the sandbox memory where the user stack guard page starts - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_user_stack_guard_page_offset(&self) -> usize { - self.user_stack_guard_page_offset - } - - /// Get the offset in guest memory to the kernel stack buffer - /// This is the offset in the sandbox memory where the kernel stack starts - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_kernel_stack_buffer_offset(&self) -> usize { - self.kernel_stack_buffer_offset - } - - /// Get the offset in guest memory to the kernel stack guard page - /// This is the offset in the sandbox memory where the kernel stack guard page starts - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_kernel_stack_guard_page_offset(&self) -> usize { - self.kernel_stack_guard_page_offset - } - - /// Get the offset in guest memory to the boot stack buffer - /// This is the offset in the sandbox memory where the boot stack starts - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_boot_stack_buffer_offset(&self) -> usize { - self.boot_stack_buffer_offset - } - - #[cfg(test)] - /// Get the page table size - fn get_page_table_size(&self) -> usize { - self.total_page_table_size - } - - // This function calculates the page table size for the sandbox - // We need enough memory to store the PML4, PDPT, PD and PTs - // The size of a single table is 4K, we can map up to 1GB total memory which requires 1 PML4, 1 PDPT, 1 PD and 512 PTs - // but we only need enough PTs to map the memory we are using. (In other words we only need 512 PTs to map the memory if the memory size is 1GB) - // - // Because we always start the physical address space at 0x200_000 - // we can calculate the amount of memory needed for the PTs by calculating how much memory is needed for the sandbox configuration in total, - // then add 0x200_000 to that (as we start at 0x200_000), - // and then add 3 * 4K (for the PML4, PDPT and PD) to that, - // then add 2MB to that (the maximum size of memory required for the PTs themselves is 2MB when we map 1GB of memory in 4K pages), - // then divide that by 0x200_000 (as we can map 2MB in each PT) and then round the result up by 1 . - // This will give us the total size of the PTs required for the sandbox to which we can add the size of the PML4, PDPT and PD. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_total_page_table_size( - cfg: SandboxConfiguration, - code_size: usize, - stack_size: usize, - heap_size: usize, - ) -> usize { - // Get the configured memory size (assume each section is 4K aligned) - - let mut total_mapped_memory_size: usize = round_up_to(code_size, PAGE_SIZE_USIZE); - total_mapped_memory_size += round_up_to(stack_size, PAGE_SIZE_USIZE); - total_mapped_memory_size += round_up_to(heap_size, PAGE_SIZE_USIZE); - total_mapped_memory_size += round_up_to(cfg.get_host_exception_size(), PAGE_SIZE_USIZE); - total_mapped_memory_size += - round_up_to(cfg.get_host_function_definition_size(), PAGE_SIZE_USIZE); - total_mapped_memory_size += round_up_to(cfg.get_guest_error_buffer_size(), PAGE_SIZE_USIZE); - total_mapped_memory_size += round_up_to(cfg.get_input_data_size(), PAGE_SIZE_USIZE); - total_mapped_memory_size += round_up_to(cfg.get_output_data_size(), PAGE_SIZE_USIZE); - total_mapped_memory_size += - round_up_to(cfg.get_guest_panic_context_buffer_size(), PAGE_SIZE_USIZE); - total_mapped_memory_size += round_up_to(size_of::(), PAGE_SIZE_USIZE); - - // Add the base address of the sandbox - total_mapped_memory_size += Self::BASE_ADDRESS; - - // Add the size of the PML4, PDPT and PD - total_mapped_memory_size += 3 * PAGE_SIZE_USIZE; - - // Add the maximum possible size of the PTs - total_mapped_memory_size += 512 * PAGE_SIZE_USIZE; - - // Get the number of pages needed for the PTs - - let num_pages: usize = ((total_mapped_memory_size + AMOUNT_OF_MEMORY_PER_PT - 1) - / AMOUNT_OF_MEMORY_PER_PT) - + 1 // Round up - + 3; // PML4, PDPT, PD - - num_pages * PAGE_SIZE_USIZE - } - - /// Get the total size of guest memory in `self`'s memory - /// layout aligned to page size boundaries. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_memory_size(&self) -> Result { - let total_memory = self.get_unaligned_memory_size(); - - // Size should be a multiple of page size. - let remainder = total_memory % PAGE_SIZE_USIZE; - let multiples = total_memory / PAGE_SIZE_USIZE; - let size = match remainder { - 0 => total_memory, - _ => (multiples + 1) * PAGE_SIZE_USIZE, - }; - - if size > Self::MAX_MEMORY_SIZE { - Err(MemoryRequestTooBig(size, Self::MAX_MEMORY_SIZE)) - } else { - Ok(size) - } - } - - /// Returns the memory regions associated with this memory layout, - /// suitable for passing to a hypervisor for mapping into memory - pub fn get_memory_regions(&self, shared_mem: &GuestSharedMemory) -> Result> { - let mut builder = MemoryRegionVecBuilder::new(Self::BASE_ADDRESS, shared_mem.base_addr()); - - // PML4, PDPT, PD - let code_offset = builder.push_page_aligned( - self.total_page_table_size, - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - PageTables, - ); - - if code_offset != self.guest_code_offset { - return Err(new_error!( - "Code offset does not match expected code offset expected: {}, actual: {}", - self.guest_code_offset, - code_offset - )); - } - - // code - let peb_offset = builder.push_page_aligned( - self.code_size, - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE, - Code, - ); - - let expected_peb_offset = TryInto::::try_into(self.peb_offset)?; - - if peb_offset != expected_peb_offset { - return Err(new_error!( - "PEB offset does not match expected PEB offset expected: {}, actual: {}", - expected_peb_offset, - peb_offset - )); - } - - // PEB - let host_functions_definitions_offset = builder.push_page_aligned( - size_of::(), - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - Peb, - ); - - let expected_host_functions_definitions_offset = - TryInto::::try_into(self.host_function_definitions_buffer_offset)?; - - if host_functions_definitions_offset != expected_host_functions_definitions_offset { - return Err(new_error!( - "Host Function Definitions offset does not match expected Host Function Definitions offset expected: {}, actual: {}", - expected_host_functions_definitions_offset, - host_functions_definitions_offset - )); - } - - // host function definitions - let host_exception_offset = builder.push_page_aligned( - self.sandbox_memory_config - .get_host_function_definition_size(), - MemoryRegionFlags::READ, - HostFunctionDefinitions, - ); - - let expected_host_exception_offset = - TryInto::::try_into(self.host_exception_buffer_offset)?; - - if host_exception_offset != expected_host_exception_offset { - return Err(new_error!( - "Host Exception offset does not match expected Host Exception offset expected: {}, actual: {}", - expected_host_exception_offset, - host_exception_offset - )); - } - - // host exception - let guest_error_offset = builder.push_page_aligned( - self.sandbox_memory_config.get_host_exception_size(), - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - HostExceptionData, - ); - - let expected_guest_error_offset = - TryInto::::try_into(self.guest_error_buffer_offset)?; - - if guest_error_offset != expected_guest_error_offset { - return Err(new_error!( - "Guest Error offset does not match expected Guest Error offset expected: {}, actual: {}", - expected_guest_error_offset, - guest_error_offset - )); - } - - // guest error - let input_data_offset = builder.push_page_aligned( - self.sandbox_memory_config.get_guest_error_buffer_size(), - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - GuestErrorData, - ); - - let expected_input_data_offset = TryInto::::try_into(self.input_data_buffer_offset)?; - - if input_data_offset != expected_input_data_offset { - return Err(new_error!( - "Input Data offset does not match expected Input Data offset expected: {}, actual: {}", - expected_input_data_offset, - input_data_offset - )); - } - - // guest input data - let output_data_offset = builder.push_page_aligned( - self.sandbox_memory_config.get_input_data_size(), - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - InputData, - ); - - let expected_output_data_offset = - TryInto::::try_into(self.output_data_buffer_offset)?; - - if output_data_offset != expected_output_data_offset { - return Err(new_error!( - "Output Data offset does not match expected Output Data offset expected: {}, actual: {}", - expected_output_data_offset, - output_data_offset - )); - } - - // guest output data - let guest_panic_context_offset = builder.push_page_aligned( - self.sandbox_memory_config.get_output_data_size(), - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - OutputData, - ); - - let expected_guest_panic_context_offset = - TryInto::::try_into(self.guest_panic_context_buffer_offset)?; - - if guest_panic_context_offset != expected_guest_panic_context_offset { - return Err(new_error!( - "Guest Panic Context offset does not match expected Guest Panic Context offset expected: {}, actual: {}", - expected_guest_panic_context_offset, - guest_panic_context_offset - )); - } - - // guest panic context - let heap_offset = builder.push_page_aligned( - self.sandbox_memory_config - .get_guest_panic_context_buffer_size(), - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - PanicContext, - ); - - let expected_heap_offset = TryInto::::try_into(self.guest_heap_buffer_offset)?; - - if heap_offset != expected_heap_offset { - return Err(new_error!( - "Guest Heap offset does not match expected Guest Heap offset expected: {}, actual: {}", - expected_heap_offset, - heap_offset - )); - } - - // heap - #[cfg(feature = "executable_heap")] - let guard_page_offset = builder.push_page_aligned( - self.heap_size, - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE, - Heap, - ); - #[cfg(not(feature = "executable_heap"))] - let guard_page_offset = builder.push_page_aligned( - self.heap_size, - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - Heap, - ); - - let expected_guard_page_offset = TryInto::::try_into(self.guard_page_offset)?; - - if guard_page_offset != expected_guard_page_offset { - return Err(new_error!( - "Guard Page offset does not match expected Guard Page offset expected: {}, actual: {}", - expected_guard_page_offset, - guard_page_offset - )); - } - - // guard page - let stack_offset = builder.push_page_aligned( - PAGE_SIZE_USIZE, - MemoryRegionFlags::READ | MemoryRegionFlags::STACK_GUARD, - GuardPage, - ); - - let expected_stack_offset = - TryInto::::try_into(self.guest_user_stack_buffer_offset)?; - - if stack_offset != expected_stack_offset { - return Err(new_error!( - "Stack offset does not match expected Stack offset expected: {}, actual: {}", - expected_stack_offset, - stack_offset - )); - } - - // stack - let user_stack_guard_page_offset = builder.push_page_aligned( - self.get_guest_stack_size(), - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - Stack, - ); - - let expected_user_stack_guard_page_offset = - TryInto::::try_into(self.get_top_of_user_stack_offset())? - + self.get_guest_stack_size(); - - if user_stack_guard_page_offset != expected_user_stack_guard_page_offset { - return Err(new_error!( - "User Guard Page offset does not match expected User Guard Page offset expected: {}, actual: {}", - expected_user_stack_guard_page_offset, - user_stack_guard_page_offset - )); - } - - let kernel_stack_offset = builder.push_page_aligned( - PAGE_SIZE_USIZE, - MemoryRegionFlags::READ | MemoryRegionFlags::STACK_GUARD, - GuardPage, - ); - - let expected_kernel_stack_offset = - TryInto::::try_into(self.kernel_stack_buffer_offset)?; - - if kernel_stack_offset != expected_kernel_stack_offset { - return Err(new_error!( - "Kernel Stack offset does not match expected Kernel Stack offset expected: {}, actual: {}", - expected_kernel_stack_offset, - kernel_stack_offset - )); - } - - let kernel_stack_guard_page_offset = builder.push_page_aligned( - self.kernel_stack_size_rounded, - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - KernelStack, - ); - - let expected_kernel_stack_guard_page_offset = - TryInto::::try_into(self.kernel_stack_guard_page_offset)?; - - if kernel_stack_guard_page_offset != expected_kernel_stack_guard_page_offset { - return Err(new_error!( - "Kernel Guard Page offset does not match expected Kernel Guard Page offset expected: {}, actual: {}", - expected_kernel_stack_guard_page_offset, - kernel_stack_guard_page_offset - )); - } - - let boot_stack_offset = builder.push_page_aligned( - PAGE_SIZE_USIZE, - MemoryRegionFlags::READ | MemoryRegionFlags::STACK_GUARD, - GuardPage, - ); - - let expected_boot_stack_offset = TryInto::::try_into(self.boot_stack_buffer_offset)?; - - if boot_stack_offset != expected_boot_stack_offset { - return Err(new_error!( - "Boot Stack offset does not match expected Boot Stack offset expected: {}, actual: {}", - expected_boot_stack_offset, - boot_stack_offset - )); - } - - let final_offset = builder.push_page_aligned( - PAGE_SIZE_USIZE, - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - BootStack, - ); - - let expected_final_offset = TryInto::::try_into(self.get_memory_size()?)?; - - if final_offset != expected_final_offset { - return Err(new_error!( - "Final offset does not match expected Final offset expected: {}, actual: {}", - expected_final_offset, - final_offset - )); - } - - Ok(builder.build()) - } - - /// Write the finished memory layout to `shared_mem` and return - /// `Ok` if successful. - /// - /// Note: `shared_mem` may have been modified, even if `Err` was returned - /// from this function. - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn write( - &self, - shared_mem: &mut ExclusiveSharedMemory, - guest_offset: usize, - size: usize, - run_inprocess: bool, - ) -> Result<()> { - macro_rules! get_address { - ($something:ident) => { - paste! { - if guest_offset == 0 { - let offset = self.[<$something _offset>]; - let calculated_addr = shared_mem.calculate_address(offset)?; - u64::try_from(calculated_addr)? - } else { - u64::try_from(guest_offset + self.[<$something _offset>])? - } - } - }; - } - - if guest_offset != SandboxMemoryLayout::BASE_ADDRESS - && guest_offset != shared_mem.base_addr() - { - return Err(GuestOffsetIsInvalid(guest_offset)); - } - - // Start of setting up the PEB. The following are in the order of the PEB fields - - // Set up the security cookie seed - let mut security_cookie_seed = [0u8; 8]; - rng().fill_bytes(&mut security_cookie_seed); - shared_mem.copy_from_slice(&security_cookie_seed, self.peb_security_cookie_seed_offset)?; - - // Skip guest_dispatch_function_ptr_offset because it is set by the guest - - // Set up Host Function Definition - shared_mem.write_u64( - self.get_host_function_definitions_size_offset(), - self.sandbox_memory_config - .get_host_function_definition_size() - .try_into()?, - )?; - let addr = get_address!(host_function_definitions_buffer); - shared_mem.write_u64(self.get_host_function_definitions_pointer_offset(), addr)?; - - // Set up Host Exception Header - // The peb only needs to include the size, not the actual buffer - // since the the guest wouldn't want to read the buffer anyway - shared_mem.write_u64( - self.get_host_exception_size_offset(), - self.sandbox_memory_config - .get_host_exception_size() - .try_into()?, - )?; - - // Set up Guest Error Fields - let addr = get_address!(guest_error_buffer); - shared_mem.write_u64(self.get_guest_error_buffer_pointer_offset(), addr)?; - shared_mem.write_u64( - self.get_guest_error_buffer_size_offset(), - u64::try_from(self.sandbox_memory_config.get_guest_error_buffer_size())?, - )?; - - // Skip code, is set when loading binary - // skip outb and outb context, is set when running in_proc - - // Set RunMode in PEB - shared_mem.write_u64( - self.get_run_mode_offset(), - match ( - run_inprocess, - cfg!(target_os = "windows"), - cfg!(target_os = "linux"), - ) { - (false, _, _) => RunMode::Hypervisor as u64, - (true, true, _) => RunMode::InProcessWindows as u64, - (true, _, true) => RunMode::InProcessLinux as u64, - (true, _, _) => log_then_return!("Unsupported OS for in-process mode"), - }, - )?; - - // Set up input buffer pointer - shared_mem.write_u64( - self.get_input_data_size_offset(), - self.sandbox_memory_config - .get_input_data_size() - .try_into()?, - )?; - let addr = get_address!(input_data_buffer); - shared_mem.write_u64(self.get_input_data_pointer_offset(), addr)?; - - // Set up output buffer pointer - shared_mem.write_u64( - self.get_output_data_size_offset(), - self.sandbox_memory_config - .get_output_data_size() - .try_into()?, - )?; - let addr = get_address!(output_data_buffer); - shared_mem.write_u64(self.get_output_data_pointer_offset(), addr)?; - - // Set up the guest panic context buffer - let addr = get_address!(guest_panic_context_buffer); - shared_mem.write_u64( - self.get_guest_panic_context_size_offset(), - self.sandbox_memory_config - .get_guest_panic_context_buffer_size() - .try_into()?, - )?; - shared_mem.write_u64(self.get_guest_panic_context_buffer_pointer_offset(), addr)?; - - // Set up heap buffer pointer - let addr = get_address!(guest_heap_buffer); - shared_mem.write_u64(self.get_heap_size_offset(), self.heap_size.try_into()?)?; - shared_mem.write_u64(self.get_heap_pointer_offset(), addr)?; - - // Set up user stack pointers - - // Set up Min Guest User Stack Address - - // The top of the user stack is calculated as the size of the guest memory + the guest offset which gives us the - // address at the bottom of the guest memory. - // we then subtract the size of the stack, the size of the kernel stack, - // the size of the boot stack, the size of the user stack guard page and the size of the kernel stack guard page - // which are all 4K - - let bottom = guest_offset + size; - let min_user_stack_address = bottom - - self.stack_size - - self.kernel_stack_size_rounded - - PAGE_SIZE_USIZE - - PAGE_SIZE_USIZE - - PAGE_SIZE_USIZE; - - // Top of user stack - - shared_mem.write_u64( - self.get_min_guest_stack_address_offset(), - min_user_stack_address.try_into()?, - )?; - - // Start of user stack - - let start_of_user_stack: u64 = (min_user_stack_address + self.stack_size).try_into()?; - - shared_mem.write_u64(self.get_user_stack_pointer_offset(), start_of_user_stack)?; - - // Start of kernel stack - - // There is a guard page between the user stack and the kernel stack and then we need to add the size of the kernel stack - - let start_of_kernel_stack: u64 = - start_of_user_stack + (PAGE_SIZE_USIZE + self.kernel_stack_size_rounded) as u64; - - shared_mem.write_u64( - self.get_kernel_stack_pointer_offset(), - start_of_kernel_stack, - )?; - - // Start of boot stack - - // There is a guard page between the kernel stack and the boot stack and then we need to add the size of the boot stack - - let start_of_boot_stack: u64 = start_of_kernel_stack + (PAGE_SIZE_USIZE * 2) as u64; - - shared_mem.write_u64(self.get_boot_stack_pointer_offset(), start_of_boot_stack)?; - - // End of setting up the PEB - - // Initialize the stack pointers of input data and output data - // to point to the ninth (index 8) byte, which is the first free address - // of the each respective stack. The first 8 bytes are the stack pointer itself. - shared_mem.write_u64( - self.input_data_buffer_offset, - Self::STACK_POINTER_SIZE_BYTES, - )?; - shared_mem.write_u64( - self.output_data_buffer_offset, - Self::STACK_POINTER_SIZE_BYTES, - )?; - - Ok(()) - } -} - -fn round_up_to(value: usize, multiple: usize) -> usize { - (value + multiple - 1) & !(multiple - 1) -} - -#[cfg(test)] -mod tests { - use hyperlight_common::mem::PAGE_SIZE_USIZE; - - use super::*; - - #[test] - fn test_round_up() { - assert_eq!(0, round_up_to(0, 4)); - assert_eq!(4, round_up_to(1, 4)); - assert_eq!(4, round_up_to(2, 4)); - assert_eq!(4, round_up_to(3, 4)); - assert_eq!(4, round_up_to(4, 4)); - assert_eq!(8, round_up_to(5, 4)); - assert_eq!(8, round_up_to(6, 4)); - assert_eq!(8, round_up_to(7, 4)); - assert_eq!(8, round_up_to(8, 4)); - assert_eq!(PAGE_SIZE_USIZE, round_up_to(44, PAGE_SIZE_USIZE)); - assert_eq!(PAGE_SIZE_USIZE, round_up_to(4095, PAGE_SIZE_USIZE)); - assert_eq!(PAGE_SIZE_USIZE, round_up_to(4096, PAGE_SIZE_USIZE)); - assert_eq!(PAGE_SIZE_USIZE * 2, round_up_to(4097, PAGE_SIZE_USIZE)); - assert_eq!(PAGE_SIZE_USIZE * 2, round_up_to(8191, PAGE_SIZE_USIZE)); - } - - // helper func for testing - fn get_expected_memory_size(layout: &SandboxMemoryLayout) -> usize { - let cfg = layout.sandbox_memory_config; - let mut expected_size = 0; - // in order of layout - expected_size += layout.get_page_table_size(); - expected_size += layout.code_size; - - expected_size += round_up_to(size_of::(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(cfg.get_host_function_definition_size(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(cfg.get_host_exception_size(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(cfg.get_guest_error_buffer_size(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(cfg.get_input_data_size(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(cfg.get_output_data_size(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(cfg.get_guest_panic_context_buffer_size(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(layout.heap_size, PAGE_SIZE_USIZE); - - expected_size += PAGE_SIZE_USIZE; // guard page - - expected_size += round_up_to(layout.stack_size, PAGE_SIZE_USIZE); - - expected_size += PAGE_SIZE_USIZE; // user stack guard page - - expected_size += round_up_to(layout.kernel_stack_size_rounded, PAGE_SIZE_USIZE); - - expected_size += PAGE_SIZE_USIZE; // kernel stack guard page - - expected_size += PAGE_SIZE_USIZE; // boot stack - - expected_size - } - - #[test] - fn test_get_memory_size() { - let sbox_cfg = SandboxConfiguration::default(); - let sbox_mem_layout = SandboxMemoryLayout::new(sbox_cfg, 4096, 2048, 4096).unwrap(); - assert_eq!( - sbox_mem_layout.get_memory_size().unwrap(), - get_expected_memory_size(&sbox_mem_layout) - ); - } -} diff --git a/src/hyperlight_host/src/mem/loaded_lib.rs b/src/hyperlight_host/src/mem/loaded_lib.rs index 9dcf4d939..572dd7019 100644 --- a/src/hyperlight_host/src/mem/loaded_lib.rs +++ b/src/hyperlight_host/src/mem/loaded_lib.rs @@ -66,7 +66,7 @@ impl LoadedLib { } #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn base_addr(&self) -> RawPtr { + pub(crate) fn base_addr(&self) -> RawPtr { self.inner.base_addr() } } diff --git a/src/hyperlight_host/src/mem/memory_region.rs b/src/hyperlight_host/src/mem/memory_region.rs deleted file mode 100644 index ced79f214..000000000 --- a/src/hyperlight_host/src/mem/memory_region.rs +++ /dev/null @@ -1,288 +0,0 @@ -/* -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. -*/ - -#[cfg(mshv2)] -extern crate mshv_bindings2 as mshv_bindings; -#[cfg(mshv2)] -extern crate mshv_ioctls2 as mshv_ioctls; - -#[cfg(mshv3)] -extern crate mshv_bindings3 as mshv_bindings; -#[cfg(mshv3)] -extern crate mshv_ioctls3 as mshv_ioctls; - -use std::ops::Range; - -use bitflags::bitflags; -#[cfg(mshv)] -use hyperlight_common::mem::PAGE_SHIFT; -use hyperlight_common::mem::PAGE_SIZE_USIZE; -#[cfg(mshv)] -use mshv_bindings::{hv_x64_memory_intercept_message, mshv_user_mem_region}; -#[cfg(mshv2)] -use mshv_bindings::{ - HV_MAP_GPA_EXECUTABLE, HV_MAP_GPA_PERMISSIONS_NONE, HV_MAP_GPA_READABLE, HV_MAP_GPA_WRITABLE, -}; -#[cfg(mshv3)] -use mshv_bindings::{ - MSHV_SET_MEM_BIT_EXECUTABLE, MSHV_SET_MEM_BIT_UNMAP, MSHV_SET_MEM_BIT_WRITABLE, -}; -#[cfg(target_os = "windows")] -use windows::Win32::System::Hypervisor::{self, WHV_MEMORY_ACCESS_TYPE}; - -bitflags! { - /// flags representing memory permission for a memory region - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - pub struct MemoryRegionFlags: u32 { - /// no permissions - const NONE = 0; - /// allow guest to read - const READ = 1; - /// allow guest to write - const WRITE = 2; - /// allow guest to execute - const EXECUTE = 4; - /// identifier that this is a stack guard page - const STACK_GUARD = 8; - } -} - -impl std::fmt::Display for MemoryRegionFlags { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.is_empty() { - write!(f, "NONE") - } else { - let mut first = true; - if self.contains(MemoryRegionFlags::READ) { - write!(f, "READ")?; - first = false; - } - if self.contains(MemoryRegionFlags::WRITE) { - if !first { - write!(f, " | ")?; - } - write!(f, "WRITE")?; - first = false; - } - if self.contains(MemoryRegionFlags::EXECUTE) { - if !first { - write!(f, " | ")?; - } - write!(f, "EXECUTE")?; - } - Ok(()) - } - } -} - -#[cfg(target_os = "windows")] -impl TryFrom for MemoryRegionFlags { - type Error = crate::HyperlightError; - - fn try_from(flags: WHV_MEMORY_ACCESS_TYPE) -> crate::Result { - match flags { - Hypervisor::WHvMemoryAccessRead => Ok(MemoryRegionFlags::READ), - Hypervisor::WHvMemoryAccessWrite => Ok(MemoryRegionFlags::WRITE), - Hypervisor::WHvMemoryAccessExecute => Ok(MemoryRegionFlags::EXECUTE), - _ => Err(crate::HyperlightError::Error( - "unknown memory access type".to_string(), - )), - } - } -} - -#[cfg(mshv)] -impl TryFrom for MemoryRegionFlags { - type Error = crate::HyperlightError; - - fn try_from(msg: hv_x64_memory_intercept_message) -> crate::Result { - let access_type = msg.header.intercept_access_type; - match access_type { - 0 => Ok(MemoryRegionFlags::READ), - 1 => Ok(MemoryRegionFlags::WRITE), - 2 => Ok(MemoryRegionFlags::EXECUTE), - _ => Err(crate::HyperlightError::Error( - "unknown memory access type".to_string(), - )), - } - } -} - -// only used for debugging -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -/// The type of memory region -pub enum MemoryRegionType { - /// The region contains the guest's page tables - PageTables, - /// The region contains the guest's code - Code, - /// The region contains the PEB - Peb, - /// The region contains the Host Function Definitions - HostFunctionDefinitions, - /// The region contains the Host Exception Data - HostExceptionData, - /// The region contains the Guest Error Data - GuestErrorData, - /// The region contains the Input Data - InputData, - /// The region contains the Output Data - OutputData, - /// The region contains the Panic Context - PanicContext, - /// The region contains the Heap - Heap, - /// The region contains the Guard Page - GuardPage, - /// The region contains the Stack - Stack, - /// The region contains the Kernel Stack - KernelStack, - /// The region contains the Boot Stack - BootStack, -} - -/// represents a single memory region inside the guest. All memory within a region has -/// the same memory permissions -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MemoryRegion { - /// the range of guest memory addresses - pub(crate) guest_region: Range, - /// the range of host memory addresses - pub(crate) host_region: Range, - /// memory access flags for the given region - pub(crate) flags: MemoryRegionFlags, - /// the type of memory region - pub(crate) region_type: MemoryRegionType, -} - -pub(crate) struct MemoryRegionVecBuilder { - guest_base_phys_addr: usize, - host_base_virt_addr: usize, - regions: Vec, -} - -impl MemoryRegionVecBuilder { - pub(crate) fn new(guest_base_phys_addr: usize, host_base_virt_addr: usize) -> Self { - Self { - guest_base_phys_addr, - host_base_virt_addr, - regions: Vec::new(), - } - } - - fn push( - &mut self, - size: usize, - flags: MemoryRegionFlags, - region_type: MemoryRegionType, - ) -> usize { - if self.regions.is_empty() { - let guest_end = self.guest_base_phys_addr + size; - let host_end = self.host_base_virt_addr + size; - self.regions.push(MemoryRegion { - guest_region: self.guest_base_phys_addr..guest_end, - host_region: self.host_base_virt_addr..host_end, - flags, - region_type, - }); - return guest_end - self.guest_base_phys_addr; - } - - #[allow(clippy::unwrap_used)] - // we know this is safe because we check if the regions are empty above - let last_region = self.regions.last().unwrap(); - let new_region = MemoryRegion { - guest_region: last_region.guest_region.end..last_region.guest_region.end + size, - host_region: last_region.host_region.end..last_region.host_region.end + size, - flags, - region_type, - }; - let ret = new_region.guest_region.end; - self.regions.push(new_region); - ret - self.guest_base_phys_addr - } - - /// Pushes a memory region with the given size. Will round up the size to the nearest page. - /// Returns the current size of the all memory regions in the builder after adding the given region. - /// # Note: - /// Memory regions pushed MUST match the guest's memory layout, in SandboxMemoryLayout::new(..) - pub(crate) fn push_page_aligned( - &mut self, - size: usize, - flags: MemoryRegionFlags, - region_type: MemoryRegionType, - ) -> usize { - let aligned_size = (size + PAGE_SIZE_USIZE - 1) & !(PAGE_SIZE_USIZE - 1); - self.push(aligned_size, flags, region_type) - } - - /// Consumes the builder and returns a vec of memory regions. The regions are guaranteed to be a contiguous chunk - /// of memory, in other words, there will be any memory gaps between them. - pub(crate) fn build(self) -> Vec { - self.regions - } -} - -#[cfg(mshv)] -impl From for mshv_user_mem_region { - fn from(region: MemoryRegion) -> Self { - let size = (region.guest_region.end - region.guest_region.start) as u64; - let guest_pfn = region.guest_region.start as u64 >> PAGE_SHIFT; - let userspace_addr = region.host_region.start as u64; - - #[cfg(mshv2)] - { - let flags = region.flags.iter().fold(0, |acc, flag| { - let flag_value = match flag { - MemoryRegionFlags::NONE => HV_MAP_GPA_PERMISSIONS_NONE, - MemoryRegionFlags::READ => HV_MAP_GPA_READABLE, - MemoryRegionFlags::WRITE => HV_MAP_GPA_WRITABLE, - MemoryRegionFlags::EXECUTE => HV_MAP_GPA_EXECUTABLE, - _ => 0, // ignore any unknown flags - }; - acc | flag_value - }); - mshv_user_mem_region { - guest_pfn, - size, - userspace_addr, - flags, - } - } - #[cfg(mshv3)] - { - let flags: u8 = region.flags.iter().fold(0, |acc, flag| { - let flag_value = match flag { - MemoryRegionFlags::NONE => 1 << MSHV_SET_MEM_BIT_UNMAP, - MemoryRegionFlags::READ => 0, - MemoryRegionFlags::WRITE => 1 << MSHV_SET_MEM_BIT_WRITABLE, - MemoryRegionFlags::EXECUTE => 1 << MSHV_SET_MEM_BIT_EXECUTABLE, - _ => 0, // ignore any unknown flags - }; - acc | flag_value - }); - - mshv_user_mem_region { - guest_pfn, - size, - userspace_addr, - flags, - ..Default::default() - } - } - } -} diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index 2c4f38e83..102e5ac34 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -14,36 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -use core::mem::size_of; use std::cmp::Ordering; -use std::str::from_utf8; use std::sync::{Arc, Mutex}; use hyperlight_common::flatbuffer_wrappers::function_call::{ validate_guest_function_call_buffer, FunctionCall, }; use hyperlight_common::flatbuffer_wrappers::function_types::ReturnValue; -use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError}; use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData; -use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails; -use serde_json::from_str; use tracing::{instrument, Span}; -use super::exe::ExeInfo; -use super::layout::SandboxMemoryLayout; #[cfg(target_os = "windows")] use super::loaded_lib::LoadedLib; -use super::memory_region::{MemoryRegion, MemoryRegionType}; -use super::ptr::{GuestPtr, RawPtr}; +use super::ptr::RawPtr; use super::ptr_offset::Offset; use super::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemory, SharedMemory}; use super::shared_mem_snapshot::SharedMemorySnapshot; -use crate::error::HyperlightError::{ - ExceptionDataLengthIncorrect, ExceptionMessageTooBig, JsonConversionFailure, NoMemorySnapshot, - UTF8SliceConversionFailure, +use crate::sandbox::sandbox_builder::{ + MemoryRegionFlags, SandboxMemorySections, BASE_ADDRESS, PDPT_OFFSET, PD_OFFSET, PT_OFFSET, }; -use crate::error::HyperlightHostError; -use crate::sandbox::SandboxConfiguration; +use crate::HyperlightError::NoMemorySnapshot; use crate::{log_then_return, new_error, HyperlightError, Result}; /// Paging Flags @@ -58,29 +48,44 @@ const PAGE_USER: u64 = 1 << 2; // User/Supervisor (if this bit is set then the p const PAGE_NX: u64 = 1 << 63; // Execute Disable (if this bit is set then data in the page cannot be executed) // The amount of memory that can be mapped per page table -pub(super) const AMOUNT_OF_MEMORY_PER_PT: usize = 0x200000; -/// Read/write permissions flag for the 64-bit PDE -/// The page size for the 64-bit PDE -/// The size of stack guard cookies +pub(crate) const AMOUNT_OF_MEMORY_PER_PT: usize = 0x200_000; pub(crate) const STACK_COOKIE_LEN: usize = 16; -/// A struct that is responsible for laying out and managing the memory -/// for a given `Sandbox`. +/// SandboxMemoryManager is a struct that pairs a `SharedMemory` and a `SandboxMemorySections` and +/// allows you to snapshot and restore memory states. #[derive(Clone)] pub(crate) struct SandboxMemoryManager { - /// Shared memory for the Sandbox + /// Shared memory for the Sandbox. + /// + /// This field is generic because S can be: + /// - `ExclusiveSharedMemory`, + /// - `HostSharedMemory`, or + /// - `GuestSharedMemory`. pub(crate) shared_mem: S, - /// The memory layout of the underlying shared memory - pub(crate) layout: SandboxMemoryLayout, - /// Whether the sandbox is running in-process - inprocess: bool, + /// Pointer to where to load memory from + /// + /// In in-process mode, this is the direct host memory address. + /// When running in a VM, this is the base address of the VM (0x0). pub(crate) load_addr: RawPtr, - /// Offset for the execution entrypoint from `load_addr` + + /// Offset for the execution entrypoint from `load_addr`. + /// + /// This is obtained from the guest binary. pub(crate) entrypoint_offset: Offset, - /// A vector of memory snapshots that can be used to save and restore the state of the memory - /// This is used by the Rust Sandbox implementation (rather than the mem_snapshot field above which only exists to support current C API) + + /// Memory sections + pub(crate) memory_sections: SandboxMemorySections, + + /// Initial RSP value + pub(crate) init_rsp: u64, + + /// A vector of memory snapshots that can be used to save and restore the state of the memory. snapshots: Arc>>, + + /// Stack guard for the memory + pub(crate) stack_guard: [u8; STACK_COOKIE_LEN], + /// This field must be present, even though it's not read, /// so that its underlying resources are properly dropped at /// the right time. @@ -88,196 +93,144 @@ pub(crate) struct SandboxMemoryManager { _lib: Option, } +/// General implementations for setting up the `SandboxMemoryManager` and its underlying +/// memory. impl SandboxMemoryManager where S: SharedMemory, { - /// Create a new `SandboxMemoryManager` with the given parameters - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn new( - layout: SandboxMemoryLayout, + /// Create a new `SandboxMemoryManager` + pub(crate) fn new( shared_mem: S, - inprocess: bool, load_addr: RawPtr, entrypoint_offset: Offset, + memory_sections: SandboxMemorySections, + init_rsp: u64, #[cfg(target_os = "windows")] lib: Option, ) -> Self { Self { - layout, shared_mem, - inprocess, load_addr, entrypoint_offset, + memory_sections, snapshots: Arc::new(Mutex::new(Vec::new())), + stack_guard: Self::create_stack_guard(), + init_rsp, #[cfg(target_os = "windows")] _lib: lib, } } - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn is_in_process(&self) -> bool { - self.inprocess - } - - /// Get `SharedMemory` in `self` as a mutable reference - pub(crate) fn get_shared_mem_mut(&mut self) -> &mut S { - &mut self.shared_mem - } - - /// Set up the hypervisor partition in the given `SharedMemory` parameter - /// `shared_mem`, with the given memory size `mem_size` + /// Set up the hypervisor partition in the given shared memory, with the given memory size. // TODO: This should perhaps happen earlier and use an // ExclusiveSharedMemory from the beginning. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn set_up_shared_memory( - &mut self, - mem_size: u64, - regions: &mut [MemoryRegion], - ) -> Result { - // Add 0x200000 because that's the start of mapped memory - // For MSVC, move rsp down by 0x28. This gives the called 'main' - // function the appearance that rsp was 16 byte aligned before - // the 'call' that calls main (note we don't really have a return value - // on the stack but some assembly instructions are expecting rsp have - // started 0x8 bytes off of 16 byte alignment when 'main' is invoked. - // We do 0x28 instead of 0x8 because MSVC can expect that there are - // 0x20 bytes of space to write to by the called function. - // I am not sure if this happens with the 'main' method, but we do this - // just in case. - // - // NOTE: We do this also for GCC freestanding binaries because we - // specify __attribute__((ms_abi)) on the start method - let rsp: u64 = self.layout.get_top_of_user_stack_offset() as u64 - + SandboxMemoryLayout::BASE_ADDRESS as u64 - + self.layout.stack_size as u64 - - 0x28; + pub(crate) fn set_up_shared_memory(&mut self) -> Result<()> { + let memory_size = self.memory_sections.get_total_size(); + let memory_sections = self.memory_sections.clone(); self.shared_mem.with_exclusivity(|shared_mem| { - // Create PDL4 table with only 1 PML4E - shared_mem.write_u64( - SandboxMemoryLayout::PML4_OFFSET, - SandboxMemoryLayout::PDPT_GUEST_ADDRESS as u64 | PAGE_PRESENT | PAGE_RW, - )?; + let pml4_offset = self + .memory_sections + .get_paging_structures_offset() + .ok_or("PML4 offset not found")?; + let pdpt_offset = pml4_offset + PDPT_OFFSET; + let pd_offset = pml4_offset + PD_OFFSET; + let pt_offset = pml4_offset + PT_OFFSET; + + // Create PML4 table with only 1 PML4E + shared_mem.write_u64(pml4_offset, pdpt_offset as u64 | PAGE_PRESENT | PAGE_RW)?; // Create PDPT with only 1 PDPTE - shared_mem.write_u64( - SandboxMemoryLayout::PDPT_OFFSET, - SandboxMemoryLayout::PD_GUEST_ADDRESS as u64 | PAGE_PRESENT | PAGE_RW, - )?; + shared_mem.write_u64(pdpt_offset, pd_offset as u64 | PAGE_PRESENT | PAGE_RW)?; for i in 0..512 { - let offset = SandboxMemoryLayout::PD_OFFSET + (i * 8); - let val_to_write: u64 = (SandboxMemoryLayout::PT_GUEST_ADDRESS as u64 - + (i * 4096) as u64) - | PAGE_PRESENT - | PAGE_RW; + let offset = pd_offset + (i * 8); + + let val_to_write: u64 = + (pt_offset as u64 + (i * 4096) as u64) | PAGE_PRESENT | PAGE_RW; shared_mem.write_u64(offset, val_to_write)?; } - // We only need to create enough PTEs to map the amount of memory we have - // We need one PT for every 2MB of memory that is mapped - // We can use the memory size to calculate the number of PTs we need - // We round up mem_size/2MB and then we need to add 1 as we start our memory mapping at 0x200000 - - let mem_size = usize::try_from(mem_size)?; + // - We only need to create enough PTEs to map the amount of memory we have. + // - We need one PT for every 2MB of memory that is mapped. + // - We can use the memory size to calculate the number of PTs we need. + // - We round up mem_size/2MB. let num_pages: usize = - ((mem_size + AMOUNT_OF_MEMORY_PER_PT - 1) / AMOUNT_OF_MEMORY_PER_PT) + 1; + ((memory_size + AMOUNT_OF_MEMORY_PER_PT - 1) / AMOUNT_OF_MEMORY_PER_PT) + 1; // Create num_pages PT with 512 PTEs for p in 0..num_pages { for i in 0..512 { - let offset = SandboxMemoryLayout::PT_OFFSET + (p * 4096) + (i * 8); + let offset = pt_offset + (p * 4096) + (i * 8); + // Each PTE maps a 4KB page - let val_to_write = if p == 0 { - (p << 21) as u64 | (i << 12) as u64 - } else { - let flags = match Self::get_page_flags(p, i, regions) { - Ok(region_type) => match region_type { - // TODO: We parse and load the exe according to its sections and then - // have the correct flags set rather than just marking the entire binary as executable - MemoryRegionType::Code => PAGE_PRESENT | PAGE_RW | PAGE_USER, - MemoryRegionType::Stack => { - PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX - } - #[cfg(feature = "executable_heap")] - MemoryRegionType::Heap => PAGE_PRESENT | PAGE_RW | PAGE_USER, - #[cfg(not(feature = "executable_heap"))] - MemoryRegionType::Heap => { - PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX - } - // The guard page is marked RW and User so that if it gets written to we can detect it in the host - // If/When we implement an interrupt handler for page faults in the guest then we can remove this access and handle things properly there - MemoryRegionType::GuardPage => { - PAGE_PRESENT | PAGE_RW | PAGE_USER | PAGE_NX - } - MemoryRegionType::InputData => PAGE_PRESENT | PAGE_RW | PAGE_NX, - MemoryRegionType::OutputData => PAGE_PRESENT | PAGE_RW | PAGE_NX, - MemoryRegionType::Peb => PAGE_PRESENT | PAGE_RW | PAGE_NX, - // Host Function Definitions are readonly in the guest - MemoryRegionType::HostFunctionDefinitions => PAGE_PRESENT | PAGE_NX, - MemoryRegionType::PanicContext => PAGE_PRESENT | PAGE_RW | PAGE_NX, - MemoryRegionType::GuestErrorData => { - PAGE_PRESENT | PAGE_RW | PAGE_NX - } - // Host Exception Data are readonly in the guest - MemoryRegionType::HostExceptionData => PAGE_PRESENT | PAGE_NX, - MemoryRegionType::PageTables => PAGE_PRESENT | PAGE_RW | PAGE_NX, - MemoryRegionType::KernelStack => PAGE_PRESENT | PAGE_RW | PAGE_NX, - MemoryRegionType::BootStack => PAGE_PRESENT | PAGE_RW | PAGE_NX, - }, - // If there is an error then the address isn't mapped so mark it as not present - Err(_) => 0, - }; - ((p << 21) as u64 | (i << 12) as u64) | flags - }; + let val_to_write = ((p << 21) as u64 | (i << 12) as u64) + | Self::get_page_flags(p, i, &memory_sections); shared_mem.write_u64(offset, val_to_write)?; } } Ok::<(), HyperlightError>(()) })??; - Ok(rsp) + Ok(()) + } + + /// Check if we are running in-process or not + pub(crate) fn is_in_process(&self) -> bool { + // We can recognize if we are in process by checking if the load address + // is the same as the base address of the memory layout. + self.load_addr != RawPtr::from(BASE_ADDRESS as u64) } - fn get_page_flags( - p: usize, - i: usize, - regions: &mut [MemoryRegion], - ) -> Result { + fn get_page_flags(p: usize, i: usize, sandbox_memory_sections: &SandboxMemorySections) -> u64 { let addr = (p << 21) + (i << 12); - let idx = regions.binary_search_by(|region| { - if region.guest_region.contains(&addr) { - std::cmp::Ordering::Equal - } else if region.guest_region.start > addr { - std::cmp::Ordering::Greater - } else { - std::cmp::Ordering::Less + // Find the memory section that contains this address + match sandbox_memory_sections.sections.range(..=addr).next_back() { + Some((_, section)) + if (section.page_aligned_guest_offset + ..(section.page_aligned_guest_offset + section.page_aligned_size)) + .contains(&addr) => + { + Self::translate_flags(section.flags) } - }); - - match idx { - Ok(index) => Ok(regions[index].region_type), - Err(_) => Err(new_error!("Could not find region for address: {}", addr)), + _ => 0, // If no section matches, default to not present } } - /// Get the process environment block (PEB) address assuming `start_addr` - /// is the address of the start of memory, using the given - /// `SandboxMemoryLayout` to calculate the address. - /// - /// For more details on PEBs, please see the following link: - /// - /// https://en.wikipedia.org/wiki/Process_Environment_Block - #[cfg(inprocess)] - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_in_process_peb_address(&self, start_addr: u64) -> Result { - Ok(start_addr + self.layout.get_in_process_peb_offset() as u64) + // Translates MemoryRegionFlags into x86-style page flags + fn translate_flags(flags: MemoryRegionFlags) -> u64 { + let mut page_flags = 0; + + if flags.contains(MemoryRegionFlags::READ) { + page_flags |= PAGE_PRESENT; // Mark page as present + } + + if flags.contains(MemoryRegionFlags::WRITE) { + page_flags |= PAGE_RW; // Allow read/write + } + + if flags.contains(MemoryRegionFlags::EXECUTE) { + page_flags |= PAGE_USER; // Allow user access + } else { + page_flags |= PAGE_NX; // Mark as non-executable if EXECUTE is not set + } + + page_flags } +} - /// this function will create a memory snapshot and push it onto the stack of snapshots - /// It should be used when you want to save the state of the memory, for example, when evolving a sandbox to a new state +/// Implementations for managing memory snapshots +impl SandboxMemoryManager +where + S: SharedMemory, +{ + /// Create a memory snapshot and push it onto the stack of snapshots. + /// + /// It should be used when you want to save the state of the memory—for example, when evolving a + /// sandbox to a new state. pub(crate) fn push_state(&mut self) -> Result<()> { let snapshot = SharedMemorySnapshot::new(&mut self.shared_mem)?; self.snapshots @@ -287,10 +240,11 @@ where Ok(()) } - /// this function restores a memory snapshot from the last snapshot in the list but does not pop the snapshot - /// off the stack - /// It should be used when you want to restore the state of the memory to a previous state but still want to - /// retain that state, for example after calling a function in the guest + /// Restores a memory snapshot from the last snapshot in the list but does not pop the snapshot + /// off the stack. + /// + /// It should be used when you want to restore the state of the memory to a previous state but + /// still want to retain that state, for example after calling a function in the guest. pub(crate) fn restore_state_from_last_snapshot(&mut self) -> Result<()> { let mut snapshots = self .snapshots @@ -305,9 +259,10 @@ where snapshot.restore_from_snapshot(&mut self.shared_mem) } - /// this function pops the last snapshot off the stack and restores the memory to the previous state - /// It should be used when you want to restore the state of the memory to a previous state and do not need to retain that state - /// for example when devolving a sandbox to a previous state. + /// Pops the last snapshot off the stack and restores the memory to the previous state. + /// + /// It should be used when you want to restore the state of the memory to a previous state and + /// do not need to retain that state for example when devolving a sandbox to a previous state. pub(crate) fn pop_and_restore_state_from_snapshot(&mut self) -> Result<()> { let last = self .snapshots @@ -320,204 +275,14 @@ where self.restore_state_from_last_snapshot() } - /// Sets `addr` to the correct offset in the memory referenced by - /// `shared_mem` to indicate the address of the outb pointer and context - /// for calling outb function - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn set_outb_address_and_context(&mut self, addr: u64, context: u64) -> Result<()> { - let pointer_offset = self.layout.get_outb_pointer_offset(); - let context_offset = self.layout.get_outb_context_offset(); - self.shared_mem.with_exclusivity(|excl| -> Result<()> { - excl.write_u64(pointer_offset, addr)?; - excl.write_u64(context_offset, context)?; - Ok(()) - })? + fn create_stack_guard() -> [u8; STACK_COOKIE_LEN] { + rand::random::<[u8; STACK_COOKIE_LEN]>() } } -/// Common setup functionality for the -/// `load_guest_binary_{into_memory, using_load_library}` functions -/// -/// Returns the newly created `SandboxMemoryLayout`, newly created -/// `SharedMemory`, load address as calculated by `load_addr_fn`, -/// and calculated entrypoint offset, in order. -#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] -fn load_guest_binary_common( - cfg: SandboxConfiguration, - exe_info: &ExeInfo, - load_addr_fn: F, -) -> Result<(SandboxMemoryLayout, ExclusiveSharedMemory, RawPtr, Offset)> -where - F: FnOnce(&ExclusiveSharedMemory, &SandboxMemoryLayout) -> Result, -{ - let layout = SandboxMemoryLayout::new( - cfg, - exe_info.loaded_size(), - usize::try_from(cfg.get_stack_size(exe_info))?, - usize::try_from(cfg.get_heap_size(exe_info))?, - )?; - let mut shared_mem = ExclusiveSharedMemory::new(layout.get_memory_size()?)?; - - let load_addr: RawPtr = load_addr_fn(&shared_mem, &layout)?; - - let entrypoint_offset = exe_info.entrypoint(); - - let offset = layout.get_code_pointer_offset(); - - { - // write the code pointer to shared memory - let load_addr_u64: u64 = load_addr.clone().into(); - shared_mem.write_u64(offset, load_addr_u64)?; - } - Ok((layout, shared_mem, load_addr, entrypoint_offset)) -} - +/// Implementations over `ExclusiveSharedMemory` and loading guest binaries impl SandboxMemoryManager { - /// Load the binary represented by `pe_info` into memory, ensuring - /// all necessary relocations are made prior to completing the load - /// operation, then create a new `SharedMemory` to store the new PE - /// file and a `SandboxMemoryLayout` to describe the layout of that - /// new `SharedMemory`. - /// - /// Returns the following: - /// - /// - The newly-created `SharedMemory` - /// - The `SandboxMemoryLayout` describing that `SharedMemory` - /// - The offset to the entrypoint. This value means something different - /// depending on whether we're using in-process mode or not: - /// - If we're using in-process mode, this value will be into - /// host memory - /// - If we're not running with in-memory mode, this value will be - /// into guest memory - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn load_guest_binary_into_memory( - cfg: SandboxConfiguration, - exe_info: &mut ExeInfo, - inprocess: bool, - ) -> Result { - let (layout, mut shared_mem, load_addr, entrypoint_offset) = load_guest_binary_common( - cfg, - exe_info, - |shared_mem: &ExclusiveSharedMemory, layout: &SandboxMemoryLayout| { - let addr_usize = if inprocess { - // if we're running in-process, load_addr is the absolute - // address to the start of shared memory, plus the offset to - // code - - // We also need to make the memory executable - - shared_mem.make_memory_executable()?; - shared_mem.base_addr() + layout.get_guest_code_offset() - } else { - // otherwise, we're running in a VM, so load_addr - // is the base address in a VM plus the code - // offset - layout.get_guest_code_address() - }; - RawPtr::try_from(addr_usize) - }, - )?; - - exe_info.load( - load_addr.clone().try_into()?, - &mut shared_mem.as_mut_slice()[layout.get_guest_code_offset()..], - )?; - - Ok(Self::new( - layout, - shared_mem, - inprocess, - load_addr, - entrypoint_offset, - #[cfg(target_os = "windows")] - None, - )) - } - - /// Similar to load_guest_binary_into_memory, except only works on Windows - /// and uses the - /// [`LoadLibraryA`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya) - /// function. - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn load_guest_binary_using_load_library( - cfg: SandboxConfiguration, - guest_bin_path: &str, - exe_info: &mut ExeInfo, - ) -> Result { - #[cfg(target_os = "windows")] - { - if !matches!(exe_info, ExeInfo::PE(_)) { - log_then_return!("LoadLibrary can only be used with PE files"); - } - - let lib = LoadedLib::load(guest_bin_path)?; - let (layout, shared_mem, load_addr, entrypoint_offset) = - load_guest_binary_common(cfg, exe_info, |_, _| Ok(lib.base_addr()))?; - - // make the memory executable when running in-process - shared_mem.make_memory_executable()?; - - Ok(Self::new( - layout, - shared_mem, - true, - load_addr, - entrypoint_offset, - Some(lib), - )) - } - #[cfg(target_os = "linux")] - { - let _ = (cfg, guest_bin_path, exe_info); - log_then_return!("load_guest_binary_using_load_library is only available on Windows"); - } - } - - /// Writes host function details to memory - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn write_buffer_host_function_details(&mut self, buffer: &[u8]) -> Result<()> { - let host_function_details = HostFunctionDetails::try_from(buffer).map_err(|e| { - new_error!( - "write_buffer_host_function_details: failed to convert buffer to HostFunctionDetails: {}", - e - ) - })?; - - let host_function_call_buffer: Vec = (&host_function_details).try_into().map_err(|_| { - new_error!( - "write_buffer_host_function_details: failed to convert HostFunctionDetails to Vec" - ) - })?; - - let buffer_size = { - let size_u64 = self - .shared_mem - .read_u64(self.layout.get_host_function_definitions_size_offset())?; - usize::try_from(size_u64) - }?; - - if host_function_call_buffer.len() > buffer_size { - log_then_return!( - "Host Function Details buffer is too big for the host_function_definitions buffer" - ); - } - - self.shared_mem.copy_from_slice( - host_function_call_buffer.as_slice(), - self.layout.host_function_definitions_buffer_offset, - )?; - Ok(()) - } - - /// Set the stack guard to `cookie` using `layout` to calculate - /// its location and `shared_mem` to write it. - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn set_stack_guard(&mut self, cookie: &[u8; STACK_COOKIE_LEN]) -> Result<()> { - let stack_offset = self.layout.get_top_of_user_stack_offset(); - self.shared_mem.copy_from_slice(cookie, stack_offset) - } - - /// Wraps ExclusiveSharedMemory::build + /// Wraps `ExclusiveSharedMemory::build`, giving you access to Host and Guest shared memories. pub fn build( self, ) -> ( @@ -528,21 +293,23 @@ impl SandboxMemoryManager { ( SandboxMemoryManager { shared_mem: hshm, - layout: self.layout, - inprocess: self.inprocess, load_addr: self.load_addr.clone(), entrypoint_offset: self.entrypoint_offset, + memory_sections: self.memory_sections.clone(), snapshots: Arc::new(Mutex::new(Vec::new())), + init_rsp: self.init_rsp, + stack_guard: self.stack_guard, #[cfg(target_os = "windows")] _lib: self._lib, }, SandboxMemoryManager { shared_mem: gshm, - layout: self.layout, - inprocess: self.inprocess, load_addr: self.load_addr.clone(), entrypoint_offset: self.entrypoint_offset, + memory_sections: self.memory_sections, snapshots: Arc::new(Mutex::new(Vec::new())), + init_rsp: self.init_rsp, + stack_guard: self.stack_guard, #[cfg(target_os = "windows")] _lib: None, }, @@ -564,40 +331,26 @@ impl SandboxMemoryManager { /// documentation at the bottom `set_stack_guard` for description /// of why it isn't. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn check_stack_guard(&self, cookie: [u8; STACK_COOKIE_LEN]) -> Result { - let offset = self.layout.get_top_of_user_stack_offset(); + pub(crate) fn check_stack_guard(&self) -> Result { + let cookie = self.stack_guard; + // There's a stack guard right before the custom guest memory section + let offset = self + .memory_sections + .get_custom_guest_memory_section_offset(); let test_cookie: [u8; STACK_COOKIE_LEN] = self.shared_mem.read(offset)?; let cmp_res = cookie.iter().cmp(test_cookie.iter()); Ok(cmp_res == Ordering::Equal) } - /// Get the address of the dispatch function in memory - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_pointer_to_dispatch_function(&self) -> Result { - let guest_dispatch_function_ptr = self - .shared_mem - .read::(self.layout.get_dispatch_function_pointer_offset())?; - - // This pointer is written by the guest library but is accessible to - // the guest engine so we should bounds check it before we return it. - // - // When executing with in-hypervisor mode, there is no danger from - // the guest manipulating this memory location because the only - // addresses that are valid are in its own address space. - // - // When executing in-process, manipulating this pointer could cause the - // host to execute arbitrary functions. - let guest_ptr = GuestPtr::try_from(RawPtr::from(guest_dispatch_function_ptr))?; - guest_ptr.absolute() - } - /// Reads a host function call from memory #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn get_host_function_call(&mut self) -> Result { - self.shared_mem.try_pop_buffer_into::( - self.layout.output_data_buffer_offset, - self.layout.sandbox_memory_config.get_output_data_size(), - ) + let (ptr, size) = self + .memory_sections + .read_hyperlight_peb()? + .get_output_data_guest_region(); + self.shared_mem + .try_pop_buffer_into::(ptr as usize, size as usize) } /// Writes a function call result to memory @@ -608,16 +361,28 @@ impl SandboxMemoryManager { "write_response_from_host_method_call: failed to convert ReturnValue to Vec" ) })?; + + let (ptr, size) = self + .memory_sections + .read_hyperlight_peb()? + .get_input_data_guest_region(); + self.shared_mem.push_buffer( - self.layout.input_data_buffer_offset, - self.layout.sandbox_memory_config.get_input_data_size(), + ptr as usize, + size as usize, function_call_ret_val_buffer.as_slice(), ) } /// Writes a guest function call to memory #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn write_guest_function_call(&mut self, buffer: &[u8]) -> Result<()> { + pub(crate) fn write_guest_function_call( + &mut self, + input_data_region: (u64, u64), + buffer: &[u8], + ) -> Result<()> { + let (ptr, size) = input_data_region; + validate_guest_function_call_buffer(buffer).map_err(|e| { new_error!( "Guest function call buffer validation failed: {}", @@ -625,342 +390,47 @@ impl SandboxMemoryManager { ) })?; - self.shared_mem.push_buffer( - self.layout.input_data_buffer_offset, - self.layout.sandbox_memory_config.get_input_data_size(), - buffer, - ) - } - - /// Reads a function call result from memory - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_function_call_result(&mut self) -> Result { - self.shared_mem.try_pop_buffer_into::( - self.layout.output_data_buffer_offset, - self.layout.sandbox_memory_config.get_output_data_size(), - ) - } - - /// Read guest log data from the `SharedMemory` contained within `self` - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn read_guest_log_data(&mut self) -> Result { - self.shared_mem.try_pop_buffer_into::( - self.layout.output_data_buffer_offset, - self.layout.sandbox_memory_config.get_output_data_size(), - ) - } - - /// Get the length of the host exception - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - fn get_host_error_length(&self) -> Result { - let offset = self.layout.get_host_exception_offset(); - // The host exception field is expected to contain a 32-bit length followed by the exception data. - self.shared_mem.read::(offset) - } - - /// Get a bool indicating if there is a host error - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - fn has_host_error(&self) -> Result { - let offset = self.layout.get_host_exception_offset(); - // The host exception field is expected to contain a 32-bit length followed by the exception data. - let len = self.shared_mem.read::(offset)?; - Ok(len != 0) - } - - /// Get the error data that was written by the Hyperlight Host - /// Returns a `Result` containing 'Unit' or an error.Error - /// Writes the exception data to the buffer at `exception_data_ptr`. - /// - /// TODO: have this function return a Vec instead of requiring - /// the user pass in a slice of the same length as returned by - /// self.get_host_error_length() - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - fn get_host_error_data(&self, exception_data_slc: &mut [u8]) -> Result<()> { - let offset = self.layout.get_host_exception_offset(); - let len = self.get_host_error_length()?; - - let exception_data_slc_len = exception_data_slc.len(); - if exception_data_slc_len != len as usize { - log_then_return!(ExceptionDataLengthIncorrect(len, exception_data_slc_len)); - } - // The host exception field is expected to contain a 32-bit length followed by the exception data. self.shared_mem - .copy_to_slice(exception_data_slc, offset + size_of::())?; - Ok(()) + .push_buffer(ptr as usize, size as usize, buffer) } - /// Look for a `HyperlightError` generated by the host, and return - /// an `Ok(Some(the_error))` if we succeeded in looking for one, and - /// it was found. Return `Ok(None)` if we succeeded in looking for - /// one and it wasn't found. Return an `Err` if we did not succeed - /// in looking for one. + /// Reads a function call result from memory #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_host_error(&self) -> Result> { - if self.has_host_error()? { - let host_err_len = { - let len_i32 = self.get_host_error_length()?; - usize::try_from(len_i32) - }?; - // create a Vec of length host_err_len. - // it's important we set the length, rather than just - // the capacity, because self.get_host_error_data ensures - // the length of the vec matches the return value of - // self.get_host_error_length() - let mut host_err_data: Vec = vec![0; host_err_len]; - self.get_host_error_data(&mut host_err_data)?; - let host_err_json = from_utf8(&host_err_data).map_err(UTF8SliceConversionFailure)?; - let host_err: HyperlightHostError = - from_str(host_err_json).map_err(JsonConversionFailure)?; - Ok(Some(host_err)) - } else { - Ok(None) - } - } + pub(crate) fn get_guest_function_call_result( + &mut self, + output_data_region: (u64, u64), + ) -> Result { + let (output_ptr, output_size) = output_data_region; - /// Get the guest error data - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_error(&self) -> Result { - // get memory buffer max size - let err_buffer_size_offset = self.layout.get_guest_error_buffer_size_offset(); - let max_err_buffer_size = self.shared_mem.read::(err_buffer_size_offset)?; - - // get guest error from layout and shared mem - let mut guest_error_buffer = vec![b'0'; usize::try_from(max_err_buffer_size)?]; - let err_msg_offset = self.layout.guest_error_buffer_offset; self.shared_mem - .copy_to_slice(guest_error_buffer.as_mut_slice(), err_msg_offset)?; - GuestError::try_from(guest_error_buffer.as_slice()).map_err(|e| { - new_error!( - "get_guest_error: failed to convert buffer to GuestError: {}", - e - ) - }) + .try_pop_buffer_into::(output_ptr as usize, output_size as usize) } - /// This function writes an error to guest memory and is intended to be - /// used when the host's outb handler code raises an error. + /// Read guest log data from the `SharedMemory` contained within `self` #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - fn write_outb_error( - &mut self, - guest_error_msg: &[u8], - host_exception_data: &[u8], - ) -> Result<()> { - let message = String::from_utf8(guest_error_msg.to_owned())?; - let ge = GuestError::new(ErrorCode::OutbError, message); - - let guest_error_buffer: Vec = (&ge) - .try_into() - .map_err(|_| new_error!("write_outb_error: failed to convert GuestError to Vec"))?; - - let err_buffer_size_offset = self.layout.get_guest_error_buffer_size_offset(); - let max_err_buffer_size = self.shared_mem.read::(err_buffer_size_offset)?; - - if guest_error_buffer.len() as u64 > max_err_buffer_size { - log_then_return!("The guest error message is too large to fit in the shared memory"); - } - self.shared_mem.copy_from_slice( - guest_error_buffer.as_slice(), - self.layout.guest_error_buffer_offset, - )?; - - let host_exception_offset = self.layout.get_host_exception_offset(); - let host_exception_size_offset = self.layout.get_host_exception_size_offset(); - let max_host_exception_size = { - let size_u64 = self.shared_mem.read::(host_exception_size_offset)?; - usize::try_from(size_u64) - }?; - - // First four bytes of host exception are length - - if host_exception_data.len() > max_host_exception_size - size_of::() { - log_then_return!(ExceptionMessageTooBig( - host_exception_data.len(), - max_host_exception_size - size_of::() - )); - } - + pub(crate) fn read_guest_log_data(&mut self) -> Result { + let (ptr, size) = self + .memory_sections + .read_hyperlight_peb()? + .get_output_data_guest_region(); self.shared_mem - .write::(host_exception_offset, host_exception_data.len() as i32)?; - self.shared_mem.copy_from_slice( - host_exception_data, - host_exception_offset + size_of::(), - )?; - - Ok(()) + .try_pop_buffer_into::(ptr as usize, size as usize) } /// Read guest panic data from the `SharedMemory` contained within `self` #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub fn read_guest_panic_context_data(&self) -> Result> { - let offset = self.layout.get_guest_panic_context_buffer_offset(); - let buffer_size = { - let size_u64 = self - .shared_mem - .read::(self.layout.get_guest_panic_context_size_offset())?; - usize::try_from(size_u64) - }?; - let mut vec_out = vec![0; buffer_size]; + let offset = self + .memory_sections + .read_hyperlight_peb()? + .get_guest_panic_context_guest_address() as usize; + let size = self + .memory_sections + .read_hyperlight_peb()? + .get_guest_panic_context_size() as usize; + let mut vec_out = vec![0; size]; self.shared_mem .copy_to_slice(vec_out.as_mut_slice(), offset)?; Ok(vec_out) } } - -#[cfg(test)] -mod tests { - use hyperlight_testing::rust_guest_as_pathbuf; - use serde_json::to_string; - #[cfg(all(target_os = "windows", inprocess))] - use serial_test::serial; - - use super::SandboxMemoryManager; - use crate::error::HyperlightHostError; - use crate::mem::exe::ExeInfo; - use crate::mem::layout::SandboxMemoryLayout; - use crate::mem::ptr::RawPtr; - use crate::mem::ptr_offset::Offset; - use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory}; - use crate::sandbox::SandboxConfiguration; - use crate::testing::bytes_for_path; - - #[test] - fn load_guest_binary_common() { - let guests = vec![ - rust_guest_as_pathbuf("simpleguest"), - rust_guest_as_pathbuf("callbackguest"), - ]; - for guest in guests { - let guest_bytes = bytes_for_path(guest).unwrap(); - let exe_info = ExeInfo::from_buf(guest_bytes.as_slice()).unwrap(); - let stack_size_override = 0x3000; - let heap_size_override = 0x10000; - let mut cfg = SandboxConfiguration::default(); - cfg.set_stack_size(stack_size_override); - cfg.set_heap_size(heap_size_override); - let (layout, shared_mem, _, _) = - super::load_guest_binary_common(cfg, &exe_info, |_, _| Ok(RawPtr::from(100))) - .unwrap(); - assert_eq!( - stack_size_override, - u64::try_from(layout.stack_size).unwrap() - ); - assert_eq!(heap_size_override, u64::try_from(layout.heap_size).unwrap()); - assert_eq!(layout.get_memory_size().unwrap(), shared_mem.mem_size()); - } - } - - #[cfg(all(target_os = "windows", inprocess))] - #[test] - #[serial] - fn load_guest_binary_using_load_library() { - use hyperlight_testing::rust_guest_as_pathbuf; - - use crate::mem::mgr::SandboxMemoryManager; - - let cfg = SandboxConfiguration::default(); - let guest_pe_path = rust_guest_as_pathbuf("simpleguest.exe"); - let guest_pe_bytes = bytes_for_path(guest_pe_path.clone()).unwrap(); - let mut pe_info = ExeInfo::from_buf(guest_pe_bytes.as_slice()).unwrap(); - let _ = SandboxMemoryManager::load_guest_binary_using_load_library( - cfg, - guest_pe_path.to_str().unwrap(), - &mut pe_info, - ) - .unwrap(); - - let guest_elf_path = rust_guest_as_pathbuf("simpleguest"); - let guest_elf_bytes = bytes_for_path(guest_elf_path.clone()).unwrap(); - let mut elf_info = ExeInfo::from_buf(guest_elf_bytes.as_slice()).unwrap(); - - let res = SandboxMemoryManager::load_guest_binary_using_load_library( - cfg, - guest_elf_path.to_str().unwrap(), - &mut elf_info, - ); - - match res { - Ok(_) => { - panic!("loadlib with elf should fail"); - } - Err(err) => { - assert!(err - .to_string() - .contains("LoadLibrary can only be used with PE files")); - } - } - } - - /// Don't write a host error, try to read it back, and verify we - /// successfully do the read but get no error back - #[test] - fn get_host_error_none() { - let cfg = SandboxConfiguration::default(); - let layout = SandboxMemoryLayout::new(cfg, 0x10000, 0x10000, 0x10000).unwrap(); - let mut eshm = ExclusiveSharedMemory::new(layout.get_memory_size().unwrap()).unwrap(); - let mem_size = eshm.mem_size(); - layout - .write( - &mut eshm, - SandboxMemoryLayout::BASE_ADDRESS, - mem_size, - false, - ) - .unwrap(); - let emgr = SandboxMemoryManager::new( - layout, - eshm, - false, - RawPtr::from(0), - Offset::from(0), - #[cfg(target_os = "windows")] - None, - ); - let (hmgr, _) = emgr.build(); - assert_eq!(None, hmgr.get_host_error().unwrap()); - } - - /// write a host error to shared memory, then try to read it back out - #[test] - fn round_trip_host_error() { - let cfg = SandboxConfiguration::default(); - let layout = SandboxMemoryLayout::new(cfg, 0x10000, 0x10000, 0x10000).unwrap(); - let mem_size = layout.get_memory_size().unwrap(); - // write a host error and then try to read it back - let mut eshm = ExclusiveSharedMemory::new(mem_size).unwrap(); - layout - .write( - &mut eshm, - SandboxMemoryLayout::BASE_ADDRESS, - mem_size, - false, - ) - .unwrap(); - let emgr = SandboxMemoryManager::new( - layout, - eshm, - false, - RawPtr::from(0), - Offset::from(0), - #[cfg(target_os = "windows")] - None, - ); - let (mut hmgr, _) = emgr.build(); - let err = HyperlightHostError { - message: "test message".to_string(), - source: "rust test".to_string(), - }; - let err_json_bytes = { - let str = to_string(&err).unwrap(); - str.into_bytes() - }; - let err_json_msg = "test error message".to_string().into_bytes(); - hmgr.write_outb_error(&err_json_msg, &err_json_bytes) - .unwrap(); - - let host_err_opt = hmgr - .get_host_error() - .expect("get_host_err should return an Ok"); - assert!(host_err_opt.is_some()); - assert_eq!(err, host_err_opt.unwrap()); - } -} diff --git a/src/hyperlight_host/src/mem/mod.rs b/src/hyperlight_host/src/mem/mod.rs index 3e6c2aa54..130d059fb 100644 --- a/src/hyperlight_host/src/mem/mod.rs +++ b/src/hyperlight_host/src/mem/mod.rs @@ -21,14 +21,10 @@ pub(crate) mod custom_drop; pub(crate) mod elf; /// A generic wrapper for executable files (PE, ELF, etc) pub(crate) mod exe; -/// Functionality to establish a sandbox's memory layout. -pub mod layout; /// Safe wrapper around an HINSTANCE created by the windows /// `LoadLibrary` call #[cfg(target_os = "windows")] pub(super) mod loaded_lib; -/// memory regions to be mapped inside a vm -pub mod memory_region; /// Functionality that wraps a `SandboxMemoryLayout` and a /// `SandboxMemoryConfig` to mutate a sandbox's memory as necessary. pub mod mgr; diff --git a/src/hyperlight_host/src/mem/ptr.rs b/src/hyperlight_host/src/mem/ptr.rs index bacab051c..3eab15414 100644 --- a/src/hyperlight_host/src/mem/ptr.rs +++ b/src/hyperlight_host/src/mem/ptr.rs @@ -27,7 +27,8 @@ use crate::Result; /// /// Use this type to distinguish between an offset and a raw pointer #[derive(Debug, Clone, Eq, PartialEq)] -pub struct RawPtr(u64); +// TODO(see #429): we should probably rethink these APIs. +pub struct RawPtr(pub u64); impl From for RawPtr { #[instrument(skip_all, parent = Span::current(), level= "Trace")] @@ -227,27 +228,16 @@ impl TryFrom> for usize { #[cfg(test)] mod tests { use super::{GuestPtr, RawPtr}; - use crate::mem::layout::SandboxMemoryLayout; + use crate::sandbox::sandbox_builder::BASE_ADDRESS; + const OFFSET: u64 = 1; #[test] fn ptr_basic_ops() { { - let raw_guest_ptr = RawPtr(OFFSET + SandboxMemoryLayout::BASE_ADDRESS as u64); + let raw_guest_ptr = RawPtr(OFFSET + BASE_ADDRESS as u64); let guest_ptr = GuestPtr::try_from(raw_guest_ptr).unwrap(); - assert_eq!( - OFFSET + SandboxMemoryLayout::BASE_ADDRESS as u64, - guest_ptr.absolute().unwrap() - ); - } - } - - #[test] - fn ptr_fail() { - { - let raw_guest_ptr = RawPtr(SandboxMemoryLayout::BASE_ADDRESS as u64 - 1); - let guest_ptr = GuestPtr::try_from(raw_guest_ptr); - assert!(guest_ptr.is_err()); + assert_eq!(OFFSET + BASE_ADDRESS as u64, guest_ptr.absolute().unwrap()); } } } diff --git a/src/hyperlight_host/src/mem/ptr_addr_space.rs b/src/hyperlight_host/src/mem/ptr_addr_space.rs index 3f84549a7..6c33d7aa0 100644 --- a/src/hyperlight_host/src/mem/ptr_addr_space.rs +++ b/src/hyperlight_host/src/mem/ptr_addr_space.rs @@ -16,7 +16,7 @@ limitations under the License. use tracing::{instrument, Span}; -use super::layout::SandboxMemoryLayout; +use crate::sandbox::sandbox_builder::BASE_ADDRESS; use crate::Result; /// A representation of a specific address space @@ -32,7 +32,7 @@ impl GuestAddressSpace { /// Create a new instance of a `GuestAddressSpace` #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(super) fn new() -> Result { - let base_addr = u64::try_from(SandboxMemoryLayout::BASE_ADDRESS)?; + let base_addr = u64::try_from(BASE_ADDRESS)?; Ok(Self(base_addr)) } } @@ -46,11 +46,11 @@ impl AddressSpace for GuestAddressSpace { #[cfg(test)] mod tests { use super::{AddressSpace, GuestAddressSpace}; - use crate::mem::layout::SandboxMemoryLayout; + use crate::sandbox::sandbox_builder::BASE_ADDRESS; #[test] fn guest_addr_space_base() { let space = GuestAddressSpace::new().unwrap(); - assert_eq!(SandboxMemoryLayout::BASE_ADDRESS as u64, space.base()); + assert_eq!(BASE_ADDRESS as u64, space.base()); } } diff --git a/src/hyperlight_host/src/mem/shared_mem.rs b/src/hyperlight_host/src/mem/shared_mem.rs index fe6ba8dd9..b692a8241 100644 --- a/src/hyperlight_host/src/mem/shared_mem.rs +++ b/src/hyperlight_host/src/mem/shared_mem.rs @@ -21,7 +21,7 @@ use std::io::Error; use std::ptr::null_mut; use std::sync::{Arc, RwLock}; -use hyperlight_common::mem::PAGE_SIZE_USIZE; +use hyperlight_common::PAGE_SIZE; use tracing::{instrument, Span}; #[cfg(target_os = "windows")] use windows::core::PCSTR; @@ -325,23 +325,23 @@ impl ExclusiveSharedMemory { } let total_size = min_size_bytes - .checked_add(2 * PAGE_SIZE_USIZE) // guard page around the memory + .checked_add(2 * PAGE_SIZE) // guard page around the memory .ok_or_else(|| new_error!("Memory required for sandbox exceeded usize::MAX"))?; - if total_size % PAGE_SIZE_USIZE != 0 { + if total_size % PAGE_SIZE != 0 { return Err(new_error!( "shared memory must be a multiple of {}", - PAGE_SIZE_USIZE + PAGE_SIZE )); } - // usize and isize are guaranteed to be the same size, and - // isize::MAX should be positive, so this cast should be safe. + // `usize` and `isize` are guaranteed to be the same size, and + // `isize::MAX` should be positive, so this cast should be safe. if total_size > isize::MAX as usize { return Err(MemoryRequestTooBig(total_size, isize::MAX as usize)); } - // allocate the memory + // Allocate the memory let addr = unsafe { mmap( null_mut(), @@ -356,16 +356,15 @@ impl ExclusiveSharedMemory { log_then_return!(MmapFailed(Error::last_os_error().raw_os_error())); } - // protect the guard pages - - let res = unsafe { mprotect(addr, PAGE_SIZE_USIZE, PROT_NONE) }; + // Protect the guard pages + let res = unsafe { mprotect(addr, PAGE_SIZE, PROT_NONE) }; if res != 0 { return Err(MprotectFailed(Error::last_os_error().raw_os_error())); } let res = unsafe { mprotect( - (addr as *const u8).add(total_size - PAGE_SIZE_USIZE) as *mut c_void, - PAGE_SIZE_USIZE, + (addr as *const u8).add(total_size - PAGE_SIZE) as *mut c_void, + PAGE_SIZE, PROT_NONE, ) }; @@ -402,13 +401,13 @@ impl ExclusiveSharedMemory { } let total_size = min_size_bytes - .checked_add(2 * PAGE_SIZE_USIZE) + .checked_add(2 * PAGE_SIZE) .ok_or_else(|| new_error!("Memory required for sandbox exceeded {}", usize::MAX))?; - if total_size % PAGE_SIZE_USIZE != 0 { + if total_size % PAGE_SIZE != 0 { return Err(new_error!( "shared memory must be a multiple of {}", - PAGE_SIZE_USIZE + PAGE_SIZE )); } @@ -474,7 +473,7 @@ impl ExclusiveSharedMemory { if let Err(e) = unsafe { VirtualProtect( first_guard_page_start, - PAGE_SIZE_USIZE, + PAGE_SIZE, PAGE_NOACCESS, &mut unused_out_old_prot_flags, ) @@ -482,11 +481,11 @@ impl ExclusiveSharedMemory { log_then_return!(WindowsAPIError(e.clone())); } - let last_guard_page_start = unsafe { addr.Value.add(total_size - PAGE_SIZE_USIZE) }; + let last_guard_page_start = unsafe { addr.Value.add(total_size - PAGE_SIZE) }; if let Err(e) = unsafe { VirtualProtect( last_guard_page_start, - PAGE_SIZE_USIZE, + PAGE_SIZE, PAGE_NOACCESS, &mut unused_out_old_prot_flags, ) @@ -512,7 +511,7 @@ impl ExclusiveSharedMemory { }) } - pub(super) fn make_memory_executable(&self) -> Result<()> { + pub(crate) fn make_memory_executable(&self) -> Result<()> { #[cfg(target_os = "windows")] { let mut _old_flags = PAGE_PROTECTION_FLAGS::default(); @@ -587,7 +586,7 @@ impl ExclusiveSharedMemory { /// /// This is ensured by a check in ::new() #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn as_mut_slice<'a>(&'a mut self) -> &'a mut [u8] { + pub(crate) fn as_mut_slice<'a>(&'a mut self) -> &'a mut [u8] { unsafe { std::slice::from_raw_parts_mut(self.base_ptr(), self.mem_size()) } } @@ -689,12 +688,35 @@ pub trait SharedMemory { /// Return a readonly reference to the host mapping backing this SharedMemory fn region(&self) -> &HostMapping; + // TODO(see #430) consider how the three functions below fit into the bigger + // picture w/ implementors of this trait having similar fxns. + /// Return data as mut slice + fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { std::slice::from_raw_parts_mut(self.base_ptr(), self.mem_size()) } + } + + /// Copies data into shared memory from a slice starting at offset + fn copy_from_slice(&mut self, src: &[u8], offset: usize) -> Result<()> { + let data = self.as_mut_slice(); + bounds_check!(offset, src.len(), data.len()); + data[offset..offset + src.len()].copy_from_slice(src); + Ok(()) + } + + /// Copies data from shared memory into a slice starting at offset + fn copy_to_slice(&mut self, dst: &mut [u8], offset: usize) -> Result<()> { + let data = self.as_mut_slice(); + bounds_check!(offset, dst.len(), data.len()); + dst.copy_from_slice(&data[offset..offset + dst.len()]); + Ok(()) + } + /// Return the base address of the host mapping of this /// region. Following the general Rust philosophy, this does not /// need to be marked as `unsafe` because doing anything with this /// pointer itself requires `unsafe`. fn base_addr(&self) -> usize { - self.region().ptr as usize + PAGE_SIZE_USIZE + self.region().ptr as usize + PAGE_SIZE } /// Return the base address of the host mapping of this region as @@ -710,7 +732,7 @@ pub trait SharedMemory { /// guard pages. #[instrument(skip_all, parent = Span::current(), level= "Trace")] fn mem_size(&self) -> usize { - self.region().size - 2 * PAGE_SIZE_USIZE + self.region().size - 2 * PAGE_SIZE } /// Return the raw base address of the host mapping, including the @@ -888,7 +910,13 @@ impl HostSharedMemory { buffer_size: usize, data: &[u8], ) -> Result<()> { - let stack_pointer_rel = self.read::(buffer_start_offset)? as usize; + let mut stack_pointer_rel = self.read::(buffer_start_offset)? as usize; + + // If stack_pointer_rel is 0, it means this is our first read, and we should set it to 8. + // This is because the first 8 bytes are used to store the offset to the top of the stack. + if stack_pointer_rel == 0 { + stack_pointer_rel = 8; + } let buffer_size_u64: u64 = buffer_size.try_into()?; if stack_pointer_rel > buffer_size || stack_pointer_rel < 8 { @@ -928,7 +956,7 @@ impl HostSharedMemory { Ok(()) } - /// Pops the given given buffer into a `T` and returns it. + /// Pops the given buffer into a `T` and returns it. /// NOTE! the data must be a size-prefixed flatbuffer, and /// buffer_start_offset must point to the beginning of the buffer #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] @@ -1013,300 +1041,301 @@ impl SharedMemory for HostSharedMemory { } } -#[cfg(test)] -mod tests { - use hyperlight_common::mem::PAGE_SIZE_USIZE; - use proptest::prelude::*; - - use super::{ExclusiveSharedMemory, HostSharedMemory, SharedMemory}; - use crate::mem::shared_mem_tests::read_write_test_suite; - use crate::Result; - - #[test] - fn fill() { - let mem_size: usize = 4096; - let eshm = ExclusiveSharedMemory::new(mem_size).unwrap(); - let (mut hshm, _) = eshm.build(); - - hshm.fill(1, 0, 1024).unwrap(); - hshm.fill(2, 1024, 1024).unwrap(); - hshm.fill(3, 2048, 1024).unwrap(); - hshm.fill(4, 3072, 1024).unwrap(); - - let vec = hshm - .with_exclusivity(|e| e.copy_all_to_vec().unwrap()) - .unwrap(); - - assert!(vec[0..1024].iter().all(|&x| x == 1)); - assert!(vec[1024..2048].iter().all(|&x| x == 2)); - assert!(vec[2048..3072].iter().all(|&x| x == 3)); - assert!(vec[3072..4096].iter().all(|&x| x == 4)); - - hshm.fill(5, 0, 4096).unwrap(); - - let vec2 = hshm - .with_exclusivity(|e| e.copy_all_to_vec().unwrap()) - .unwrap(); - assert!(vec2.iter().all(|&x| x == 5)); - - assert!(hshm.fill(0, 0, mem_size + 1).is_err()); - assert!(hshm.fill(0, mem_size, 1).is_err()); - } - - #[test] - fn copy_into_from() -> Result<()> { - let mem_size: usize = 4096; - let vec_len = 10; - let eshm = ExclusiveSharedMemory::new(mem_size)?; - let (hshm, _) = eshm.build(); - let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - // write the value to the memory at the beginning. - hshm.copy_from_slice(&vec, 0)?; - - let mut vec2 = vec![0; vec_len]; - // read the value back from the memory at the beginning. - hshm.copy_to_slice(vec2.as_mut_slice(), 0)?; - assert_eq!(vec, vec2); - - let offset = mem_size - vec.len(); - // write the value to the memory at the end. - hshm.copy_from_slice(&vec, offset)?; - - let mut vec3 = vec![0; vec_len]; - // read the value back from the memory at the end. - hshm.copy_to_slice(&mut vec3, offset)?; - assert_eq!(vec, vec3); - - let offset = mem_size / 2; - // write the value to the memory at the middle. - hshm.copy_from_slice(&vec, offset)?; - - let mut vec4 = vec![0; vec_len]; - // read the value back from the memory at the middle. - hshm.copy_to_slice(&mut vec4, offset)?; - assert_eq!(vec, vec4); - - // try and read a value from an offset that is beyond the end of the memory. - let mut vec5 = vec![0; vec_len]; - assert!(hshm.copy_to_slice(&mut vec5, mem_size).is_err()); - - // try and write a value to an offset that is beyond the end of the memory. - assert!(hshm.copy_from_slice(&vec5, mem_size).is_err()); - - // try and read a value from an offset that is too large. - let mut vec6 = vec![0; vec_len]; - assert!(hshm.copy_to_slice(&mut vec6, mem_size * 2).is_err()); - - // try and write a value to an offset that is too large. - assert!(hshm.copy_from_slice(&vec6, mem_size * 2).is_err()); - - // try and read a value that is too large. - let mut vec7 = vec![0; mem_size * 2]; - assert!(hshm.copy_to_slice(&mut vec7, 0).is_err()); - - // try and write a value that is too large. - assert!(hshm.copy_from_slice(&vec7, 0).is_err()); - - Ok(()) - } - - proptest! { - #[test] - fn read_write_i32(val in -0x1000_i32..0x1000_i32) { - read_write_test_suite( - val, - ExclusiveSharedMemory::new, - Box::new(ExclusiveSharedMemory::read_i32), - Box::new(ExclusiveSharedMemory::write_i32), - ) - .unwrap(); - read_write_test_suite( - val, - |s| { - let e = ExclusiveSharedMemory::new(s)?; - let (h, _) = e.build(); - Ok(h) - }, - Box::new(HostSharedMemory::read::), - Box::new(|h, o, v| h.write::(o, v)), - ) - .unwrap(); - } - } - - #[test] - fn alloc_fail() { - let gm = ExclusiveSharedMemory::new(0); - assert!(gm.is_err()); - let gm = ExclusiveSharedMemory::new(usize::MAX); - assert!(gm.is_err()); - } - - #[test] - fn clone() { - let eshm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap(); - let (hshm1, _) = eshm.build(); - let hshm2 = hshm1.clone(); - - // after hshm1 is cloned, hshm1 and hshm2 should have identical - // memory sizes and pointers. - assert_eq!(hshm1.mem_size(), hshm2.mem_size()); - assert_eq!(hshm1.base_addr(), hshm2.base_addr()); - - // we should be able to copy a byte array into both hshm1 and hshm2, - // and have both changes be reflected in all clones - hshm1.copy_from_slice(b"a", 0).unwrap(); - hshm2.copy_from_slice(b"b", 1).unwrap(); - - // at this point, both hshm1 and hshm2 should have - // offset 0 = 'a', offset 1 = 'b' - for (raw_offset, expected) in &[(0, b'a'), (1, b'b')] { - assert_eq!(hshm1.read::(*raw_offset).unwrap(), *expected); - assert_eq!(hshm2.read::(*raw_offset).unwrap(), *expected); - } - - // after we drop hshm1, hshm2 should still exist, be valid, - // and have all contents from before hshm1 was dropped - drop(hshm1); - - // at this point, hshm2 should still have offset 0 = 'a', offset 1 = 'b' - for (raw_offset, expected) in &[(0, b'a'), (1, b'b')] { - assert_eq!(hshm2.read::(*raw_offset).unwrap(), *expected); - } - hshm2.copy_from_slice(b"c", 2).unwrap(); - assert_eq!(hshm2.read::(2).unwrap(), b'c'); - drop(hshm2); - } - - #[test] - fn copy_all_to_vec() { - let mut data = vec![b'a', b'b', b'c']; - data.resize(4096, 0); - let mut eshm = ExclusiveSharedMemory::new(data.len()).unwrap(); - eshm.copy_from_slice(data.as_slice(), 0).unwrap(); - let ret_vec = eshm.copy_all_to_vec().unwrap(); - assert_eq!(data, ret_vec); - } - - /// A test to ensure that, if a `SharedMem` instance is cloned - /// and _all_ clones are dropped, the memory region will no longer - /// be valid. - /// - /// This test is ignored because it is incompatible with other tests as - /// they may be allocating memory at the same time. - /// - /// Marking this test as ignored means that running `cargo test` will not - /// run it. This feature will allow a developer who runs that command - /// from their workstation to be successful without needing to know about - /// test interdependencies. This test will, however, be run explicitly as a - /// part of the CI pipeline. - #[test] - #[ignore] - #[cfg(target_os = "linux")] - fn test_drop() { - use proc_maps::maps_contain_addr; - - let pid = std::process::id(); - - let eshm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap(); - let (hshm1, gshm) = eshm.build(); - let hshm2 = hshm1.clone(); - let addr = hshm1.raw_ptr() as usize; - - // ensure the address is in the process's virtual memory - let maps_before_drop = proc_maps::get_process_maps(pid.try_into().unwrap()).unwrap(); - assert!( - maps_contain_addr(addr, &maps_before_drop), - "shared memory address {:#x} was not found in process map, but should be", - addr, - ); - // drop both shared memory instances, which should result - // in freeing the memory region - drop(hshm1); - drop(hshm2); - drop(gshm); - - let maps_after_drop = proc_maps::get_process_maps(pid.try_into().unwrap()).unwrap(); - // now, ensure the address is not in the process's virtual memory - assert!( - !maps_contain_addr(addr, &maps_after_drop), - "shared memory address {:#x} was found in the process map, but shouldn't be", - addr - ); - } - - #[cfg(target_os = "linux")] - mod guard_page_crash_test { - use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory}; - - const TEST_EXIT_CODE: u8 = 211; // an uncommon exit code, used for testing purposes - - /// hook sigsegv to exit with status code, to make it testable, rather than have it exit from a signal - /// NOTE: We CANNOT panic!() in the handler, and make the tests #[should_panic], because - /// the test harness process will crash anyway after the test passes - fn setup_signal_handler() { - unsafe { - signal_hook_registry::register_signal_unchecked(libc::SIGSEGV, || { - std::process::exit(TEST_EXIT_CODE.into()); - }) - .unwrap(); - } - } - - #[test] - #[ignore] // this test is ignored because it will crash the running process - fn read() { - setup_signal_handler(); - - let eshm = ExclusiveSharedMemory::new(4096).unwrap(); - let (hshm, _) = eshm.build(); - let guard_page_ptr = hshm.raw_ptr(); - unsafe { std::ptr::read_volatile(guard_page_ptr) }; - } - - #[test] - #[ignore] // this test is ignored because it will crash the running process - fn write() { - setup_signal_handler(); - - let eshm = ExclusiveSharedMemory::new(4096).unwrap(); - let (hshm, _) = eshm.build(); - let guard_page_ptr = hshm.raw_ptr(); - unsafe { std::ptr::write_volatile(guard_page_ptr, 0u8) }; - } - - #[test] - #[ignore] // this test is ignored because it will crash the running process - fn exec() { - setup_signal_handler(); - - let eshm = ExclusiveSharedMemory::new(4096).unwrap(); - let (hshm, _) = eshm.build(); - let guard_page_ptr = hshm.raw_ptr(); - let func: fn() = unsafe { std::mem::transmute(guard_page_ptr) }; - func(); - } - - // provides a way for running the above tests in a separate process since they expect to crash - #[test] - fn guard_page_testing_shim() { - let tests = vec!["read", "write", "exec"]; - - for test in tests { - let status = std::process::Command::new("cargo") - .args(["test", "-p", "hyperlight-host", "--", "--ignored", test]) - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .status() - .expect("Unable to launch tests"); - assert_eq!( - status.code(), - Some(TEST_EXIT_CODE.into()), - "Guard Page test failed: {}", - test - ); - } - } - } -} +// TODO(danbugs:297): bring back +// #[cfg(test)] +// mod tests { +// use hyperlight_common::mem::PAGE_SIZE_USIZE; +// use proptest::prelude::*; +// +// use super::{ExclusiveSharedMemory, HostSharedMemory, SharedMemory}; +// use crate::mem::shared_mem_tests::read_write_test_suite; +// use crate::Result; +// +// #[test] +// fn fill() { +// let mem_size: usize = 4096; +// let eshm = ExclusiveSharedMemory::new(mem_size).unwrap(); +// let (mut hshm, _) = eshm.build(); +// +// hshm.fill(1, 0, 1024).unwrap(); +// hshm.fill(2, 1024, 1024).unwrap(); +// hshm.fill(3, 2048, 1024).unwrap(); +// hshm.fill(4, 3072, 1024).unwrap(); +// +// let vec = hshm +// .with_exclusivity(|e| e.copy_all_to_vec().unwrap()) +// .unwrap(); +// +// assert!(vec[0..1024].iter().all(|&x| x == 1)); +// assert!(vec[1024..2048].iter().all(|&x| x == 2)); +// assert!(vec[2048..3072].iter().all(|&x| x == 3)); +// assert!(vec[3072..4096].iter().all(|&x| x == 4)); +// +// hshm.fill(5, 0, 4096).unwrap(); +// +// let vec2 = hshm +// .with_exclusivity(|e| e.copy_all_to_vec().unwrap()) +// .unwrap(); +// assert!(vec2.iter().all(|&x| x == 5)); +// +// assert!(hshm.fill(0, 0, mem_size + 1).is_err()); +// assert!(hshm.fill(0, mem_size, 1).is_err()); +// } +// +// #[test] +// fn copy_into_from() -> Result<()> { +// let mem_size: usize = 4096; +// let vec_len = 10; +// let eshm = ExclusiveSharedMemory::new(mem_size)?; +// let (hshm, _) = eshm.build(); +// let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +// // write the value to the memory at the beginning. +// hshm.copy_from_slice(&vec, 0)?; +// +// let mut vec2 = vec![0; vec_len]; +// // read the value back from the memory at the beginning. +// hshm.copy_to_slice(vec2.as_mut_slice(), 0)?; +// assert_eq!(vec, vec2); +// +// let offset = mem_size - vec.len(); +// // write the value to the memory at the end. +// hshm.copy_from_slice(&vec, offset)?; +// +// let mut vec3 = vec![0; vec_len]; +// // read the value back from the memory at the end. +// hshm.copy_to_slice(&mut vec3, offset)?; +// assert_eq!(vec, vec3); +// +// let offset = mem_size / 2; +// // write the value to the memory at the middle. +// hshm.copy_from_slice(&vec, offset)?; +// +// let mut vec4 = vec![0; vec_len]; +// // read the value back from the memory at the middle. +// hshm.copy_to_slice(&mut vec4, offset)?; +// assert_eq!(vec, vec4); +// +// // try and read a value from an offset that is beyond the end of the memory. +// let mut vec5 = vec![0; vec_len]; +// assert!(hshm.copy_to_slice(&mut vec5, mem_size).is_err()); +// +// // try and write a value to an offset that is beyond the end of the memory. +// assert!(hshm.copy_from_slice(&vec5, mem_size).is_err()); +// +// // try and read a value from an offset that is too large. +// let mut vec6 = vec![0; vec_len]; +// assert!(hshm.copy_to_slice(&mut vec6, mem_size * 2).is_err()); +// +// // try and write a value to an offset that is too large. +// assert!(hshm.copy_from_slice(&vec6, mem_size * 2).is_err()); +// +// // try and read a value that is too large. +// let mut vec7 = vec![0; mem_size * 2]; +// assert!(hshm.copy_to_slice(&mut vec7, 0).is_err()); +// +// // try and write a value that is too large. +// assert!(hshm.copy_from_slice(&vec7, 0).is_err()); +// +// Ok(()) +// } +// +// proptest! { +// #[test] +// fn read_write_i32(val in -0x1000_i32..0x1000_i32) { +// read_write_test_suite( +// val, +// ExclusiveSharedMemory::new, +// Box::new(ExclusiveSharedMemory::read_i32), +// Box::new(ExclusiveSharedMemory::write_i32), +// ) +// .unwrap(); +// read_write_test_suite( +// val, +// |s| { +// let e = ExclusiveSharedMemory::new(s)?; +// let (h, _) = e.build(); +// Ok(h) +// }, +// Box::new(HostSharedMemory::read::), +// Box::new(|h, o, v| h.write::(o, v)), +// ) +// .unwrap(); +// } +// } +// +// #[test] +// fn alloc_fail() { +// let gm = ExclusiveSharedMemory::new(0); +// assert!(gm.is_err()); +// let gm = ExclusiveSharedMemory::new(usize::MAX); +// assert!(gm.is_err()); +// } +// +// #[test] +// fn clone() { +// let eshm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap(); +// let (hshm1, _) = eshm.build(); +// let hshm2 = hshm1.clone(); +// +// // after hshm1 is cloned, hshm1 and hshm2 should have identical +// // memory sizes and pointers. +// assert_eq!(hshm1.mem_size(), hshm2.mem_size()); +// assert_eq!(hshm1.base_addr(), hshm2.base_addr()); +// +// // we should be able to copy a byte array into both hshm1 and hshm2, +// // and have both changes be reflected in all clones +// hshm1.copy_from_slice(b"a", 0).unwrap(); +// hshm2.copy_from_slice(b"b", 1).unwrap(); +// +// // at this point, both hshm1 and hshm2 should have +// // offset 0 = 'a', offset 1 = 'b' +// for (raw_offset, expected) in &[(0, b'a'), (1, b'b')] { +// assert_eq!(hshm1.read::(*raw_offset).unwrap(), *expected); +// assert_eq!(hshm2.read::(*raw_offset).unwrap(), *expected); +// } +// +// // after we drop hshm1, hshm2 should still exist, be valid, +// // and have all contents from before hshm1 was dropped +// drop(hshm1); +// +// // at this point, hshm2 should still have offset 0 = 'a', offset 1 = 'b' +// for (raw_offset, expected) in &[(0, b'a'), (1, b'b')] { +// assert_eq!(hshm2.read::(*raw_offset).unwrap(), *expected); +// } +// hshm2.copy_from_slice(b"c", 2).unwrap(); +// assert_eq!(hshm2.read::(2).unwrap(), b'c'); +// drop(hshm2); +// } +// +// #[test] +// fn copy_all_to_vec() { +// let mut data = vec![b'a', b'b', b'c']; +// data.resize(4096, 0); +// let mut eshm = ExclusiveSharedMemory::new(data.len()).unwrap(); +// eshm.copy_from_slice(data.as_slice(), 0).unwrap(); +// let ret_vec = eshm.copy_all_to_vec().unwrap(); +// assert_eq!(data, ret_vec); +// } +// +// /// A test to ensure that, if a `SharedMem` instance is cloned +// /// and _all_ clones are dropped, the memory region will no longer +// /// be valid. +// /// +// /// This test is ignored because it is incompatible with other tests as +// /// they may be allocating memory at the same time. +// /// +// /// Marking this test as ignored means that running `cargo test` will not +// /// run it. This feature will allow a developer who runs that command +// /// from their workstation to be successful without needing to know about +// /// test interdependencies. This test will, however, be run explicitly as a +// /// part of the CI pipeline. +// #[test] +// #[ignore] +// #[cfg(target_os = "linux")] +// fn test_drop() { +// use proc_maps::maps_contain_addr; +// +// let pid = std::process::id(); +// +// let eshm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap(); +// let (hshm1, gshm) = eshm.build(); +// let hshm2 = hshm1.clone(); +// let addr = hshm1.raw_ptr() as usize; +// +// // ensure the address is in the process's virtual memory +// let maps_before_drop = proc_maps::get_process_maps(pid.try_into().unwrap()).unwrap(); +// assert!( +// maps_contain_addr(addr, &maps_before_drop), +// "shared memory address {:#x} was not found in process map, but should be", +// addr, +// ); +// // drop both shared memory instances, which should result +// // in freeing the memory region +// drop(hshm1); +// drop(hshm2); +// drop(gshm); +// +// let maps_after_drop = proc_maps::get_process_maps(pid.try_into().unwrap()).unwrap(); +// // now, ensure the address is not in the process's virtual memory +// assert!( +// !maps_contain_addr(addr, &maps_after_drop), +// "shared memory address {:#x} was found in the process map, but shouldn't be", +// addr +// ); +// } +// +// #[cfg(target_os = "linux")] +// mod guard_page_crash_test { +// use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory}; +// +// const TEST_EXIT_CODE: u8 = 211; // an uncommon exit code, used for testing purposes +// +// /// hook sigsegv to exit with status code, to make it testable, rather than have it exit from a signal +// /// NOTE: We CANNOT panic!() in the handler, and make the tests #[should_panic], because +// /// the test harness process will crash anyway after the test passes +// fn setup_signal_handler() { +// unsafe { +// signal_hook_registry::register_signal_unchecked(libc::SIGSEGV, || { +// std::process::exit(TEST_EXIT_CODE.into()); +// }) +// .unwrap(); +// } +// } +// +// #[test] +// #[ignore] // this test is ignored because it will crash the running process +// fn read() { +// setup_signal_handler(); +// +// let eshm = ExclusiveSharedMemory::new(4096).unwrap(); +// let (hshm, _) = eshm.build(); +// let guard_page_ptr = hshm.raw_ptr(); +// unsafe { std::ptr::read_volatile(guard_page_ptr) }; +// } +// +// #[test] +// #[ignore] // this test is ignored because it will crash the running process +// fn write() { +// setup_signal_handler(); +// +// let eshm = ExclusiveSharedMemory::new(4096).unwrap(); +// let (hshm, _) = eshm.build(); +// let guard_page_ptr = hshm.raw_ptr(); +// unsafe { std::ptr::write_volatile(guard_page_ptr, 0u8) }; +// } +// +// #[test] +// #[ignore] // this test is ignored because it will crash the running process +// fn exec() { +// setup_signal_handler(); +// +// let eshm = ExclusiveSharedMemory::new(4096).unwrap(); +// let (hshm, _) = eshm.build(); +// let guard_page_ptr = hshm.raw_ptr(); +// let func: fn() = unsafe { std::mem::transmute(guard_page_ptr) }; +// func(); +// } +// +// // provides a way for running the above tests in a separate process since they expect to crash +// #[test] +// fn guard_page_testing_shim() { +// let tests = vec!["read", "write", "exec"]; +// +// for test in tests { +// let status = std::process::Command::new("cargo") +// .args(["test", "-p", "hyperlight-host", "--", "--ignored", test]) +// .stdin(std::process::Stdio::null()) +// .stdout(std::process::Stdio::null()) +// .stderr(std::process::Stdio::null()) +// .status() +// .expect("Unable to launch tests"); +// assert_eq!( +// status.code(), +// Some(TEST_EXIT_CODE.into()), +// "Guard Page test failed: {}", +// test +// ); +// } +// } +// } +// } diff --git a/src/hyperlight_host/src/mem/shared_mem_snapshot.rs b/src/hyperlight_host/src/mem/shared_mem_snapshot.rs index 28d514b76..0ac4941cb 100644 --- a/src/hyperlight_host/src/mem/shared_mem_snapshot.rs +++ b/src/hyperlight_host/src/mem/shared_mem_snapshot.rs @@ -58,16 +58,16 @@ impl SharedMemorySnapshot { #[cfg(test)] mod tests { - use hyperlight_common::mem::PAGE_SIZE_USIZE; + use hyperlight_common::PAGE_SIZE; use crate::mem::shared_mem::ExclusiveSharedMemory; #[test] fn restore_replace() { let mut data1 = vec![b'a', b'b', b'c']; - data1.resize_with(PAGE_SIZE_USIZE, || 0); + data1.resize_with(PAGE_SIZE, || 0); let data2 = data1.iter().map(|b| b + 1).collect::>(); - let mut gm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap(); + let mut gm = ExclusiveSharedMemory::new(PAGE_SIZE).unwrap(); gm.copy_from_slice(data1.as_slice(), 0).unwrap(); let mut snap = super::SharedMemorySnapshot::new(&mut gm).unwrap(); { diff --git a/src/hyperlight_host/src/mem/shared_mem_tests.rs b/src/hyperlight_host/src/mem/shared_mem_tests.rs index 44c54bd03..fef225745 100644 --- a/src/hyperlight_host/src/mem/shared_mem_tests.rs +++ b/src/hyperlight_host/src/mem/shared_mem_tests.rs @@ -1,114 +1,114 @@ -/* -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. -*/ - -use std::clone::Clone; -use std::cmp::PartialEq; -use std::convert::TryFrom; -use std::fmt::Debug; -use std::mem::size_of; - -use hyperlight_common::mem::PAGE_SIZE_USIZE; - -use crate::{log_then_return, new_error, Result}; - -/// A function that knows how to read data of type `T` from a -/// `SharedMemory` at a specified offset -type ReaderFn = dyn Fn(&S, usize) -> Result; -/// A function that knows how to write data of type `T` from a -/// `SharedMemory` at a specified offset. -type WriterFn = dyn Fn(&mut S, usize, T) -> Result<()>; - -/// Run the standard suite of tests for a specified type `U` to write to -/// a `SharedMemory` and a specified type `T` to read back out of -/// the same `SharedMemory`. -/// -/// It's possible to write one type and read a different type so you -/// can write tests involving different type combinations. For example, -/// this function is designed such that you can write a `u64` and read the -/// 8 `u8`s that make up that `u64` back out. -/// -/// Regardless of which types you choose, they must be `Clone`able, -/// `Debug`able, and you must be able to check if `T`, the one returned -/// by the `reader`, is equal to `U`, the one accepted by the writer. -pub(super) fn read_write_test_suite Result>( - initial_val: U, - shared_memory_new: ShmNew, - reader: Box>, - writer: Box>, -) -> Result<()> -where - T: PartialEq + Debug + Clone + TryFrom, - U: Debug + Clone, -{ - let mem_size = PAGE_SIZE_USIZE; - let test_read = |mem_size, offset| { - let sm = shared_memory_new(mem_size)?; - (reader)(&sm, offset) - }; - - let test_write = |mem_size, offset, val| { - let mut sm = shared_memory_new(mem_size)?; - (writer)(&mut sm, offset, val) - }; - - let test_write_read = |mem_size, offset: usize, initial_val: U| { - let mut sm = shared_memory_new(mem_size)?; - writer(&mut sm, offset, initial_val.clone())?; - let ret_val = reader(&sm, offset)?; - - let initial_val_as_t = - T::try_from(initial_val.clone()).map_err(|_| new_error!("cannot convert types"))?; - if initial_val_as_t == ret_val { - Ok(()) - } else { - log_then_return!( - "(mem_size: {}, offset: {}, val: {:?}), actual returned val = {:?}", - mem_size, - offset, - initial_val, - ret_val, - ); - } - }; - - // write the value to the start of memory, then read it back - test_write_read(mem_size, 0, initial_val.clone())?; - // write the value to the end of memory then read it back - test_write_read(mem_size, mem_size - size_of::(), initial_val.clone())?; - // write the value to the middle of memory, then read it back - test_write_read(mem_size, mem_size / 2, initial_val.clone())?; - // read a value from the memory at an invalid offset. - swap_res(test_write_read(mem_size, mem_size * 2, initial_val.clone()))?; - // write the value to the memory at an invalid offset. - swap_res(test_write(mem_size, mem_size * 2, initial_val.clone()))?; - // read a value from the memory beyond the end of the memory. - swap_res(test_read(mem_size, mem_size))?; - // write the value to the memory beyond the end of the memory. - swap_res(test_write(mem_size, mem_size, initial_val))?; - Ok(()) -} - -/// Swaps a result's status. If it was passed as an `Ok`, it will be returned -/// as an `Err` with a hard-coded error message. If it was passed as an `Err`, -/// it will be returned as an `Ok(_)`. -fn swap_res(r: Result) -> Result<()> { - match r { - Ok(_) => { - log_then_return!("result was expected to be an error, but wasn't"); - } - Err(_) => Ok(()), - } -} +// // /* +// // 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. +// // */ +// +// use std::clone::Clone; +// use std::cmp::PartialEq; +// use std::convert::TryFrom; +// use std::fmt::Debug; +// use std::mem::size_of; +// +// use hyperlight_common::PAGE_SIZE; +// +// use crate::{log_then_return, new_error, Result}; +// +// /// A function that knows how to read data of type `T` from a +// /// `SharedMemory` at a specified offset +// type ReaderFn = dyn Fn(&S, usize) -> Result; +// /// A function that knows how to write data of type `T` from a +// /// `SharedMemory` at a specified offset. +// type WriterFn = dyn Fn(&mut S, usize, T) -> Result<()>; +// +// /// Run the standard suite of tests for a specified type `U` to write to +// /// a `SharedMemory` and a specified type `T` to read back out of +// /// the same `SharedMemory`. +// /// +// /// It's possible to write one type and read a different type so you +// /// can write tests involving different type combinations. For example, +// /// this function is designed such that you can write a `u64` and read the +// /// 8 `u8`s that make up that `u64` back out. +// /// +// /// Regardless of which types you choose, they must be `Clone`able, +// /// `Debug`able, and you must be able to check if `T`, the one returned +// /// by the `reader`, is equal to `U`, the one accepted by the writer. +// pub(super) fn read_write_test_suite Result>( +// initial_val: U, +// shared_memory_new: ShmNew, +// reader: Box>, +// writer: Box>, +// ) -> Result<()> +// where +// T: PartialEq + Debug + Clone + TryFrom, +// U: Debug + Clone, +// { +// let mem_size = PAGE_SIZE; +// let test_read = |mem_size, offset| { +// let sm = shared_memory_new(mem_size)?; +// (reader)(&sm, offset) +// }; +// +// let test_write = |mem_size, offset, val| { +// let mut sm = shared_memory_new(mem_size)?; +// (writer)(&mut sm, offset, val) +// }; +// +// let test_write_read = |mem_size, offset: usize, initial_val: U| { +// let mut sm = shared_memory_new(mem_size)?; +// writer(&mut sm, offset, initial_val.clone())?; +// let ret_val = reader(&sm, offset)?; +// +// let initial_val_as_t = +// T::try_from(initial_val.clone()).map_err(|_| new_error!("cannot convert types"))?; +// if initial_val_as_t == ret_val { +// Ok(()) +// } else { +// log_then_return!( +// "(mem_size: {}, offset: {}, val: {:?}), actual returned val = {:?}", +// mem_size, +// offset, +// initial_val, +// ret_val, +// ); +// } +// }; +// +// // write the value to the start of memory, then read it back +// test_write_read(mem_size, 0, initial_val.clone())?; +// // write the value to the end of memory then read it back +// test_write_read(mem_size, mem_size - size_of::(), initial_val.clone())?; +// // write the value to the middle of memory, then read it back +// test_write_read(mem_size, mem_size / 2, initial_val.clone())?; +// // read a value from the memory at an invalid offset. +// swap_res(test_write_read(mem_size, mem_size * 2, initial_val.clone()))?; +// // write the value to the memory at an invalid offset. +// swap_res(test_write(mem_size, mem_size * 2, initial_val.clone()))?; +// // read a value from the memory beyond the end of the memory. +// swap_res(test_read(mem_size, mem_size))?; +// // write the value to the memory beyond the end of the memory. +// swap_res(test_write(mem_size, mem_size, initial_val))?; +// Ok(()) +// } +// +// /// Swaps a result's status. If it was passed as an `Ok`, it will be returned +// /// as an `Err` with a hard-coded error message. If it was passed as an `Err`, +// /// it will be returned as an `Ok(_)`. +// fn swap_res(r: Result) -> Result<()> { +// match r { +// Ok(_) => { +// log_then_return!("result was expected to be an error, but wasn't"); +// } +// Err(_) => Ok(()), +// } +// } diff --git a/src/hyperlight_host/src/metrics/mod.rs b/src/hyperlight_host/src/metrics/mod.rs index 7db1ed4cc..cec622f7b 100644 --- a/src/hyperlight_host/src/metrics/mod.rs +++ b/src/hyperlight_host/src/metrics/mod.rs @@ -83,21 +83,24 @@ pub(crate) fn maybe_time_and_emit_host_call T>( } } -#[cfg(test)] mod tests { - use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; - use hyperlight_testing::simple_guest_as_string; - use metrics::Key; - use metrics_util::CompositeKey; - - use super::*; - use crate::sandbox_state::sandbox::EvolvableSandbox; - use crate::sandbox_state::transition::Noop; - use crate::{GuestBinary, UninitializedSandbox}; - #[test] #[ignore = "This test needs to be run separately to avoid having other tests interfere with it"] - fn test_metrics_are_emitted() { + fn test_metrics_are_emitted() -> crate::Result<()> { + use std::sync::{Arc, Mutex}; + + use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; + use hyperlight_testing::simple_guest_as_string; + use metrics::Key; + use metrics_util::CompositeKey; + + use super::*; + use crate::func::HostFunction2; + use crate::sandbox::sandbox_builder::SandboxBuilder; + use crate::sandbox_state::sandbox::EvolvableSandbox; + use crate::sandbox_state::transition::Noop; + use crate::GuestBinary; + // Set up the recorder and snapshotter let recorder = metrics_util::debugging::DebuggingRecorder::new(); let snapshotter = recorder.snapshotter(); @@ -107,23 +110,23 @@ mod tests { recorder.install().unwrap(); let snapshot = { - let uninit = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().unwrap()), - None, - None, - None, - ) - .unwrap(); + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + let mut uninit = sandbox_builder.build()?; + + fn add(a: i32, b: i32) -> crate::Result { + Ok(a + b) + } + let host_function = Arc::new(Mutex::new(add)); + host_function.register(&mut uninit, "HostAdd")?; let mut multi = uninit.evolve(Noop::default()).unwrap(); - multi - .call_guest_function_by_name( - "PrintOutput", - ReturnType::Int, - Some(vec![ParameterValue::String("Hello".to_string())]), - ) - .unwrap(); + multi.call_guest_function_by_name( + "Add", + ReturnType::Int, + Some(vec![ParameterValue::Int(1), ParameterValue::Int(2)]), + )?; multi .call_guest_function_by_name("Spin", ReturnType::Int, None) @@ -142,12 +145,12 @@ mod tests { // Verify that the histogram metrics are recorded correctly assert_eq!(snapshot.len(), 4, "Expected two metrics in the snapshot"); - // 1. Host print duration + // 1. Host add duration let histogram_key = CompositeKey::new( metrics_util::MetricKind::Histogram, Key::from_parts( METRIC_HOST_FUNC_DURATION, - vec![Label::new("function_name", "HostPrint")], + vec![Label::new("function_name", "HostAdd")], ), ); let histogram_value = &snapshot.get(&histogram_key).unwrap().2; @@ -164,7 +167,7 @@ mod tests { metrics_util::MetricKind::Histogram, Key::from_parts( METRIC_GUEST_FUNC_DURATION, - vec![Label::new("function_name", "PrintOutput")], + vec![Label::new("function_name", "Add")], ), ); let histogram_value = &snapshot.get(&histogram_key).unwrap().2; @@ -216,5 +219,7 @@ mod tests { ); } } + + Ok(()) } } diff --git a/src/hyperlight_host/src/sandbox/config.rs b/src/hyperlight_host/src/sandbox/config.rs index e0bbaddbe..8daca4629 100644 --- a/src/hyperlight_host/src/sandbox/config.rs +++ b/src/hyperlight_host/src/sandbox/config.rs @@ -19,8 +19,6 @@ use std::time::Duration; use tracing::{instrument, Span}; -use crate::mem::exe::ExeInfo; - /// Used for passing debug configuration to a sandbox #[cfg(gdb)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -32,95 +30,41 @@ pub struct DebugInfo { /// The complete set of configuration needed to create a Sandbox #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(C)] -pub struct SandboxConfiguration { - /// Guest gdb debug port - #[cfg(gdb)] - guest_debug_info: Option, - /// The maximum size of the guest error buffer. - guest_error_buffer_size: usize, - /// The size of the memory buffer that is made available for Guest Function - /// Definitions - host_function_definition_size: usize, - /// The size of the memory buffer that is made available for serialising - /// Host Exceptions - host_exception_size: usize, - /// The size of the memory buffer that is made available for input to the - /// Guest Binary - input_data_size: usize, - /// The size of the memory buffer that is made available for input to the - /// Guest Binary - output_data_size: usize, - /// The stack size to use in the guest sandbox. If set to 0, the stack - /// size will be determined from the PE file header. - /// - /// Note: this is a C-compatible struct, so even though this optional - /// field should be represented as an `Option`, that type is not - /// FFI-safe, so it cannot be. - stack_size_override: u64, - /// The heap size to use in the guest sandbox. If set to 0, the heap - /// size will be determined from the PE file header - /// - /// Note: this is a C-compatible struct, so even though this optional - /// field should be represented as an `Option`, that type is not - /// FFI-safe, so it cannot be. - heap_size_override: u64, - /// The kernel_stack_size to use in the guest sandbox. If set to 0, the default kernel stack size will be used. - /// The value will be increased to a multiple page size when memory is allocated if necessary. - /// - kernel_stack_size: usize, - /// The max_execution_time of a guest execution in milliseconds. If set to 0, the max_execution_time - /// will be set to the default value of 1000ms if the guest execution does not complete within the time specified - /// then the execution will be cancelled, the minimum value is 1ms +pub(crate) struct SandboxConfiguration { + // The size of custom guest memory, which includes everything the guest might want to make + // addressable (e.g., heap, etc.). + custom_guest_memory_size: u64, + + /// The `max_execution_time` of a guest execution in milliseconds. /// /// Note: this is a C-compatible struct, so even though this optional /// field should be represented as an `Option`, that type is not /// FFI-safe, so it cannot be. - /// max_execution_time: u16, - /// The max_wait_for_cancellation represents the maximum time the host should wait for a guest execution to be cancelled - /// If set to 0, the max_wait_for_cancellation will be set to the default value of 10ms. - /// The minimum value is 1ms. + /// The `max_wait_for_cancellation` represents the maximum time the host should wait for a guest + /// execution to be cancelled. /// /// Note: this is a C-compatible struct, so even though this optional /// field should be represented as an `Option`, that type is not /// FFI-safe, so it cannot be. max_wait_for_cancellation: u8, - // The max_initialization_time represents the maximum time the host should wait for a guest to initialize - // If set to 0, the max_initialization_time will be set to the default value of 2000ms. - // The minimum value is 1ms. + // The `max_initialization_time` represents the maximum time the host should wait for a guest to + // initialize. // // Note: this is a C-compatible struct, so even though this optional // field should be represented as an `Option`, that type is not // FFI-safe, so it cannot be. max_initialization_time: u16, - /// The size of the memory buffer that is made available for serializing - /// guest panic context - guest_panic_context_buffer_size: usize, + + /// Guest gdb debug port + #[cfg(gdb)] + guest_debug_info: Option, } impl SandboxConfiguration { - /// The default size of input data - pub const DEFAULT_INPUT_SIZE: usize = 0x4000; - /// The minimum size of input data - pub const MIN_INPUT_SIZE: usize = 0x2000; - /// The default size of output data - pub const DEFAULT_OUTPUT_SIZE: usize = 0x4000; - /// The minimum size of output data - pub const MIN_OUTPUT_SIZE: usize = 0x2000; - /// The default size of host function definitions - /// Host function definitions has its own page in memory, in order to be READ-ONLY - /// from a guest's perspective. - pub const DEFAULT_HOST_FUNCTION_DEFINITION_SIZE: usize = 0x1000; - /// The minimum size of host function definitions - pub const MIN_HOST_FUNCTION_DEFINITION_SIZE: usize = 0x1000; - /// The default size for host exceptions - pub const DEFAULT_HOST_EXCEPTION_SIZE: usize = 0x4000; - /// The minimum size for host exceptions - pub const MIN_HOST_EXCEPTION_SIZE: usize = 0x4000; - /// The default size for guest error messages - pub const DEFAULT_GUEST_ERROR_BUFFER_SIZE: usize = 0x100; - /// The minimum size for guest error messages - pub const MIN_GUEST_ERROR_BUFFER_SIZE: usize = 0x80; + /// The default value for custom memory region (200MB) + // TODO: arbitrary value, maybe change + pub const DEFAULT_CUSTOM_GUEST_MEMORY_SIZE: u64 = 200 * 1024 * 1024; /// The default value for max initialization time (in milliseconds) pub const DEFAULT_MAX_INITIALIZATION_TIME: u16 = 2000; /// The minimum value for max initialization time (in milliseconds) @@ -139,52 +83,42 @@ impl SandboxConfiguration { pub const MIN_MAX_WAIT_FOR_CANCELLATION: u8 = 10; /// The maximum value for max wait for cancellation (in milliseconds) pub const MAX_MAX_WAIT_FOR_CANCELLATION: u8 = u8::MAX; - /// The default and minimum values for guest panic context data - pub const DEFAULT_GUEST_PANIC_CONTEXT_BUFFER_SIZE: usize = 0x400; - /// The minimum value for guest panic context data - pub const MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE: usize = 0x400; - /// The minimum value for kernel stack size - pub const MIN_KERNEL_STACK_SIZE: usize = 0x1000; - /// The default value for kernel stack size - pub const DEFAULT_KERNEL_STACK_SIZE: usize = Self::MIN_KERNEL_STACK_SIZE; - #[allow(clippy::too_many_arguments)] - /// Create a new configuration for a sandbox with the given sizes. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] + /// Create a new configuration for a sandbox fn new( - input_data_size: usize, - output_data_size: usize, - function_definition_size: usize, - host_exception_size: usize, - guest_error_buffer_size: usize, - stack_size_override: Option, - heap_size_override: Option, - kernel_stack_size: usize, - max_execution_time: Option, + custom_guest_memory_size: u64, max_initialization_time: Option, + max_execution_time: Option, max_wait_for_cancellation: Option, - guest_panic_context_buffer_size: usize, #[cfg(gdb)] guest_debug_info: Option, ) -> Self { Self { - input_data_size: max(input_data_size, Self::MIN_INPUT_SIZE), - output_data_size: max(output_data_size, Self::MIN_OUTPUT_SIZE), - host_function_definition_size: max( - function_definition_size, - Self::MIN_HOST_FUNCTION_DEFINITION_SIZE, - ), - host_exception_size: max(host_exception_size, Self::MIN_HOST_EXCEPTION_SIZE), - guest_error_buffer_size: max( - guest_error_buffer_size, - Self::MIN_GUEST_ERROR_BUFFER_SIZE, - ), - stack_size_override: stack_size_override.unwrap_or(0), - heap_size_override: heap_size_override.unwrap_or(0), - kernel_stack_size: max(kernel_stack_size, Self::MIN_KERNEL_STACK_SIZE), + custom_guest_memory_size, + max_initialization_time: { + match max_initialization_time { + Some(max_initialization_time) => match max_initialization_time.as_millis() { + 0 => Self::DEFAULT_MAX_INITIALIZATION_TIME, + // TODO: it's pretty confusing that if someone sets the + // max initialization time to 0 that we change it to + // DEFAULT_MAX_INITIALIZATION_TIME, we should improve this API. + 1.. => min( + Self::MAX_MAX_INITIALIZATION_TIME.into(), + max( + max_initialization_time.as_millis(), + Self::MIN_MAX_INITIALIZATION_TIME.into(), + ), + ) as u16, + }, + None => Self::DEFAULT_MAX_INITIALIZATION_TIME, + } + }, max_execution_time: { match max_execution_time { Some(max_execution_time) => match max_execution_time.as_millis() { 0 => Self::DEFAULT_MAX_EXECUTION_TIME, + // TODO: it's pretty confusing that if someone sets the + // max execution time to 0 that we change it to + // DEFAULT_MAX_EXECUTION_TIME, we should improve this API. 1.. => min( Self::MAX_MAX_EXECUTION_TIME.into(), max( @@ -201,6 +135,9 @@ impl SandboxConfiguration { Some(max_wait_for_cancellation) => { match max_wait_for_cancellation.as_millis() { 0 => Self::DEFAULT_MAX_WAIT_FOR_CANCELLATION, + // TODO: it's pretty confusing that if someone sets the + // max wait for cancellation time to 0 that we change it to + // DEFAULT_MAX_WAIT_FOR_CANCELLATION, we should improve this API. 1.. => min( Self::MAX_MAX_WAIT_FOR_CANCELLATION.into(), max( @@ -213,91 +150,39 @@ impl SandboxConfiguration { None => Self::DEFAULT_MAX_WAIT_FOR_CANCELLATION, } }, - max_initialization_time: { - match max_initialization_time { - Some(max_initialization_time) => match max_initialization_time.as_millis() { - 0 => Self::DEFAULT_MAX_INITIALIZATION_TIME, - 1.. => min( - Self::MAX_MAX_INITIALIZATION_TIME.into(), - max( - max_initialization_time.as_millis(), - Self::MIN_MAX_INITIALIZATION_TIME.into(), - ), - ) as u16, - }, - None => Self::DEFAULT_MAX_INITIALIZATION_TIME, - } - }, - guest_panic_context_buffer_size: max( - guest_panic_context_buffer_size, - Self::MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE, - ), #[cfg(gdb)] guest_debug_info, } } - /// Set the size of the memory buffer that is made available for input to the guest - /// the minimum value is MIN_INPUT_SIZE - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_input_data_size(&mut self, input_data_size: usize) { - self.input_data_size = max(input_data_size, Self::MIN_INPUT_SIZE); - } - - /// Set the size of the memory buffer that is made available for output from the guest - /// the minimum value is MIN_OUTPUT_SIZE - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_output_data_size(&mut self, output_data_size: usize) { - self.output_data_size = max(output_data_size, Self::MIN_OUTPUT_SIZE); - } - - /// Set the size of the memory buffer that is made available for serialising host function definitions - /// the minimum value is MIN_HOST_FUNCTION_DEFINITION_SIZE - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_host_function_definition_size(&mut self, host_function_definition_size: usize) { - self.host_function_definition_size = max( - host_function_definition_size, - Self::MIN_HOST_FUNCTION_DEFINITION_SIZE, - ); - } - - /// Set the size of the memory buffer that is made available for serialising host exceptions - /// the minimum value is MIN_HOST_EXCEPTION_SIZE - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_host_exception_size(&mut self, host_exception_size: usize) { - self.host_exception_size = max(host_exception_size, Self::MIN_HOST_EXCEPTION_SIZE); - } - - /// Set the size of the memory buffer that is made available for serialising guest error messages - /// the minimum value is MIN_GUEST_ERROR_BUFFER_SIZE - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_guest_error_buffer_size(&mut self, guest_error_buffer_size: usize) { - self.guest_error_buffer_size = - max(guest_error_buffer_size, Self::MIN_GUEST_ERROR_BUFFER_SIZE); - } - - /// Set the stack size to use in the guest sandbox. If set to 0, the stack size will be determined from the PE file header - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_stack_size(&mut self, stack_size: u64) { - self.stack_size_override = stack_size; - } - - /// Set the heap size to use in the guest sandbox. If set to 0, the heap size will be determined from the PE file header - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_heap_size(&mut self, heap_size: u64) { - self.heap_size_override = heap_size; + /// Set the maximum time to wait for guest initialization. If set to 0, the maximum initialization + /// time will be set to the default value of `DEFAULT_MAX_INITIALIZATION_TIME`. If the guest + /// initialization does not complete within the time specified then an error will be returned, + /// the minimum value is `MIN_MAX_INITIALIZATION_TIME` + pub fn set_max_initialization_time(&mut self, max_initialization_time: Duration) { + match max_initialization_time.as_millis() { + 0 => self.max_initialization_time = Self::DEFAULT_MAX_INITIALIZATION_TIME, + 1.. => { + self.max_initialization_time = min( + Self::MAX_MAX_INITIALIZATION_TIME.into(), + max( + max_initialization_time.as_millis(), + Self::MIN_MAX_INITIALIZATION_TIME.into(), + ), + ) as u16 + } + } } - /// Set the kernel stack size to use in the guest sandbox. If less than the minimum value of MIN_KERNEL_STACK_SIZE, the minimum value will be used. - /// If its not a multiple of the page size, it will be increased to the a multiple of the page size when memory is allocated. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_kernel_stack_size(&mut self, kernel_stack_size: usize) { - self.kernel_stack_size = max(kernel_stack_size, Self::MIN_KERNEL_STACK_SIZE); + /// Get the maximum time to wait for guest initialization + pub(crate) fn get_max_initialization_time(&self) -> u16 { + self.max_initialization_time } - /// Set the maximum execution time of a guest function execution. If set to 0, the max_execution_time - /// will be set to the default value of DEFAULT_MAX_EXECUTION_TIME if the guest execution does not complete within the time specified - /// then the execution will be cancelled, the minimum value is MIN_MAX_EXECUTION_TIME + /// Set the maximum execution time of a guest function execution. If set to 0, the + /// `max_execution_time` will be set to the default value of `DEFAULT_MAX_EXECUTION_TIME`. + /// If the guest execution does not complete within the time specified then the execution + /// will be cancelled, the minimum value is `MIN_MAX_EXECUTION_TIME`. #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub fn set_max_execution_time(&mut self, max_execution_time: Duration) { match max_execution_time.as_millis() { @@ -314,9 +199,15 @@ impl SandboxConfiguration { } } - /// Set the maximum time to wait for guest execution calculation. If set to 0, the maximum cancellation time - /// will be set to the default value of DEFAULT_MAX_WAIT_FOR_CANCELLATION if the guest execution cancellation does not complete within the time specified - /// then an error will be returned, the minimum value is MIN_MAX_WAIT_FOR_CANCELLATION + /// Get the maximum execution time of a guest function. + pub(crate) fn get_max_execution_time(&self) -> u16 { + self.max_execution_time + } + + /// Set the maximum time to wait for guest execution calculation. If set to 0, the maximum + /// cancellation time will be set to the default value of `DEFAULT_MAX_WAIT_FOR_CANCELLATION`. + /// If the guest execution cancellation does not complete within the time specified + /// then an error will be returned, the minimum value is `MIN_MAX_WAIT_FOR_CANCELLATION` #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub fn set_max_execution_cancel_wait_time(&mut self, max_wait_for_cancellation: Duration) { match max_wait_for_cancellation.as_millis() { @@ -333,31 +224,9 @@ impl SandboxConfiguration { } } - /// Set the maximum time to wait for guest initialization. If set to 0, the maximum initialization time - /// will be set to the default value of DEFAULT_MAX_INITIALIZATION_TIME if the guest initialization does not complete within the time specified - /// then an error will be returned, the minimum value is MIN_MAX_INITIALIZATION_TIME - pub fn set_max_initialization_time(&mut self, max_initialization_time: Duration) { - match max_initialization_time.as_millis() { - 0 => self.max_initialization_time = Self::DEFAULT_MAX_INITIALIZATION_TIME, - 1.. => { - self.max_initialization_time = min( - Self::MAX_MAX_INITIALIZATION_TIME.into(), - max( - max_initialization_time.as_millis(), - Self::MIN_MAX_INITIALIZATION_TIME.into(), - ), - ) as u16 - } - } - } - - /// Set the size of the memory buffer that is made available for serializing guest panic context - /// the minimum value is MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE - pub fn set_guest_panic_context_buffer_size(&mut self, guest_panic_context_buffer_size: usize) { - self.guest_panic_context_buffer_size = max( - guest_panic_context_buffer_size, - Self::MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE, - ); + /// Get the maximum time to wait for guest execution cancellation. + pub(crate) fn get_max_wait_for_cancellation(&self) -> u8 { + self.max_wait_for_cancellation } /// Sets the configuration for the guest debug @@ -366,395 +235,23 @@ impl SandboxConfiguration { pub fn set_guest_debug_info(&mut self, debug_info: DebugInfo) { self.guest_debug_info = Some(debug_info); } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_error_buffer_size(&self) -> usize { - self.guest_error_buffer_size - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_host_function_definition_size(&self) -> usize { - self.host_function_definition_size - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_host_exception_size(&self) -> usize { - self.host_exception_size - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_input_data_size(&self) -> usize { - self.input_data_size - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_output_data_size(&self) -> usize { - self.output_data_size - } - - #[instrument(skip_all, parent = Span::current(), level="Trace")] - pub(crate) fn get_guest_panic_context_buffer_size(&self) -> usize { - self.guest_panic_context_buffer_size - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_max_execution_time(&self) -> u16 { - self.max_execution_time - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_max_wait_for_cancellation(&self) -> u8 { - self.max_wait_for_cancellation - } - - pub(crate) fn get_max_initialization_time(&self) -> u16 { - self.max_initialization_time - } - - #[cfg(gdb)] - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_guest_debug_info(&self) -> Option { - self.guest_debug_info - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn stack_size_override_opt(&self) -> Option { - (self.stack_size_override > 0).then_some(self.stack_size_override) - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn heap_size_override_opt(&self) -> Option { - (self.heap_size_override > 0).then_some(self.heap_size_override) - } - - /// If self.stack_size is non-zero, return it. Otherwise, - /// return exe_info.stack_reserve() - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_stack_size(&self, exe_info: &ExeInfo) -> u64 { - self.stack_size_override_opt() - .unwrap_or_else(|| exe_info.stack_reserve()) - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_kernel_stack_size(&self) -> usize { - self.kernel_stack_size - } - - /// If self.heap_size_override is non-zero, return it. Otherwise, - /// return exe_info.heap_reserve() - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_heap_size(&self, exe_info: &ExeInfo) -> u64 { - self.heap_size_override_opt() - .unwrap_or_else(|| exe_info.heap_reserve()) - } } impl Default for SandboxConfiguration { - #[instrument(skip_all, parent = Span::current(), level= "Trace")] fn default() -> Self { Self::new( - Self::DEFAULT_INPUT_SIZE, - Self::DEFAULT_OUTPUT_SIZE, - Self::DEFAULT_HOST_FUNCTION_DEFINITION_SIZE, - Self::DEFAULT_HOST_EXCEPTION_SIZE, - Self::DEFAULT_GUEST_ERROR_BUFFER_SIZE, - None, - None, - Self::DEFAULT_KERNEL_STACK_SIZE, - None, - None, - None, - Self::DEFAULT_GUEST_PANIC_CONTEXT_BUFFER_SIZE, - #[cfg(gdb)] - None, - ) - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use super::SandboxConfiguration; - use crate::testing::{callback_guest_exe_info, simple_guest_exe_info}; - - #[test] - fn overrides() { - const STACK_SIZE_OVERRIDE: u64 = 0x10000; - const HEAP_SIZE_OVERRIDE: u64 = 0x50000; - const INPUT_DATA_SIZE_OVERRIDE: usize = 0x4000; - const OUTPUT_DATA_SIZE_OVERRIDE: usize = 0x4001; - const HOST_FUNCTION_DEFINITION_SIZE_OVERRIDE: usize = 0x4002; - const HOST_EXCEPTION_SIZE_OVERRIDE: usize = 0x4003; - const GUEST_ERROR_BUFFER_SIZE_OVERRIDE: usize = 0x40004; - const MAX_EXECUTION_TIME_OVERRIDE: u16 = 1010; - const MAX_WAIT_FOR_CANCELLATION_OVERRIDE: u8 = 200; - const MAX_INITIALIZATION_TIME_OVERRIDE: u16 = 2000; - const GUEST_PANIC_CONTEXT_BUFFER_SIZE_OVERRIDE: usize = 0x4005; - const KERNEL_STACK_SIZE_OVERRIDE: usize = 0x4000; - let mut cfg = SandboxConfiguration::new( - INPUT_DATA_SIZE_OVERRIDE, - OUTPUT_DATA_SIZE_OVERRIDE, - HOST_FUNCTION_DEFINITION_SIZE_OVERRIDE, - HOST_EXCEPTION_SIZE_OVERRIDE, - GUEST_ERROR_BUFFER_SIZE_OVERRIDE, - Some(STACK_SIZE_OVERRIDE), - Some(HEAP_SIZE_OVERRIDE), - KERNEL_STACK_SIZE_OVERRIDE, - Some(Duration::from_millis(MAX_EXECUTION_TIME_OVERRIDE as u64)), + Self::DEFAULT_CUSTOM_GUEST_MEMORY_SIZE, Some(Duration::from_millis( - MAX_INITIALIZATION_TIME_OVERRIDE as u64, + Self::DEFAULT_MAX_INITIALIZATION_TIME as u64, )), Some(Duration::from_millis( - MAX_WAIT_FOR_CANCELLATION_OVERRIDE as u64, - )), - GUEST_PANIC_CONTEXT_BUFFER_SIZE_OVERRIDE, - #[cfg(gdb)] - None, - ); - let exe_infos = vec![ - simple_guest_exe_info().unwrap(), - callback_guest_exe_info().unwrap(), - ]; - for exe_info in exe_infos { - let stack_size = cfg.get_stack_size(&exe_info); - let heap_size = cfg.get_heap_size(&exe_info); - assert_eq!(STACK_SIZE_OVERRIDE, stack_size); - assert_eq!(HEAP_SIZE_OVERRIDE, heap_size); - } - cfg.stack_size_override = 1024; - cfg.heap_size_override = 2048; - assert_eq!(1024, cfg.stack_size_override); - assert_eq!(2048, cfg.heap_size_override); - assert_eq!(16384, cfg.kernel_stack_size); - assert_eq!(INPUT_DATA_SIZE_OVERRIDE, cfg.input_data_size); - assert_eq!(OUTPUT_DATA_SIZE_OVERRIDE, cfg.output_data_size); - assert_eq!( - HOST_FUNCTION_DEFINITION_SIZE_OVERRIDE, - cfg.host_function_definition_size - ); - assert_eq!(HOST_EXCEPTION_SIZE_OVERRIDE, cfg.host_exception_size); - assert_eq!( - GUEST_ERROR_BUFFER_SIZE_OVERRIDE, - cfg.guest_error_buffer_size - ); - assert_eq!(MAX_EXECUTION_TIME_OVERRIDE, cfg.max_execution_time); - assert_eq!( - MAX_WAIT_FOR_CANCELLATION_OVERRIDE, - cfg.max_wait_for_cancellation - ); - assert_eq!( - MAX_WAIT_FOR_CANCELLATION_OVERRIDE, - cfg.max_wait_for_cancellation - ); - assert_eq!( - GUEST_PANIC_CONTEXT_BUFFER_SIZE_OVERRIDE, - cfg.guest_panic_context_buffer_size - ); - } - - #[test] - fn min_sizes() { - let mut cfg = SandboxConfiguration::new( - SandboxConfiguration::MIN_INPUT_SIZE - 1, - SandboxConfiguration::MIN_OUTPUT_SIZE - 1, - SandboxConfiguration::MIN_HOST_FUNCTION_DEFINITION_SIZE - 1, - SandboxConfiguration::MIN_HOST_EXCEPTION_SIZE - 1, - SandboxConfiguration::MIN_GUEST_ERROR_BUFFER_SIZE - 1, - None, - None, - SandboxConfiguration::MIN_KERNEL_STACK_SIZE - 1, - Some(Duration::from_millis( - SandboxConfiguration::MIN_MAX_EXECUTION_TIME as u64, - )), - Some(Duration::from_millis( - SandboxConfiguration::MIN_MAX_INITIALIZATION_TIME as u64, + Self::DEFAULT_MAX_EXECUTION_TIME as u64, )), Some(Duration::from_millis( - SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION as u64 - 1, + Self::DEFAULT_MAX_WAIT_FOR_CANCELLATION as u64, )), - SandboxConfiguration::MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE - 1, #[cfg(gdb)] None, - ); - assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size); - assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size); - assert_eq!( - SandboxConfiguration::MIN_KERNEL_STACK_SIZE, - cfg.kernel_stack_size - ); - assert_eq!( - SandboxConfiguration::MIN_HOST_FUNCTION_DEFINITION_SIZE, - cfg.host_function_definition_size - ); - assert_eq!( - SandboxConfiguration::MIN_HOST_EXCEPTION_SIZE, - cfg.host_exception_size - ); - assert_eq!( - SandboxConfiguration::MIN_GUEST_ERROR_BUFFER_SIZE, - cfg.guest_error_buffer_size - ); - assert_eq!(0, cfg.stack_size_override); - assert_eq!(0, cfg.heap_size_override); - assert_eq!( - SandboxConfiguration::MIN_MAX_EXECUTION_TIME, - cfg.max_execution_time - ); - assert_eq!( - SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION, - cfg.max_wait_for_cancellation - ); - assert_eq!( - SandboxConfiguration::MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE, - cfg.guest_panic_context_buffer_size - ); - assert_eq!( - SandboxConfiguration::MIN_MAX_EXECUTION_TIME, - cfg.max_initialization_time - ); - - cfg.set_input_data_size(SandboxConfiguration::MIN_INPUT_SIZE - 1); - cfg.set_output_data_size(SandboxConfiguration::MIN_OUTPUT_SIZE - 1); - cfg.set_host_function_definition_size( - SandboxConfiguration::MIN_HOST_FUNCTION_DEFINITION_SIZE - 1, - ); - cfg.set_host_exception_size(SandboxConfiguration::MIN_HOST_EXCEPTION_SIZE - 1); - cfg.set_guest_error_buffer_size(SandboxConfiguration::MIN_GUEST_ERROR_BUFFER_SIZE - 1); - cfg.set_max_execution_time(Duration::from_millis( - SandboxConfiguration::MIN_MAX_EXECUTION_TIME as u64, - )); - cfg.set_max_initialization_time(Duration::from_millis( - SandboxConfiguration::MIN_MAX_INITIALIZATION_TIME as u64 - 1, - )); - cfg.set_max_execution_cancel_wait_time(Duration::from_millis( - SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION as u64 - 1, - )); - cfg.set_guest_panic_context_buffer_size( - SandboxConfiguration::MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE - 1, - ); - - assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size); - assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size); - assert_eq!( - SandboxConfiguration::MIN_HOST_FUNCTION_DEFINITION_SIZE, - cfg.host_function_definition_size - ); - assert_eq!( - SandboxConfiguration::MIN_HOST_EXCEPTION_SIZE, - cfg.host_exception_size - ); - assert_eq!( - SandboxConfiguration::MIN_GUEST_ERROR_BUFFER_SIZE, - cfg.guest_error_buffer_size - ); - assert_eq!( - SandboxConfiguration::MIN_MAX_EXECUTION_TIME, - cfg.max_execution_time - ); - assert_eq!( - SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION, - cfg.max_wait_for_cancellation - ); - assert_eq!( - SandboxConfiguration::MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE, - cfg.guest_panic_context_buffer_size - ); - } - - mod proptests { - use proptest::prelude::*; - - use super::SandboxConfiguration; - #[cfg(gdb)] - use crate::sandbox::config::DebugInfo; - - proptest! { - #[test] - fn error_buffer_size(size in SandboxConfiguration::MIN_GUEST_ERROR_BUFFER_SIZE..=SandboxConfiguration::MIN_GUEST_ERROR_BUFFER_SIZE * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_guest_error_buffer_size(size); - prop_assert_eq!(size, cfg.get_guest_error_buffer_size()); - } - - #[test] - fn host_function_definition_size(size in SandboxConfiguration::MIN_HOST_FUNCTION_DEFINITION_SIZE..=SandboxConfiguration::MIN_HOST_FUNCTION_DEFINITION_SIZE * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_host_function_definition_size(size); - prop_assert_eq!(size, cfg.get_host_function_definition_size()); - } - - #[test] - fn host_exception_size(size in SandboxConfiguration::MIN_HOST_EXCEPTION_SIZE..=SandboxConfiguration::MIN_HOST_EXCEPTION_SIZE * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_host_exception_size(size); - prop_assert_eq!(size, cfg.get_host_exception_size()); - } - - #[test] - fn input_data_size(size in SandboxConfiguration::MIN_INPUT_SIZE..=SandboxConfiguration::MIN_INPUT_SIZE * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_input_data_size(size); - prop_assert_eq!(size, cfg.get_input_data_size()); - } - - #[test] - fn output_data_size(size in SandboxConfiguration::MIN_OUTPUT_SIZE..=SandboxConfiguration::MIN_OUTPUT_SIZE * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_output_data_size(size); - prop_assert_eq!(size, cfg.get_output_data_size()); - } - - #[test] - fn guest_panic_context_buffer_size(size in SandboxConfiguration::MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE..=SandboxConfiguration::MIN_GUEST_PANIC_CONTEXT_BUFFER_SIZE * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_guest_panic_context_buffer_size(size); - prop_assert_eq!(size, cfg.get_guest_panic_context_buffer_size()); - } - - #[test] - fn max_execution_time(time in SandboxConfiguration::MIN_MAX_EXECUTION_TIME..=SandboxConfiguration::MIN_MAX_EXECUTION_TIME * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_max_execution_time(std::time::Duration::from_millis(time.into())); - prop_assert_eq!(time, cfg.get_max_execution_time()); - } - - #[test] - fn max_wait_for_cancellation(time in SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION..=SandboxConfiguration::MIN_MAX_WAIT_FOR_CANCELLATION * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_max_execution_cancel_wait_time(std::time::Duration::from_millis(time.into())); - prop_assert_eq!(time, cfg.get_max_wait_for_cancellation()); - } - - #[test] - fn max_initialization_time(time in SandboxConfiguration::MIN_MAX_INITIALIZATION_TIME..=SandboxConfiguration::MIN_MAX_INITIALIZATION_TIME * 10) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_max_initialization_time(std::time::Duration::from_millis(time.into())); - prop_assert_eq!(time, cfg.get_max_initialization_time()); - } - - #[test] - fn stack_size_override(size in 0x1000..=0x10000u64) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_stack_size(size); - prop_assert_eq!(size, cfg.stack_size_override); - } - - #[test] - fn heap_size_override(size in 0x1000..=0x10000u64) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(size); - prop_assert_eq!(size, cfg.heap_size_override); - } - - #[test] - #[cfg(gdb)] - fn guest_debug_info(port in 9000..=u16::MAX) { - let mut cfg = SandboxConfiguration::default(); - let debug_info = DebugInfo { port }; - cfg.set_guest_debug_info(debug_info); - prop_assert_eq!(debug_info, *cfg.get_guest_debug_info().as_ref().unwrap()); - } - } + ) } } diff --git a/src/hyperlight_host/src/sandbox/host_funcs.rs b/src/hyperlight_host/src/sandbox/host_funcs.rs index b0d3fa05c..57cbdba4f 100644 --- a/src/hyperlight_host/src/sandbox/host_funcs.rs +++ b/src/hyperlight_host/src/sandbox/host_funcs.rs @@ -79,21 +79,6 @@ impl HostFuncsWrapper { register_host_function_helper(self, mgr, hfd, func, Some(extra_allowed_syscalls)) } - /// Assuming a host function called `"HostPrint"` exists, and takes a - /// single string parameter, call it with the given `msg` parameter. - /// - /// Return `Ok` if the function was found and was of the right signature, - /// and `Err` otherwise. - #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] - pub(super) fn host_print(&mut self, msg: String) -> Result { - let res = call_host_func_impl( - self.get_host_funcs(), - "HostPrint", - vec![ParameterValue::String(msg)], - )?; - res.try_into() - .map_err(|_| HostFunctionNotFound("HostPrint".to_string())) - } /// From the set of registered host functions, attempt to get the one /// named `name`. If it exists, call it with the given arguments list /// `args` and return its result. @@ -113,7 +98,7 @@ impl HostFuncsWrapper { fn register_host_function_helper( self_: &mut HostFuncsWrapper, - mgr: &mut SandboxMemoryManager, + _mgr: &mut SandboxMemoryManager, hfd: &HostFunctionDefinition, func: HyperlightFunction, extra_allowed_syscalls: Option>, @@ -142,13 +127,6 @@ fn register_host_function_helper( self_ .get_host_func_details_mut() .sort_host_functions_by_name(); - let buffer: Vec = self_.get_host_func_details().try_into().map_err(|e| { - new_error!( - "Error serializing host function details to flatbuffer: {}", - e - ) - })?; - mgr.write_buffer_host_function_details(&buffer)?; Ok(()) } diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index d92252a8a..fa38b25ef 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -14,18 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -use std::sync::{Arc, Mutex}; - use hyperlight_common::flatbuffer_wrappers::function_types::{ ParameterValue, ReturnType, ReturnValue, }; use tracing::{instrument, Span}; -use super::host_funcs::HostFuncsWrapper; -use super::{MemMgrWrapper, WrapperGetter}; use crate::func::call_ctx::MultiUseGuestCallContext; use crate::func::guest_dispatch::call_function_on_guest; use crate::hypervisor::hypervisor_handler::HypervisorHandler; +use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox, Sandbox}; use crate::sandbox_state::transition::{MultiUseContextCallback, Noop}; @@ -40,10 +37,8 @@ use crate::Result; /// 2. A MultiUseGuestCallContext can be created from the sandbox and used to make multiple guest function calls to the Sandbox. /// in this case the state of the sandbox is not reset until the context is finished and the `MultiUseSandbox` is returned. pub struct MultiUseSandbox { - // We need to keep a reference to the host functions, even if the compiler marks it as unused. The compiler cannot detect our dynamic usages of the host function in `HyperlightFunction::call`. - pub(super) _host_funcs: Arc>, - pub(crate) mem_mgr: MemMgrWrapper, - hv_handler: HypervisorHandler, + pub(crate) mem_mgr: SandboxMemoryManager, + pub(crate) hv_handler: HypervisorHandler, } // We need to implement drop to join the @@ -73,13 +68,11 @@ impl MultiUseSandbox { /// (as a `From` implementation would be) #[instrument(skip_all, parent = Span::current(), level = "Trace")] pub(super) fn from_uninit( - host_funcs: Arc>, - mgr: MemMgrWrapper, + mem_mgr: SandboxMemoryManager, hv_handler: HypervisorHandler, ) -> MultiUseSandbox { Self { - _host_funcs: host_funcs, - mem_mgr: mgr, + mem_mgr, hv_handler, } } @@ -101,20 +94,20 @@ impl MultiUseSandbox { /// will not be found): /// /// ```no_run - /// use hyperlight_host::sandbox::{UninitializedSandbox, MultiUseSandbox}; + /// use hyperlight_host::sandbox:: MultiUseSandbox; /// use hyperlight_common::flatbuffer_wrappers::function_types::{ReturnType, ParameterValue, ReturnValue}; /// use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; /// use hyperlight_host::sandbox_state::transition::Noop; /// use hyperlight_host::GuestBinary; + /// use hyperlight_host::sandbox::sandbox_builder::SandboxBuilder; + /// use hyperlight_testing::simple_guest_as_string; /// /// // First, create a new uninitialized sandbox, then evolve it to become /// // an initialized, single-use one. - /// let u_sbox = UninitializedSandbox::new( - /// GuestBinary::FilePath("some_guest_binary".to_string()), - /// None, - /// None, - /// None, - /// ).unwrap(); + /// let sandbox_builder = + /// SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string().unwrap())).unwrap(); + /// + /// let mut u_sbox = sandbox_builder.build().unwrap(); /// let sbox: MultiUseSandbox = u_sbox.evolve(Noop::default()).unwrap(); /// // Next, create a new call context from the single-use sandbox. /// // After this line, your code will not compile if you try to use the @@ -125,7 +118,7 @@ impl MultiUseSandbox { /// // ("some_guest_binary") has a function therein called "SomeGuestFunc" /// // that takes a single integer argument and returns an integer. /// match ctx.call( - /// "SomeGuestFunc", + /// "PrintOutput", /// ReturnType::Int, /// Some(vec![ParameterValue::Int(1)]) /// ) { @@ -163,7 +156,13 @@ impl MultiUseSandbox { func_ret_type: ReturnType, args: Option>, ) -> Result { - let res = call_function_on_guest(self, func_name, func_ret_type, args); + let res = call_function_on_guest( + &mut self.hv_handler, + &mut self.mem_mgr, + func_name, + func_ret_type, + args, + ); self.restore_state()?; res } @@ -171,23 +170,7 @@ impl MultiUseSandbox { /// Restore the Sandbox's state #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub(crate) fn restore_state(&mut self) -> Result<()> { - let mem_mgr = self.mem_mgr.unwrap_mgr_mut(); - mem_mgr.restore_state_from_last_snapshot() - } -} - -impl WrapperGetter for MultiUseSandbox { - fn get_mgr_wrapper(&self) -> &MemMgrWrapper { - &self.mem_mgr - } - fn get_mgr_wrapper_mut(&mut self) -> &mut MemMgrWrapper { - &mut self.mem_mgr - } - fn get_hv_handler(&self) -> &HypervisorHandler { - &self.hv_handler - } - fn get_hv_handler_mut(&mut self) -> &mut HypervisorHandler { - &mut self.hv_handler + self.mem_mgr.restore_state_from_last_snapshot() } } @@ -200,7 +183,7 @@ impl Sandbox for MultiUseSandbox { impl std::fmt::Debug for MultiUseSandbox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MultiUseSandbox") - .field("stack_guard", &self.mem_mgr.get_stack_cookie()) + .field("stack_guard", &self.mem_mgr.stack_guard) .finish() } } @@ -217,9 +200,7 @@ impl DevolvableSandbox) -> Result { - self.mem_mgr - .unwrap_mgr_mut() - .pop_and_restore_state_from_snapshot()?; + self.mem_mgr.pop_and_restore_state_from_snapshot()?; Ok(self) } } @@ -240,7 +221,7 @@ where /// /// The evolve function creates a new MultiUseCallContext which is then passed to a callback function allowing the /// callback function to call guest functions as part of the evolve process, once the callback function is complete - /// the context is finished using a crate internal method that does not restore the prior state of the Sanbbox. + /// the context is finished using a crate internal method that does not restore the prior state of the Sandbox. /// It then creates a mew memory snapshot on the snapshot stack and returns the MultiUseSandbox #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] fn evolve( @@ -250,7 +231,7 @@ where let mut ctx = self.new_call_context(); transition_func.call(&mut ctx)?; let mut sbox = ctx.finish_no_reset(); - sbox.mem_mgr.unwrap_mgr_mut().push_state()?; + sbox.mem_mgr.push_state()?; Ok(sbox) } } @@ -263,70 +244,21 @@ mod tests { use hyperlight_testing::simple_guest_as_string; use crate::func::call_ctx::MultiUseGuestCallContext; - use crate::sandbox::SandboxConfiguration; + use crate::sandbox::sandbox_builder::SandboxBuilder; use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox}; use crate::sandbox_state::transition::{MultiUseContextCallback, Noop}; - use crate::{GuestBinary, MultiUseSandbox, UninitializedSandbox}; - - // Tests to ensure that many (1000) function calls can be made in a call context with a small stack (1K) and heap(14K). - // This test effectively ensures that the stack is being properly reset after each call and we are not leaking memory in the Guest. - #[test] - fn test_with_small_stack_and_heap() { - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(20 * 1024); - cfg.set_stack_size(16 * 1024); - - let sbox1: MultiUseSandbox = { - let path = simple_guest_as_string().unwrap(); - let u_sbox = - UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg), None, None) - .unwrap(); - u_sbox.evolve(Noop::default()) - } - .unwrap(); - - let mut ctx = sbox1.new_call_context(); - - for _ in 0..1000 { - ctx.call( - "Echo", - ReturnType::String, - Some(vec![ParameterValue::String("hello".to_string())]), - ) - .unwrap(); - } - - let sbox2: MultiUseSandbox = { - let path = simple_guest_as_string().unwrap(); - let u_sbox = - UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg), None, None) - .unwrap(); - u_sbox.evolve(Noop::default()) - } - .unwrap(); - - let mut ctx = sbox2.new_call_context(); - - for i in 0..1000 { - ctx.call( - "PrintUsingPrintf", - ReturnType::Int, - Some(vec![ParameterValue::String( - format!("Hello World {}\n", i).to_string(), - )]), - ) - .unwrap(); - } - } + use crate::{GuestBinary, MultiUseSandbox}; /// Tests that evolving from MultiUseSandbox to MultiUseSandbox creates a new state /// and devolving from MultiUseSandbox to MultiUseSandbox restores the previous state #[test] fn evolve_devolve_handles_state_correctly() { let sbox1: MultiUseSandbox = { - let path = simple_guest_as_string().unwrap(); - let u_sbox = - UninitializedSandbox::new(GuestBinary::FilePath(path), None, None, None).unwrap(); + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string().unwrap())) + .unwrap(); + + let u_sbox = sandbox_builder.build().unwrap(); u_sbox.evolve(Noop::default()) } .unwrap(); diff --git a/src/hyperlight_host/src/sandbox/leaked_outb.rs b/src/hyperlight_host/src/sandbox/leaked_outb.rs index 87fc6c344..706b92c34 100644 --- a/src/hyperlight_host/src/sandbox/leaked_outb.rs +++ b/src/hyperlight_host/src/sandbox/leaked_outb.rs @@ -29,8 +29,11 @@ use crate::mem::shared_mem::GuestSharedMemory; /// /// NOTE: This is not part of the C Hyperlight API , it is intended only to be /// called in proc through a pointer passed to the guest. -extern "win64" fn call_outb(ptr: *mut Arc>, port: u16, data: u64) { - let outb_handlercaller = unsafe { Box::from_raw(ptr) }; +extern "sysv64" fn call_outb(ptr: *mut core::ffi::c_void, port: u16, data: u64) { + // Convert from *mut core::ffi::c_void to *mut Arc> + let outb_handlercaller = + unsafe { Box::from_raw(ptr as *mut Arc>) }; + let res = outb_handlercaller .try_lock() .map_err(|_| crate::new_error!("Error locking")) @@ -99,7 +102,11 @@ impl<'a> LeakedOutBWrapper<'a> { }; let addr: u64 = res.hdl_wrapper_addr()?; - mgr.set_outb_address_and_context(Self::outb_addr(), addr)?; + let mut peb = mgr.memory_sections.read_hyperlight_peb()?; + peb.set_outb_ptr(Self::outb_addr()); + peb.set_outb_ptr_ctx(addr); + mgr.memory_sections.write_hyperlight_peb(peb)?; + Ok(res) } diff --git a/src/hyperlight_host/src/sandbox/mem_access.rs b/src/hyperlight_host/src/sandbox/mem_access.rs index 7bf4fbdc2..bf9c0277d 100644 --- a/src/hyperlight_host/src/sandbox/mem_access.rs +++ b/src/hyperlight_host/src/sandbox/mem_access.rs @@ -18,18 +18,20 @@ use std::sync::{Arc, Mutex}; use tracing::{instrument, Span}; -use super::mem_mgr::MemMgrWrapper; use crate::error::HyperlightError::StackOverflow; #[cfg(gdb)] use crate::hypervisor::handlers::{DbgMemAccessHandlerCaller, DbgMemAccessHandlerWrapper}; use crate::hypervisor::handlers::{ MemAccessHandler, MemAccessHandlerFunction, MemAccessHandlerWrapper, }; +use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; use crate::{log_then_return, Result}; #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] -pub(super) fn handle_mem_access_impl(wrapper: &MemMgrWrapper) -> Result<()> { +pub(super) fn handle_mem_access_impl( + wrapper: &SandboxMemoryManager, +) -> Result<()> { if !wrapper.check_stack_guard()? { log_then_return!(StackOverflow()); } @@ -39,7 +41,7 @@ pub(super) fn handle_mem_access_impl(wrapper: &MemMgrWrapper) #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn mem_access_handler_wrapper( - wrapper: MemMgrWrapper, + wrapper: SandboxMemoryManager, ) -> MemAccessHandlerWrapper { let mem_access_func: MemAccessHandlerFunction = Box::new(move || handle_mem_access_impl(&wrapper)); @@ -49,37 +51,35 @@ pub(crate) fn mem_access_handler_wrapper( #[cfg(gdb)] struct DbgMemAccessContainer { - wrapper: MemMgrWrapper, + wrapper: SandboxMemoryManager, } #[cfg(gdb)] impl DbgMemAccessHandlerCaller for DbgMemAccessContainer { #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - fn read(&mut self, addr: usize, data: &mut [u8]) -> Result<()> { - self.wrapper - .unwrap_mgr_mut() - .get_shared_mem_mut() - .copy_to_slice(data, addr) + fn read(&mut self, addr: usize, data: &mut [u8]) -> crate::Result<()> { + self.wrapper.shared_mem.copy_to_slice(data, addr) } #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - fn write(&mut self, addr: usize, data: &[u8]) -> Result<()> { - self.wrapper - .unwrap_mgr_mut() - .get_shared_mem_mut() - .copy_from_slice(data, addr) + fn write(&mut self, addr: usize, data: &[u8]) -> crate::Result<()> { + self.wrapper.shared_mem.copy_from_slice(data, addr) } #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - fn get_code_offset(&mut self) -> Result { - Ok(self.wrapper.unwrap_mgr().layout.get_guest_code_address()) + fn get_code_offset(&mut self) -> crate::Result { + Ok(self + .wrapper + .memory_sections + .get_guest_code_offset() + .unwrap()) } } #[cfg(gdb)] #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn dbg_mem_access_handler_wrapper( - wrapper: MemMgrWrapper, + wrapper: SandboxMemoryManager, ) -> DbgMemAccessHandlerWrapper { let container = DbgMemAccessContainer { wrapper }; diff --git a/src/hyperlight_host/src/sandbox/mem_mgr.rs b/src/hyperlight_host/src/sandbox/mem_mgr.rs deleted file mode 100644 index 8099b0991..000000000 --- a/src/hyperlight_host/src/sandbox/mem_mgr.rs +++ /dev/null @@ -1,107 +0,0 @@ -/* -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. -*/ - -use tracing::{instrument, Span}; - -use crate::mem::layout::SandboxMemoryLayout; -use crate::mem::mgr::{SandboxMemoryManager, STACK_COOKIE_LEN}; -use crate::mem::shared_mem::{ - ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemory, SharedMemory, -}; -use crate::Result; - -/// StackCookie -pub type StackCookie = [u8; STACK_COOKIE_LEN]; - -/// A container with methods for accessing `SandboxMemoryManager` and other -/// related objects -#[derive(Clone)] -pub(crate) struct MemMgrWrapper(SandboxMemoryManager, StackCookie); - -impl MemMgrWrapper { - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn new(mgr: SandboxMemoryManager, stack_cookie: StackCookie) -> Self { - Self(mgr, stack_cookie) - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn unwrap_mgr(&self) -> &SandboxMemoryManager { - &self.0 - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn unwrap_mgr_mut(&mut self) -> &mut SandboxMemoryManager { - &mut self.0 - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn get_stack_cookie(&self) -> &StackCookie { - &self.1 - } -} - -impl AsMut> for MemMgrWrapper { - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn as_mut(&mut self) -> &mut SandboxMemoryManager { - self.unwrap_mgr_mut() - } -} - -impl AsRef> for MemMgrWrapper { - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn as_ref(&self) -> &SandboxMemoryManager { - self.unwrap_mgr() - } -} - -impl MemMgrWrapper { - pub(crate) fn build( - self, - ) -> ( - MemMgrWrapper, - SandboxMemoryManager, - ) { - let (hshm, gshm) = self.0.build(); - (MemMgrWrapper(hshm, self.1), gshm) - } - - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(super) fn write_memory_layout(&mut self, run_inprocess: bool) -> Result<()> { - let mgr = self.unwrap_mgr_mut(); - let layout = mgr.layout; - let shared_mem = mgr.get_shared_mem_mut(); - let mem_size = shared_mem.mem_size(); - let guest_offset = if run_inprocess { - shared_mem.base_addr() - } else { - SandboxMemoryLayout::BASE_ADDRESS - }; - layout.write(shared_mem, guest_offset, mem_size, run_inprocess) - } -} - -impl MemMgrWrapper { - /// Check the stack guard against the given `stack_cookie`. - /// - /// Return `Ok(true)` if the given cookie matches the one in guest memory, - /// and `Ok(false)` otherwise. Return `Err` if it could not be found or - /// there was some other error. - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn check_stack_guard(&self) -> Result { - self.unwrap_mgr() - .check_stack_guard(*self.get_stack_cookie()) - } -} diff --git a/src/hyperlight_host/src/sandbox/mod.rs b/src/hyperlight_host/src/sandbox/mod.rs index 2b45864f0..f86255990 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -32,9 +32,6 @@ pub(crate) mod leaked_outb; /// Functionality for dealing with memory access from the VM guest /// executable pub(crate) mod mem_access; -/// Functionality for interacting with a sandbox's internally-stored -/// `SandboxMemoryManager` -pub(crate) mod mem_mgr; pub(crate) mod outb; /// Options for configuring a sandbox mod run_options; @@ -45,23 +42,22 @@ pub mod uninitialized; /// initialized `Sandbox`es. pub(crate) mod uninitialized_evolve; +/// Functionality for creating a sandbox with a specific configuration +pub mod sandbox_builder; + use std::collections::HashMap; -/// Re-export for `SandboxConfiguration` type -pub use config::SandboxConfiguration; /// Re-export for the `MultiUseSandbox` type pub use initialized_multi_use::MultiUseSandbox; /// Re-export for `SandboxRunOptions` type pub use run_options::SandboxRunOptions; -use tracing::{instrument, Span}; /// Re-export for `GuestBinary` type -pub use uninitialized::GuestBinary; +pub use sandbox_builder::GuestBinary; +use tracing::{instrument, Span}; /// Re-export for `UninitializedSandbox` type pub use uninitialized::UninitializedSandbox; -use self::mem_mgr::MemMgrWrapper; use crate::func::HyperlightFunction; -use crate::hypervisor::hypervisor_handler::HypervisorHandler; #[cfg(target_os = "windows")] use crate::hypervisor::windows_hypervisor_platform; use crate::mem::shared_mem::HostSharedMemory; @@ -138,27 +134,20 @@ pub fn is_hypervisor_present() -> bool { hypervisor::get_available_hypervisor().is_some() } -pub(crate) trait WrapperGetter { - #[allow(dead_code)] - fn get_mgr_wrapper(&self) -> &MemMgrWrapper; - fn get_mgr_wrapper_mut(&mut self) -> &mut MemMgrWrapper; - fn get_hv_handler(&self) -> &HypervisorHandler; - #[allow(dead_code)] - fn get_hv_handler_mut(&mut self) -> &mut HypervisorHandler; -} - #[cfg(test)] mod tests { - use std::sync::Arc; + use std::sync::{Arc, Mutex}; use std::thread; use crossbeam_queue::ArrayQueue; + use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; use hyperlight_testing::simple_guest_as_string; - use crate::sandbox::uninitialized::GuestBinary; + use crate::func::HostFunction2; + use crate::sandbox::sandbox_builder::SandboxBuilder; use crate::sandbox_state::sandbox::EvolvableSandbox; use crate::sandbox_state::transition::Noop; - use crate::{new_error, MultiUseSandbox, UninitializedSandbox}; + use crate::{GuestBinary, MultiUseSandbox, Result, UninitializedSandbox}; #[test] // TODO: add support for testing on WHP @@ -180,22 +169,24 @@ mod tests { } #[test] - fn check_create_and_use_sandbox_on_different_threads() { + fn check_create_and_use_sandbox_on_different_threads() -> Result<()> { let unintializedsandbox_queue = Arc::new(ArrayQueue::::new(10)); let sandbox_queue = Arc::new(ArrayQueue::::new(10)); for i in 0..10 { - let simple_guest_path = simple_guest_as_string().expect("Guest Binary Missing"); - let unintializedsandbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_path), - None, - None, - None, - ) - .unwrap_or_else(|_| panic!("Failed to create UninitializedSandbox {}", i)); + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + let mut uninitialized_sandbox = sandbox_builder.build()?; + + fn add(a: i32, b: i32) -> crate::Result { + Ok(a + b) + } + let host_function = Arc::new(Mutex::new(add)); + host_function.register(&mut uninitialized_sandbox, "HostAdd")?; unintializedsandbox_queue - .push(unintializedsandbox) + .push(uninitialized_sandbox) .unwrap_or_else(|_| panic!("Failed to push UninitializedSandbox {}", i)); } @@ -207,20 +198,6 @@ mod tests { let uninitialized_sandbox = uq.pop().unwrap_or_else(|| { panic!("Failed to pop UninitializedSandbox thread {}", i) }); - let host_funcs = uninitialized_sandbox - .host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - host_funcs - .unwrap() - .host_print(format!( - "Printing from UninitializedSandbox on Thread {}\n", - i - )) - .unwrap(); let sandbox = uninitialized_sandbox .evolve(Noop::default()) @@ -243,20 +220,17 @@ mod tests { .map(|i| { let sq = sandbox_queue.clone(); thread::spawn(move || { - let sandbox = sq + let mut sandbox = sq .pop() .unwrap_or_else(|| panic!("Failed to pop Sandbox thread {}", i)); - let host_funcs = sandbox - ._host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - assert!(host_funcs.is_ok()); + let result = sandbox.call_guest_function_by_name( + "Add", + ReturnType::Int, + Some(vec![ParameterValue::Int(1), ParameterValue::Int(2)]), + ); - host_funcs - .unwrap() - .host_print(format!("Print from Sandbox on Thread {}\n", i)) - .unwrap(); + result.unwrap_or_else(|_| panic!("Failed to call guest function thread {}", i)); }) }) .collect::>(); @@ -264,5 +238,7 @@ mod tests { for handle in thread_handles { handle.join().unwrap(); } + + Ok(()) } } diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 3679cb2e9..e32a87ec1 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -19,36 +19,17 @@ use std::sync::{Arc, Mutex}; use hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData; +use hyperlight_common::outb::OutBAction; use log::{Level, Record}; use tracing::{instrument, Span}; use tracing_log::format_trace; use super::host_funcs::HostFuncsWrapper; -use super::mem_mgr::MemMgrWrapper; use crate::hypervisor::handlers::{OutBHandler, OutBHandlerFunction, OutBHandlerWrapper}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::HostSharedMemory; use crate::{new_error, HyperlightError, Result}; -pub(super) enum OutBAction { - Log, - CallFunction, - Abort, -} - -impl TryFrom for OutBAction { - type Error = HyperlightError; - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn try_from(val: u16) -> Result { - match val { - 99 => Ok(OutBAction::Log), - 101 => Ok(OutBAction::CallFunction), - 102 => Ok(OutBAction::Abort), - _ => Err(new_error!("Invalid OutB value: {}", val)), - } - } -} - #[instrument(err(Debug), skip_all, parent = Span::current(), level="Trace")] pub(super) fn outb_log(mgr: &mut SandboxMemoryManager) -> Result<()> { // This code will create either a logging record or a tracing record for the GuestLogData depending on if the host has set up a tracing subscriber. @@ -63,7 +44,7 @@ pub(super) fn outb_log(mgr: &mut SandboxMemoryManager) -> Resu let record_level: Level = (&log_data.level).into(); // Work out if we need to log or trace - // this API is marked as follows but it is the easiest way to work out if we should trace or log + // this API is marked as follows, but it is the easiest way to work out if we should trace or log // Private API for internal use by tracing's macros. // @@ -114,30 +95,28 @@ pub(super) fn outb_log(mgr: &mut SandboxMemoryManager) -> Resu /// Handles OutB operations from the guest. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] fn handle_outb_impl( - mem_mgr: &mut MemMgrWrapper, + mem_mgr: &mut SandboxMemoryManager, host_funcs: Arc>, port: u16, byte: u64, ) -> Result<()> { match port.try_into()? { - OutBAction::Log => outb_log(mem_mgr.as_mut()), + OutBAction::Log => outb_log(mem_mgr), OutBAction::CallFunction => { - let call = mem_mgr.as_mut().get_host_function_call()?; // pop output buffer + let call = mem_mgr.get_host_function_call()?; // pop output buffer let name = call.function_name.clone(); let args: Vec = call.parameters.unwrap_or(vec![]); let res = host_funcs .try_lock() .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? .call_host_function(&name, args)?; - mem_mgr - .as_mut() - .write_response_from_host_method_call(&res)?; // push input buffers + mem_mgr.write_response_from_host_method_call(&res)?; // push input buffers Ok(()) } OutBAction::Abort => { let guest_error = ErrorCode::from(byte); - let panic_context = mem_mgr.as_mut().read_guest_panic_context_data()?; + let panic_context = mem_mgr.read_guest_panic_context_data()?; // trim off trailing \0 bytes if they exist let index_opt = panic_context.iter().position(|&x| x == 0x00); let trimmed = match index_opt { @@ -153,6 +132,17 @@ fn handle_outb_impl( )), } } + OutBAction::DebugPrint => { + let ch: char = match char::from_u32(byte as u32) { + Some(c) => c, + None => { + return Err(new_error!("Invalid character for logging: {}", byte)); + } + }; + + eprint!("{}", ch); + Ok(()) + } } } @@ -162,7 +152,7 @@ fn handle_outb_impl( /// TODO: pass at least the `host_funcs_wrapper` param by reference. #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn outb_handler_wrapper( - mut mem_mgr_wrapper: MemMgrWrapper, + mut mem_mgr_wrapper: SandboxMemoryManager, host_funcs_wrapper: Arc>, ) -> OutBHandlerWrapper { let outb_func: OutBHandlerFunction = Box::new(move |port, payload| { @@ -177,281 +167,281 @@ pub(crate) fn outb_handler_wrapper( Arc::new(Mutex::new(outb_hdl)) } -#[cfg(test)] -mod tests { - use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel; - use hyperlight_testing::logger::{Logger, LOGGER}; - use log::Level; - use tracing_core::callsite::rebuild_interest_cache; - - use super::outb_log; - use crate::mem::layout::SandboxMemoryLayout; - use crate::mem::mgr::SandboxMemoryManager; - use crate::mem::shared_mem::SharedMemory; - use crate::sandbox::outb::GuestLogData; - use crate::sandbox::SandboxConfiguration; - use crate::testing::log_values::test_value_as_str; - use crate::testing::simple_guest_exe_info; - - fn new_guest_log_data(level: LogLevel) -> GuestLogData { - GuestLogData::new( - "test log".to_string(), - "test source".to_string(), - level, - "test caller".to_string(), - "test source file".to_string(), - 123, - ) - } - - #[test] - #[ignore] - fn test_log_outb_log() { - Logger::initialize_test_logger(); - LOGGER.set_max_level(log::LevelFilter::Off); - - let sandbox_cfg = SandboxConfiguration::default(); - - let new_mgr = || { - let mut exe_info = simple_guest_exe_info().unwrap(); - let mut mgr = SandboxMemoryManager::load_guest_binary_into_memory( - sandbox_cfg, - &mut exe_info, - false, - ) - .unwrap(); - let mem_size = mgr.get_shared_mem_mut().mem_size(); - let layout = mgr.layout; - let shared_mem = mgr.get_shared_mem_mut(); - layout - .write( - shared_mem, - SandboxMemoryLayout::BASE_ADDRESS, - mem_size, - false, - ) - .unwrap(); - let (hmgr, _) = mgr.build(); - hmgr - }; - { - // We set a logger but there is no guest log data - // in memory, so expect a log operation to fail - let mut mgr = new_mgr(); - assert!(outb_log(&mut mgr).is_err()); - } - { - // Write a log message so outb_log will succeed. - // Since the logger level is set off, expect logs to be no-ops - let mut mgr = new_mgr(); - let log_msg = new_guest_log_data(LogLevel::Information); - - let guest_log_data_buffer: Vec = log_msg.try_into().unwrap(); - let offset = mgr.layout.get_output_data_offset(); - mgr.get_shared_mem_mut() - .push_buffer( - offset, - sandbox_cfg.get_output_data_size(), - &guest_log_data_buffer, - ) - .unwrap(); - - let res = outb_log(&mut mgr); - assert!(res.is_ok()); - assert_eq!(0, LOGGER.num_log_calls()); - LOGGER.clear_log_calls(); - } - { - // now, test logging - LOGGER.set_max_level(log::LevelFilter::Trace); - let mut mgr = new_mgr(); - LOGGER.clear_log_calls(); - - // set up the logger and set the log level to the maximum - // possible (Trace) to ensure we're able to test all - // the possible branches of the match in outb_log - - let levels = vec![ - LogLevel::Trace, - LogLevel::Debug, - LogLevel::Information, - LogLevel::Warning, - LogLevel::Error, - LogLevel::Critical, - LogLevel::None, - ]; - for level in levels { - let layout = mgr.layout; - let log_data = new_guest_log_data(level); - - let guest_log_data_buffer: Vec = log_data.clone().try_into().unwrap(); - mgr.get_shared_mem_mut() - .push_buffer( - layout.get_output_data_offset(), - sandbox_cfg.get_output_data_size(), - guest_log_data_buffer.as_slice(), - ) - .unwrap(); - - outb_log(&mut mgr).unwrap(); - - LOGGER.test_log_records(|log_calls| { - let expected_level: Level = (&level).into(); - - assert!( - log_calls - .iter() - .filter(|log_call| { - log_call.level == expected_level - && log_call.line == Some(log_data.line) - && log_call.args == log_data.message - && log_call.module_path == Some(log_data.source.clone()) - && log_call.file == Some(log_data.source_file.clone()) - }) - .count() - == 1, - "log call did not occur for level {:?}", - level.clone() - ); - }); - } - } - } - - // Tests that outb_log emits traces when a trace subscriber is set - // this test is ignored because it is incompatible with other tests , specifically those which require a logger for tracing - // marking this test as ignored means that running `cargo test` will not run this test but will allow a developer who runs that command - // from their workstation to be successful without needed to know about test interdependencies - // this test will be run explicitly as a part of the CI pipeline - #[ignore] - #[test] - fn test_trace_outb_log() { - Logger::initialize_log_tracer(); - rebuild_interest_cache(); - let subscriber = - hyperlight_testing::tracing_subscriber::TracingSubscriber::new(tracing::Level::TRACE); - let sandbox_cfg = SandboxConfiguration::default(); - tracing::subscriber::with_default(subscriber.clone(), || { - let new_mgr = || { - let mut exe_info = simple_guest_exe_info().unwrap(); - let mut mgr = SandboxMemoryManager::load_guest_binary_into_memory( - sandbox_cfg, - &mut exe_info, - false, - ) - .unwrap(); - let mem_size = mgr.get_shared_mem_mut().mem_size(); - let layout = mgr.layout; - let shared_mem = mgr.get_shared_mem_mut(); - layout - .write( - shared_mem, - SandboxMemoryLayout::BASE_ADDRESS, - mem_size, - false, - ) - .unwrap(); - let (hmgr, _) = mgr.build(); - hmgr - }; - - // as a span does not exist one will be automatically created - // after that there will be an event for each log message - // we are interested only in the events for the log messages that we created - - let levels = vec![ - LogLevel::Trace, - LogLevel::Debug, - LogLevel::Information, - LogLevel::Warning, - LogLevel::Error, - LogLevel::Critical, - LogLevel::None, - ]; - for level in levels { - let mut mgr = new_mgr(); - let layout = mgr.layout; - let log_data: GuestLogData = new_guest_log_data(level); - subscriber.clear(); - - let guest_log_data_buffer: Vec = log_data.try_into().unwrap(); - mgr.get_shared_mem_mut() - .push_buffer( - layout.get_output_data_offset(), - sandbox_cfg.get_output_data_size(), - guest_log_data_buffer.as_slice(), - ) - .unwrap(); - subscriber.clear(); - outb_log(&mut mgr).unwrap(); - - subscriber.test_trace_records(|spans, events| { - let expected_level = match level { - LogLevel::Trace => "TRACE", - LogLevel::Debug => "DEBUG", - LogLevel::Information => "INFO", - LogLevel::Warning => "WARN", - LogLevel::Error => "ERROR", - LogLevel::Critical => "ERROR", - LogLevel::None => "TRACE", - }; - - // We cannot get the parent span using the `current_span()` method as by the time we get to this point that span has been exited so there is no current span - // We need to make sure that the span that we created is in the spans map instead - // We expect to have created 21 spans at this point. We are only interested in the first one that was created when calling outb_log. - - assert!( - spans.len() == 21, - "expected 21 spans, found {}", - spans.len() - ); - - let span_value = spans - .get(&1) - .unwrap() - .as_object() - .unwrap() - .get("span") - .unwrap() - .get("attributes") - .unwrap() - .as_object() - .unwrap() - .get("metadata") - .unwrap() - .as_object() - .unwrap(); - - //test_value_as_str(span_value, "level", "INFO"); - test_value_as_str(span_value, "module_path", "hyperlight_host::sandbox::outb"); - let expected_file = if cfg!(windows) { - "src\\hyperlight_host\\src\\sandbox\\outb.rs" - } else { - "src/hyperlight_host/src/sandbox/outb.rs" - }; - test_value_as_str(span_value, "file", expected_file); - test_value_as_str(span_value, "target", "hyperlight_host::sandbox::outb"); - - let mut count_matching_events = 0; - - for json_value in events { - let event_values = json_value.as_object().unwrap().get("event").unwrap(); - let metadata_values_map = - event_values.get("metadata").unwrap().as_object().unwrap(); - let event_values_map = event_values.as_object().unwrap(); - test_value_as_str(metadata_values_map, "level", expected_level); - test_value_as_str(event_values_map, "log.file", "test source file"); - test_value_as_str(event_values_map, "log.module_path", "test source"); - test_value_as_str(event_values_map, "log.target", "hyperlight_guest"); - count_matching_events += 1; - } - assert!( - count_matching_events == 1, - "trace log call did not occur for level {:?}", - level.clone() - ); - }); - } - }); - } -} +// TODO(danbugs:297): bring back +// #[cfg(test)] +// mod tests { +// use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel; +// use hyperlight_testing::logger::{Logger, LOGGER}; +// use log::Level; +// use tracing_core::callsite::rebuild_interest_cache; +// +// use super::outb_log; +// use crate::mem::mgr::SandboxMemoryManager; +// use crate::mem::shared_mem::SharedMemory; +// use crate::sandbox::outb::GuestLogData; +// use crate::sandbox::SandboxConfiguration; +// use crate::testing::log_values::test_value_as_str; +// use crate::testing::simple_guest_exe_info; +// +// fn new_guest_log_data(level: LogLevel) -> GuestLogData { +// GuestLogData::new( +// "test log".to_string(), +// "test source".to_string(), +// level, +// "test caller".to_string(), +// "test source file".to_string(), +// 123, +// ) +// } +// +// #[test] +// #[ignore] +// fn test_log_outb_log() { +// Logger::initialize_test_logger(); +// LOGGER.set_max_level(log::LevelFilter::Off); +// +// let sandbox_cfg = SandboxConfiguration::default(); +// +// let new_mgr = || { +// let mut exe_info = simple_guest_exe_info().unwrap(); +// let mut mgr = SandboxMemoryManager::load_guest_binary_into_memory( +// sandbox_cfg, +// &mut exe_info, +// false, +// ) +// .unwrap(); +// let mem_size = mgr.get_shared_mem_mut().mem_size(); +// let layout = mgr.layout; +// let shared_mem = mgr.get_shared_mem_mut(); +// layout +// .write( +// shared_mem, +// SandboxMemoryLayout::BASE_ADDRESS, +// mem_size, +// false, +// ) +// .unwrap(); +// let (hmgr, _) = mgr.build(); +// hmgr +// }; +// { +// // We set a logger but there is no guest log data +// // in memory, so expect a log operation to fail +// let mut mgr = new_mgr(); +// assert!(outb_log(&mut mgr).is_err()); +// } +// { +// // Write a log message so outb_log will succeed. +// // Since the logger level is set off, expect logs to be no-ops +// let mut mgr = new_mgr(); +// let log_msg = new_guest_log_data(LogLevel::Information); +// +// let guest_log_data_buffer: Vec = log_msg.try_into().unwrap(); +// let offset = mgr.layout.get_output_data_offset(); +// mgr.get_shared_mem_mut() +// .push_buffer( +// offset, +// sandbox_cfg.get_output_data_size(), +// &guest_log_data_buffer, +// ) +// .unwrap(); +// +// let res = outb_log(&mut mgr); +// assert!(res.is_ok()); +// assert_eq!(0, LOGGER.num_log_calls()); +// LOGGER.clear_log_calls(); +// } +// { +// // now, test logging +// LOGGER.set_max_level(log::LevelFilter::Trace); +// let mut mgr = new_mgr(); +// LOGGER.clear_log_calls(); +// +// // set up the logger and set the log level to the maximum +// // possible (Trace) to ensure we're able to test all +// // the possible branches of the match in outb_log +// +// let levels = vec![ +// LogLevel::Trace, +// LogLevel::Debug, +// LogLevel::Information, +// LogLevel::Warning, +// LogLevel::Error, +// LogLevel::Critical, +// LogLevel::None, +// ]; +// for level in levels { +// let layout = mgr.layout; +// let log_data = new_guest_log_data(level); +// +// let guest_log_data_buffer: Vec = log_data.clone().try_into().unwrap(); +// mgr.get_shared_mem_mut() +// .push_buffer( +// layout.get_output_data_offset(), +// sandbox_cfg.get_output_data_size(), +// guest_log_data_buffer.as_slice(), +// ) +// .unwrap(); +// +// outb_log(&mut mgr).unwrap(); +// +// LOGGER.test_log_records(|log_calls| { +// let expected_level: Level = (&level).into(); +// +// assert!( +// log_calls +// .iter() +// .filter(|log_call| { +// log_call.level == expected_level +// && log_call.line == Some(log_data.line) +// && log_call.args == log_data.message +// && log_call.module_path == Some(log_data.source.clone()) +// && log_call.file == Some(log_data.source_file.clone()) +// }) +// .count() +// == 1, +// "log call did not occur for level {:?}", +// level.clone() +// ); +// }); +// } +// } +// } +// +// // Tests that outb_log emits traces when a trace subscriber is set +// // this test is ignored because it is incompatible with other tests , specifically those which require a logger for tracing +// // marking this test as ignored means that running `cargo test` will not run this test but will allow a developer who runs that command +// // from their workstation to be successful without needed to know about test interdependencies +// // this test will be run explicitly as a part of the CI pipeline +// #[ignore] +// #[test] +// fn test_trace_outb_log() { +// Logger::initialize_log_tracer(); +// rebuild_interest_cache(); +// let subscriber = +// hyperlight_testing::tracing_subscriber::TracingSubscriber::new(tracing::Level::TRACE); +// let sandbox_cfg = SandboxConfiguration::default(); +// tracing::subscriber::with_default(subscriber.clone(), || { +// let new_mgr = || { +// let mut exe_info = simple_guest_exe_info().unwrap(); +// let mut mgr = SandboxMemoryManager::load_guest_binary_into_memory( +// sandbox_cfg, +// &mut exe_info, +// false, +// ) +// .unwrap(); +// let mem_size = mgr.get_shared_mem_mut().mem_size(); +// let layout = mgr.layout; +// let shared_mem = mgr.get_shared_mem_mut(); +// layout +// .write( +// shared_mem, +// SandboxMemoryLayout::BASE_ADDRESS, +// mem_size, +// false, +// ) +// .unwrap(); +// let (hmgr, _) = mgr.build(); +// hmgr +// }; +// +// // as a span does not exist one will be automatically created +// // after that there will be an event for each log message +// // we are interested only in the events for the log messages that we created +// +// let levels = vec![ +// LogLevel::Trace, +// LogLevel::Debug, +// LogLevel::Information, +// LogLevel::Warning, +// LogLevel::Error, +// LogLevel::Critical, +// LogLevel::None, +// ]; +// for level in levels { +// let mut mgr = new_mgr(); +// let layout = mgr.layout; +// let log_data: GuestLogData = new_guest_log_data(level); +// subscriber.clear(); +// +// let guest_log_data_buffer: Vec = log_data.try_into().unwrap(); +// mgr.get_shared_mem_mut() +// .push_buffer( +// layout.get_output_data_offset(), +// sandbox_cfg.get_output_data_size(), +// guest_log_data_buffer.as_slice(), +// ) +// .unwrap(); +// subscriber.clear(); +// outb_log(&mut mgr).unwrap(); +// +// subscriber.test_trace_records(|spans, events| { +// let expected_level = match level { +// LogLevel::Trace => "TRACE", +// LogLevel::Debug => "DEBUG", +// LogLevel::Information => "INFO", +// LogLevel::Warning => "WARN", +// LogLevel::Error => "ERROR", +// LogLevel::Critical => "ERROR", +// LogLevel::None => "TRACE", +// }; +// +// // We cannot get the parent span using the `current_span()` method as by the time we get to this point that span has been exited so there is no current span +// // We need to make sure that the span that we created is in the spans map instead +// // We expect to have created 21 spans at this point. We are only interested in the first one that was created when calling outb_log. +// +// assert!( +// spans.len() == 21, +// "expected 21 spans, found {}", +// spans.len() +// ); +// +// let span_value = spans +// .get(&1) +// .unwrap() +// .as_object() +// .unwrap() +// .get("span") +// .unwrap() +// .get("attributes") +// .unwrap() +// .as_object() +// .unwrap() +// .get("metadata") +// .unwrap() +// .as_object() +// .unwrap(); +// +// //test_value_as_str(span_value, "level", "INFO"); +// test_value_as_str(span_value, "module_path", "hyperlight_host::sandbox::outb"); +// let expected_file = if cfg!(windows) { +// "src\\hyperlight_host\\src\\sandbox\\outb.rs" +// } else { +// "src/hyperlight_host/src/sandbox/outb.rs" +// }; +// test_value_as_str(span_value, "file", expected_file); +// test_value_as_str(span_value, "target", "hyperlight_host::sandbox::outb"); +// +// let mut count_matching_events = 0; +// +// for json_value in events { +// let event_values = json_value.as_object().unwrap().get("event").unwrap(); +// let metadata_values_map = +// event_values.get("metadata").unwrap().as_object().unwrap(); +// let event_values_map = event_values.as_object().unwrap(); +// test_value_as_str(metadata_values_map, "level", expected_level); +// test_value_as_str(event_values_map, "log.file", "test source file"); +// test_value_as_str(event_values_map, "log.module_path", "test source"); +// test_value_as_str(event_values_map, "log.target", "hyperlight_guest"); +// count_matching_events += 1; +// } +// assert!( +// count_matching_events == 1, +// "trace log call did not occur for level {:?}", +// level.clone() +// ); +// }); +// } +// }); +// } +// } diff --git a/src/hyperlight_host/src/sandbox/sandbox_builder.rs b/src/hyperlight_host/src/sandbox/sandbox_builder.rs new file mode 100644 index 000000000..72a80cd9e --- /dev/null +++ b/src/hyperlight_host/src/sandbox/sandbox_builder.rs @@ -0,0 +1,1214 @@ +use std::collections::BTreeMap; +use std::fmt::{Debug, Formatter}; +use std::path::Path; +use std::time::Duration; + +use hyperlight_common::peb::{HyperlightPEB, RunMode}; +use hyperlight_common::PAGE_SIZE; + +use crate::mem::exe::ExeInfo; +use crate::mem::mgr::SandboxMemoryManager; +use crate::mem::ptr::RawPtr; +use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory}; +#[cfg(gdb)] +use crate::sandbox::config::DebugInfo; +use crate::sandbox::config::SandboxConfiguration; +use crate::sandbox::Span; +use crate::{ + log_build_details, log_then_return, new_error, Result, SandboxRunOptions, UninitializedSandbox, +}; + +#[cfg(mshv2)] +extern crate mshv_bindings2 as mshv_bindings; + +#[cfg(mshv2)] +use mshv_bindings::{ + HV_MAP_GPA_EXECUTABLE, HV_MAP_GPA_PERMISSIONS_NONE, HV_MAP_GPA_READABLE, HV_MAP_GPA_WRITABLE, +}; + +#[cfg(mshv3)] +extern crate mshv_bindings3 as mshv_bindings; + +#[cfg(mshv3)] +use mshv_bindings::{ + MSHV_SET_MEM_BIT_EXECUTABLE, MSHV_SET_MEM_BIT_UNMAP, MSHV_SET_MEM_BIT_WRITABLE, +}; + +const DEFAULT_GUEST_CODE_SECTION_NAME: &str = "guest code"; +const DEFAULT_TMP_STACK_SECTION_NAME: &str = "tmp stack"; +const DEFAULT_HYPERLIGHT_PEB_SECTION_NAME: &str = "HyperlightPEB"; +const DEFAULT_CUSTOM_GUEST_MEMORY_SECTION_NAME: &str = "custom guest memory"; +const DEFAULT_PAGING_STRUCTURES_SECTION_NAME: &str = "paging structures"; +pub(crate) const BASE_ADDRESS: usize = 0x0; +pub(crate) const PDPT_OFFSET: usize = 0x1000; // this offset is from the PML4 base address +pub(crate) const PD_OFFSET: usize = 0x2000; // this offset is from the PML4 base address +pub(crate) const PT_OFFSET: usize = 0x3000; // this offset is from the PML4 base address +const DEFAULT_GUEST_MEMORY_SIZE: usize = 0x200_000; // 2MB + +/// Represents a memory section in the sandbox. +#[derive(Debug, Clone)] +pub struct SandboxMemorySection { + /// Name of the memory section + pub name: String, + /// Flags for the memory section + pub flags: MemoryRegionFlags, + /// Offset of the memory section in the guest's address space + pub page_aligned_guest_offset: usize, + /// Host address of the memory section + pub host_address: Option, + /// Size of the memory section + pub page_aligned_size: usize, +} + +/// Holds the memory sections of the sandbox. +/// We use a BTreeMap to keep the sections sorted by offset. +#[derive(Clone)] +pub(crate) struct SandboxMemorySections { + /// A map of memory sections, sorted by offset + pub sections: BTreeMap, +} + +impl Debug for SandboxMemorySections { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_map().entries(self.sections.iter()).finish() + } +} + +impl SandboxMemorySections { + pub(crate) fn new() -> Self { + Self { + sections: BTreeMap::new(), + } + } + + pub(crate) fn get_guest_code_offset(&self) -> Option { + self.sections + .iter() + .find(|(_, section)| section.name == DEFAULT_GUEST_CODE_SECTION_NAME) + .map(|(_, section)| section.page_aligned_guest_offset) + } + + pub(crate) fn get_hyperlight_peb_section_offset(&self) -> Option { + self.sections + .iter() + .find(|(_, section)| section.name == DEFAULT_HYPERLIGHT_PEB_SECTION_NAME) + .map(|(_, section)| section.page_aligned_guest_offset) + } + + pub(crate) fn get_hyperlight_peb_section_host_address(&self) -> Option { + self.sections + .iter() + .find(|(_, section)| section.name == DEFAULT_HYPERLIGHT_PEB_SECTION_NAME) + .map(|(_, section)| section.host_address.unwrap()) + } + + pub(crate) fn get_tmp_stack_section_offset(&self) -> Option { + self.sections + .iter() + .find(|(_, section)| section.name == DEFAULT_TMP_STACK_SECTION_NAME) + .map(|(_, section)| section.page_aligned_guest_offset) + } + + pub(crate) fn get_paging_structures_offset(&self) -> Option { + self.sections + .iter() + .find(|(_, section)| section.name == DEFAULT_PAGING_STRUCTURES_SECTION_NAME) + .map(|(_, section)| section.page_aligned_guest_offset) + } + + pub(crate) fn get_custom_guest_memory_section_offset(&self) -> usize { + let offset = self + .sections + .iter() + .find(|(_, section)| section.name == DEFAULT_CUSTOM_GUEST_MEMORY_SECTION_NAME) + .map(|(_, section)| section.page_aligned_guest_offset); + + // if not set, we have a critical error and we should panic + if offset.is_none() { + panic!("Custom guest memory section not found"); + } + + offset.unwrap() + } + + pub(crate) fn get_custom_guest_memory_section_host_address(&self) -> usize { + let host_address = self + .sections + .iter() + .find(|(_, section)| section.name == DEFAULT_CUSTOM_GUEST_MEMORY_SECTION_NAME) + .map(|(_, section)| section.host_address.unwrap()); + + // if not set, we have a critical error and we should panic + if host_address.is_none() { + panic!("Custom guest memory section not found"); + } + + host_address.unwrap() + } + + pub(crate) fn get_custom_guest_memory_size(&self) -> usize { + let size = self + .sections + .iter() + .find(|(_, section)| section.name == DEFAULT_CUSTOM_GUEST_MEMORY_SECTION_NAME) + .map(|(_, section)| section.page_aligned_size); + + // if not set, we have a critical error and we should panic + if size.is_none() { + panic!("Custom guest memory section not found"); + } + + size.unwrap() + } + + pub(crate) fn get_total_size(&self) -> usize { + self.sections + .values() + .map(|section| section.page_aligned_size) + .sum() + } + + pub(crate) fn read_hyperlight_peb(&self) -> Result { + let peb_offset = self + .get_hyperlight_peb_section_host_address() + .ok_or(new_error!("Hyperlight PEB section not found"))? + as *const HyperlightPEB; + + Ok(unsafe { peb_offset.read() }) + } + + pub(crate) fn write_hyperlight_peb(&mut self, peb: HyperlightPEB) -> Result<()> { + let peb_offset = self + .get_hyperlight_peb_section_host_address() + .ok_or(new_error!("Hyperlight PEB section not found"))? + as *mut HyperlightPEB; + + unsafe { peb_offset.copy_from(&peb, 1) }; + + Ok(()) + } + + pub(crate) fn sections(&self) -> impl Iterator { + self.sections.values() + } + + pub(crate) fn insert(&mut self, offset: usize, section: SandboxMemorySection) { + self.sections.insert(offset, section); + } + + pub(crate) fn iter(&self) -> impl Iterator { + self.sections.iter() + } +} + +use bitflags::bitflags; +#[cfg(mshv)] +use mshv_bindings::hv_x64_memory_intercept_message; +use tracing::instrument; +#[cfg(target_os = "windows")] +use windows::Win32::System::Hypervisor::{self, WHV_MEMORY_ACCESS_TYPE}; +bitflags! { + /// flags representing memory permission for a memory region + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct MemoryRegionFlags: u32 { + /// no permissions + const NONE = 0; + /// allow guest to read + const READ = 1; + /// allow guest to write + const WRITE = 2; + /// allow guest to execute + const EXECUTE = 4; + /// identifier that this is a stack guard page + const STACK_GUARD = 8; + } +} + +impl std::fmt::Display for MemoryRegionFlags { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.is_empty() { + write!(f, "NONE") + } else { + let mut first = true; + if self.contains(MemoryRegionFlags::READ) { + write!(f, "READ")?; + first = false; + } + if self.contains(MemoryRegionFlags::WRITE) { + if !first { + write!(f, " | ")?; + } + write!(f, "WRITE")?; + first = false; + } + if self.contains(MemoryRegionFlags::EXECUTE) { + if !first { + write!(f, " | ")?; + } + write!(f, "EXECUTE")?; + } + Ok(()) + } + } +} + +#[cfg(target_os = "windows")] +impl TryFrom for MemoryRegionFlags { + type Error = crate::HyperlightError; + + fn try_from(flags: WHV_MEMORY_ACCESS_TYPE) -> crate::Result { + match flags { + Hypervisor::WHvMemoryAccessRead => Ok(MemoryRegionFlags::READ), + Hypervisor::WHvMemoryAccessWrite => Ok(MemoryRegionFlags::WRITE), + Hypervisor::WHvMemoryAccessExecute => Ok(MemoryRegionFlags::EXECUTE), + _ => Err(crate::HyperlightError::Error( + "unknown memory access type".to_string(), + )), + } + } +} + +#[cfg(mshv)] +impl TryFrom for MemoryRegionFlags { + type Error = crate::HyperlightError; + + fn try_from(msg: hv_x64_memory_intercept_message) -> Result { + let access_type = msg.header.intercept_access_type; + match access_type { + 0 => Ok(MemoryRegionFlags::READ), + 1 => Ok(MemoryRegionFlags::WRITE), + 2 => Ok(MemoryRegionFlags::EXECUTE), + _ => Err(crate::HyperlightError::Error( + "unknown memory access type".to_string(), + )), + } + } +} + +/// A `GuestBinary` is either a buffer containing the binary or a path to the binary +#[derive(Debug)] +pub enum GuestBinary { + /// A buffer containing the guest binary + Buffer(Vec), + /// A path to the guest binary + FilePath(String), +} + +impl GuestBinary { + fn to_exe_info(&self) -> Result { + match self { + GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer), + GuestBinary::FilePath(path) => ExeInfo::from_file(path), + } + } +} + +/// A builder for creating a sandbox. +/// +/// - Example usage: +/// ```no_run +/// use hyperlight_host::{GuestBinary, SandboxRunOptions}; +/// use hyperlight_host::sandbox::sandbox_builder::SandboxBuilder; +/// use hyperlight_testing::simple_guest_as_string; +/// +/// let sandbox_builder = SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string().unwrap())).unwrap() +/// .set_guest_memory_size(0x200_000) +/// .set_max_initialization_time(2000) +/// .set_max_execution_time(1000) +/// .set_sandbox_run_options(SandboxRunOptions::RunInHypervisor).unwrap(); +/// +/// let uninitialized_sandbox = sandbox_builder.build().unwrap(); +/// ``` +pub struct SandboxBuilder { + guest_binary: GuestBinary, + memory_sections: SandboxMemorySections, + init_rsp: Option, + guest_memory_size: usize, + max_initialization_time: Duration, + max_execution_time: Duration, + sandbox_run_options: SandboxRunOptions, + #[cfg(gdb)] + guest_debug_info: Option, +} + +impl SandboxBuilder { + /// Create a new SandboxBuilder with a guest binary. + /// - Sets the default maximum initialization time to 2000ms. + /// - Sets the default maximum execution time to 1000ms. + /// - Resolves the path of the guest binary if it is a file. + /// - Sets the sandbox to run in Hyperlight mode by default. + /// - Adds a memory section for the guest binary. + #[instrument(err(Debug), skip(guest_binary), parent = Span::current())] + pub fn new(guest_binary: GuestBinary) -> Result { + // If the guest binary is a file, resolve the path + let guest_binary = match guest_binary { + GuestBinary::FilePath(binary_path) => { + let path = Path::new(&binary_path) + .canonicalize() + .map_err(|e| new_error!("Guest binary not found: '{}': {}", binary_path, e))?; + GuestBinary::FilePath( + path.into_os_string() + .into_string() + .map_err(|e| new_error!("Error converting OsString to String: {:?}", e))?, + ) + } + buffer @ GuestBinary::Buffer(_) => buffer, + }; + + let sandbox_builder = SandboxBuilder { + guest_binary, + memory_sections: SandboxMemorySections::new(), + init_rsp: None, + guest_memory_size: DEFAULT_GUEST_MEMORY_SIZE, + max_initialization_time: Duration::from_millis( + SandboxConfiguration::DEFAULT_MAX_INITIALIZATION_TIME as u64, + ), + max_execution_time: Duration::from_millis( + SandboxConfiguration::DEFAULT_MAX_EXECUTION_TIME as u64, + ), + sandbox_run_options: SandboxRunOptions::default(), + #[cfg(gdb)] + guest_debug_info: None, + }; + + let guest_binary_size = sandbox_builder.guest_binary.to_exe_info()?.loaded_size(); + + Ok(sandbox_builder.add_memory_section( + DEFAULT_GUEST_CODE_SECTION_NAME, + guest_binary_size, + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE, + )) + } + + /// Set the size of the guest memory. By default, the guest + /// memory is 2MB. + pub fn set_guest_memory_size(mut self, guest_memory_size: usize) -> Self { + self.guest_memory_size = guest_memory_size; + self + } + + /// Set the maximum time the sandbox is allowed to take + /// to initialize (in milliseconds). By default, the sandbox + /// can take 2000ms to initialize. + // TODO: change this to make the default be that the + // sandbox can take as long as it needs to initialize. + pub fn set_max_initialization_time(mut self, max_initialization_time: u64) -> Self { + self.max_initialization_time = Duration::from_millis(max_initialization_time); + self + } + + /// Set the maximum time the sandbox is allowed to take + /// to execute a guest function (in milliseconds). By default, + /// the sandbox can 1000ms to execute a guest function. + // TODO: change this to make the default be that the + // sandbox can take as long as it needs to execute a guest function. + pub fn set_max_execution_time(mut self, max_execution_time: u64) -> Self { + self.max_initialization_time = Duration::from_millis(max_execution_time); + self + } + + /// Set the sandbox run mode. Options: + /// - SandboxRunOptions::RunInHypervisor: Run the sandbox in a hypervisor. + /// - SandboxRunOptions::RunInProcess(false): Run the sandbox in process (i.e., without a + /// hypervisor), but also without using the Windows LoadLibrary to load the guest binary. + /// - SandboxRunOptions::RunInProcess(true): Run the sandbox in process (i.e., without a + /// hypervisor), and use the Windows LoadLibrary to load the guest binary. + pub fn set_sandbox_run_options( + mut self, + sandbox_run_options: SandboxRunOptions, + ) -> Result { + let run_inprocess = sandbox_run_options.in_process(); + let use_loadlib = sandbox_run_options.use_loadlib(); + + if run_inprocess && cfg!(not(inprocess)) { + log_then_return!( + "In process mode is only available in debug builds, and also requires cargo feature 'inprocess'" + ) + } + + if use_loadlib && cfg!(not(all(inprocess, target_os = "windows"))) { + log_then_return!("In process mode with LoadLibrary is only available on Windows") + } + + self.sandbox_run_options = sandbox_run_options; + Ok(self) + } + + /// Set the debug information for the guest binary. + #[cfg(gdb)] + pub fn set_guest_debug_info(mut self, debug_info: DebugInfo) -> Self { + self.guest_debug_info = Some(debug_info); + self + } + + /// Sets the initial RSP value. + fn set_init_rsp(mut self, init_rsp: u64) -> Self { + self.init_rsp = Some(init_rsp); + self + } + + /// Calculate the size of the page table structures. + /// - Iterates through the memory sections and adds their sizes. + /// - Adds the size of the PML4, PDPT, and PD. + /// - Adds the maximum possible size of the PTs. + /// - Gets the number of pages needed. + fn calculate_page_table_size(&self) -> Result { + let mut total_mapped_memory_size: usize = 0; + + // Iterate through the memory sections and add their sizes + total_mapped_memory_size += self.memory_sections.get_total_size(); + + // Add the size of the PML4, PDPT and PD + total_mapped_memory_size += 3 * PAGE_SIZE; + + // Add the maximum possible size of the PTs + total_mapped_memory_size += 512 * PAGE_SIZE; + + // Get the number of pages needed + let num_pages: usize = ((total_mapped_memory_size + crate::mem::mgr::AMOUNT_OF_MEMORY_PER_PT - 1) + / crate::mem::mgr::AMOUNT_OF_MEMORY_PER_PT) + + 1 // Round up + + 3; // PML4, PDPT, PD + + Ok(num_pages * PAGE_SIZE) + } + + fn round_up_to(value: usize, multiple: usize) -> usize { + (value + multiple - 1) & !(multiple - 1) + } + + /// Finds the next available offset that can accommodate a given size. + fn next_free_offset(&self, page_aligned_size: usize) -> usize { + let mut current_offset = 0; + + for section in self.memory_sections.sections() { + let section_start = section.page_aligned_guest_offset; // this is page aligned + let section_end = section.page_aligned_guest_offset + section.page_aligned_size; // this is also page aligned + + // Check if a gap exists before this section that is large enough + if section_start >= current_offset + page_aligned_size { + return current_offset; // Found a suitable gap + } + + // Move to the next available offset after this section + current_offset = section_end; + } + + // If no gaps were found, place the section after the last one + current_offset + } + + /// Adds a memory section without specifying an offset. + /// Automatically finds the next available aligned offset. + fn add_memory_section(mut self, name: &str, size: usize, flags: MemoryRegionFlags) -> Self { + let page_aligned_size = SandboxBuilder::round_up_to(size, PAGE_SIZE); + let page_aligned_guest_offset = self.next_free_offset(page_aligned_size); + + let section = SandboxMemorySection { + name: name.to_string(), + page_aligned_guest_offset, + page_aligned_size, + host_address: None, + flags, + }; + + self.memory_sections + .insert(page_aligned_guest_offset, section); + self + } + + /// Build a default uninitialized sandbox. + /// + /// Default uninitialized sandboxes have the following memory layout: + /// - +--------------------------+ + /// - |Custom guest memory (CGM) | (see note 1) + /// - +--------------------------+ + /// - |CGM Guard page | (4KB) + /// - +--------------------------+ <- initial RSP + /// - |Tmp stack | (16KB) + /// - +--------------------------+ + /// - |Tmp stack guard page | (4KB) + /// - +--------------------------+ + /// - |HyperlightPEB | (4KB) + /// - +--------------------------+ + /// - |Guest code | (binary size) + /// - +--------------------------+ 0x0 + /// + /// - Note 1: The guest stack size can be set manually via the `stack_size_override` parameter. If + /// not provided, the stack size is set to the default stack reserve size of the guest binary. + fn set_memory_layout(mut self) -> Result { + // Name of guard page regions + const DEFAULT_TMP_STACK_GUARD_PAGE_NAME: &str = "tmp stack guard page"; + const DEFAULT_CUSTOM_GUEST_MEMORY_GUARD_PAGE_NAME: &str = "custom guest memory guard page"; + + let tmp_stack_size = 0x200_000; + let guest_memory_size = self.guest_memory_size; + + // (a) guest code added on `new` + + // (b) Hyperlight PEB section + // - Hyperlight, when initializing a guest, provides it with the address and size of the PEB + // region that this function creates. In this region, there is HyperlightPEB struct serialized + // via Flatbuffers. The guest can deserialize this struct and populate it to inform the host + // how it will operate. + self = self.add_memory_section( + DEFAULT_HYPERLIGHT_PEB_SECTION_NAME, + PAGE_SIZE, + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, + ); + + // (c) tmp stack guard page + self = self.add_memory_section( + DEFAULT_TMP_STACK_GUARD_PAGE_NAME, + PAGE_SIZE, + MemoryRegionFlags::READ | MemoryRegionFlags::STACK_GUARD, + ); + + // (d) tmp stack + self = self.add_memory_section( + DEFAULT_TMP_STACK_SECTION_NAME, + tmp_stack_size, + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, + ); + + // - We need to create a tmp stack section to be able to set the initial RSP value. + // The guest can later modify this to set up the stack however it wants. + let init_rsp = self + .memory_sections + .get_tmp_stack_section_offset() + .ok_or("tmp stack section not found")? + + tmp_stack_size; + self = self.set_init_rsp(init_rsp as u64); + + // (e) custom guest memory guard page + // - Optimally, the guest will set up its own stack to leverage this stack guard page. + self = self.add_memory_section( + DEFAULT_CUSTOM_GUEST_MEMORY_GUARD_PAGE_NAME, + PAGE_SIZE, + MemoryRegionFlags::READ | MemoryRegionFlags::STACK_GUARD, + ); + + // (f) custom guest memory + self = self.add_memory_section( + DEFAULT_CUSTOM_GUEST_MEMORY_SECTION_NAME, + guest_memory_size, + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE, + ); + + Ok(self) + } + + /// Loads the guest binary. There are three modes: + /// 1. Run in process and use LoadLibrary (makes the memory executable) + /// 2. Run in process and not use LoadLibrary (makes the memory executable) + /// 3. Run in hypervisor + /// + /// - Sets the load address in shared memory. + /// - Returns load address. + fn load_guest_binary( + &self, + init_rsp: u64, + memory_sections: SandboxMemorySections, + mut exclusive_shared_memory: ExclusiveSharedMemory, + ) -> Result> { + let run_inprocess = self.sandbox_run_options.in_process(); + let use_loadlib = self.sandbox_run_options.use_loadlib(); + let mut guest_binary_exe_info = self.guest_binary.to_exe_info()?; + let guest_code_offset = memory_sections + .get_guest_code_offset() + .ok_or_else(|| new_error!("Guest code section not found"))?; + + let sandbox_memory_manager = if run_inprocess && use_loadlib { + #[cfg(target_os = "windows")] + { + // We are running in process and using LoadLibrary + if !matches!(guest_binary_exe_info, ExeInfo::PE(_)) { + log_then_return!("LoadLibrary can only be used with PE files"); + } + + // Get guest binary path + let guest_bin_path = match &self.guest_binary { + GuestBinary::FilePath(bin_path_str) => bin_path_str, + GuestBinary::Buffer(_) => { + log_then_return!("Guest binary should be a file to use LoadLibrary"); + } + }; + + let lib = crate::mem::loaded_lib::LoadedLib::load(guest_bin_path)?; + exclusive_shared_memory.make_memory_executable()?; + + SandboxMemoryManager::new( + exclusive_shared_memory, + lib.base_addr(), + guest_binary_exe_info.entrypoint(), + memory_sections, + init_rsp, + Some(lib), + ) + } + #[cfg(target_os = "linux")] + { + log_then_return!("LoadLibrary is only available on Windows"); + } + } else if run_inprocess && !use_loadlib { + // We are running in process and not using LoadLibrary + exclusive_shared_memory.make_memory_executable()?; + let load_address = exclusive_shared_memory.base_addr() + guest_code_offset; + + guest_binary_exe_info.load( + load_address, + &mut exclusive_shared_memory.as_mut_slice()[guest_code_offset..], + )?; + + SandboxMemoryManager::new( + exclusive_shared_memory, + RawPtr(load_address as u64), + guest_binary_exe_info.entrypoint(), + memory_sections, + init_rsp, + #[cfg(target_os = "windows")] + None, + ) + } else if !run_inprocess && !use_loadlib { + guest_binary_exe_info.load( + guest_code_offset, + &mut exclusive_shared_memory.as_mut_slice()[guest_code_offset..], + )?; + + SandboxMemoryManager::new( + exclusive_shared_memory, + RawPtr(guest_code_offset as u64), + guest_binary_exe_info.entrypoint(), + memory_sections, + init_rsp, + #[cfg(target_os = "windows")] + None, + ) + } else { + log_then_return!("Invalid combination of run options: inprocess and use_loadlib") + }; + + Ok(sandbox_memory_manager) + } + + fn map_host_addresses(&mut self, host_base_address: usize) { + for (_, section) in self.memory_sections.clone().iter() { + let host_address = host_base_address + section.page_aligned_guest_offset; + self.memory_sections + .sections + .get_mut(§ion.page_aligned_guest_offset) + .unwrap() + .host_address = Some(host_address); + } + } + + /// Build the sandbox. + /// + /// Building includes the following steps: + /// - Set up the sandbox configuration + /// - Set up the sandbox memory layout + /// - Configure paging structures + /// - Allocate memory on the host + /// - Set the run mode + /// - Set the default guest stack and heap sizes + /// - Map host addresses to guest addresses + /// - Load the guest binary + pub fn build(self) -> Result { + log_build_details(); + + // Hyperlight is only supported on Windows 11 and Windows Server 2022 and later + #[cfg(target_os = "windows")] + check_windows_version()?; + + // Set up sandbox configuration + let mut sandbox_configuration = SandboxConfiguration::default(); + sandbox_configuration.set_max_initialization_time(self.max_initialization_time); + sandbox_configuration.set_max_execution_time(self.max_execution_time); + #[cfg(gdb)] + if let Some(debug_info) = self.guest_debug_info { + sandbox_configuration.set_guest_debug_info(debug_info); + } + + let sandbox_builder = self.set_memory_layout()?; + + // Add sandbox memory section for paging + let paging_sections_size = sandbox_builder.calculate_page_table_size()?; + let mut sandbox_builder = sandbox_builder.add_memory_section( + DEFAULT_PAGING_STRUCTURES_SECTION_NAME, + paging_sections_size, + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, + ); + + // Allocate memory on host + let exclusive_shared_memory = + ExclusiveSharedMemory::new(sandbox_builder.memory_sections.get_total_size())?; + + // Set run mode + let run_mode = if sandbox_builder.sandbox_run_options.in_process() { + #[cfg(target_os = "windows")] + { + RunMode::InProcessWindows + } + #[cfg(target_os = "linux")] + { + RunMode::InProcessLinux + } + } else { + RunMode::Hypervisor + }; + + // Default guest stack and heap sizes + let guest_stack_size = sandbox_builder.guest_binary.to_exe_info()?.stack_reserve(); + let guest_heap_size = sandbox_builder.guest_binary.to_exe_info()?.heap_reserve(); + + // Map host addresses to guest addresses + sandbox_builder.map_host_addresses(exclusive_shared_memory.base_addr()); + + let hyperlight_peb = HyperlightPEB::new( + run_mode, + guest_heap_size, + guest_stack_size, + sandbox_builder + .memory_sections + .get_custom_guest_memory_section_host_address() as u64, + sandbox_builder + .memory_sections + .get_custom_guest_memory_section_offset() as u64, + sandbox_builder + .memory_sections + .get_custom_guest_memory_size() as u64, + ); + + let mut sandbox_memory_manager = sandbox_builder.load_guest_binary( + sandbox_builder + .init_rsp + .ok_or(new_error!("SandboxBuilder: init_rsp not set"))?, + sandbox_builder.memory_sections.clone(), + exclusive_shared_memory, + )?; + + sandbox_memory_manager + .memory_sections + .write_hyperlight_peb(hyperlight_peb)?; + + //TODO(danbugs:297): bring back host functions and adding host_printer + + Ok(UninitializedSandbox::new( + sandbox_memory_manager, + sandbox_configuration, + #[cfg(gdb)] + sandbox_builder.guest_debug_info, + )) + } +} + +#[cfg(mshv)] +impl From for mshv_bindings::mshv_user_mem_region { + fn from(section: SandboxMemorySection) -> Self { + let size = section.page_aligned_size as u64; + let guest_pfn = (section.page_aligned_guest_offset as u64) >> 12; + let userspace_addr = section.host_address.unwrap_or(0) as u64; + + #[cfg(mshv2)] + { + let mut flags = 0; + if section.flags.contains(MemoryRegionFlags::READ) { + flags |= HV_MAP_GPA_READABLE; + } + if section.flags.contains(MemoryRegionFlags::WRITE) { + flags |= HV_MAP_GPA_WRITABLE; + } + if section.flags.contains(MemoryRegionFlags::EXECUTE) { + flags |= HV_MAP_GPA_EXECUTABLE; + } + if section.flags.is_empty() || section.flags.contains(MemoryRegionFlags::NONE) { + flags |= HV_MAP_GPA_PERMISSIONS_NONE; + } + + mshv_bindings::mshv_user_mem_region { + guest_pfn, + size, + userspace_addr, + flags, + } + } + #[cfg(mshv3)] + { + let mut flags: u8 = 0; + if section.flags.contains(MemoryRegionFlags::WRITE) { + flags |= 1 << MSHV_SET_MEM_BIT_WRITABLE; + } + if section.flags.contains(MemoryRegionFlags::EXECUTE) { + flags |= 1 << MSHV_SET_MEM_BIT_EXECUTABLE; + } + if section.flags.is_empty() || section.flags.contains(MemoryRegionFlags::NONE) { + flags |= 1 << MSHV_SET_MEM_BIT_UNMAP; + } + + mshv_bindings::mshv_user_mem_region { + guest_pfn, + size, + userspace_addr, + flags, + ..Default::default() + } + } + } +} + +// Check to see if the current version of Windows is supported +// Hyperlight is only supported on Windows 11 and Windows Server 2022 and later +#[cfg(target_os = "windows")] +fn check_windows_version() -> Result<()> { + use windows_version::{is_server, OsVersion}; + const WINDOWS_MAJOR: u32 = 10; + const WINDOWS_MINOR: u32 = 0; + const WINDOWS_PACK: u32 = 0; + + // Windows Server 2022 has version numbers 10.0.20348 or greater + if is_server() { + if OsVersion::current() < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 20348) + { + return Err(crate::new_error!( + "Hyperlight Requires Windows Server 2022 or newer" + )); + } + } else if OsVersion::current() + < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 22000) + { + return Err(crate::new_error!("Hyperlight Requires Windows 11 or newer")); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::sync::{Arc, Mutex}; + + use cfg_if::cfg_if; + use hyperlight_common::flatbuffer_wrappers::function_types::{ + ParameterValue, ReturnType, ReturnValue, + }; + use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode::{ + GuestFunctionNotFound, GuestFunctionParameterTypeMismatch, + }; + use hyperlight_testing::simple_guest_as_string; + + use super::*; + use crate::func::HostFunction2; + use crate::sandbox_state::sandbox::EvolvableSandbox; + use crate::sandbox_state::transition::Noop; + use crate::HyperlightError; + + #[test] + fn test_sandbox_builder() -> Result<()> { + // Tests building an uninitialized sandbox w/ the sandbox builder + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + // let sandbox_builder = sandbox_builder.set_guest_debug_info(DebugInfo { port: 8080 }); + + let mut uninitialized_sandbox = sandbox_builder.build()?; + + // Tests registering a host function + fn add(a: i32, b: i32) -> Result { + Ok(a + b) + } + let host_function = Arc::new(Mutex::new(add)); + host_function.register(&mut uninitialized_sandbox, "HostAdd")?; + + // Tests evolving to a multi-use sandbox + let mut multi_use_sandbox = uninitialized_sandbox.evolve(Noop::default())?; + + let result = multi_use_sandbox.call_guest_function_by_name( + "Add", + ReturnType::Int, + Some(vec![ParameterValue::Int(1), ParameterValue::Int(41)]), + )?; + + assert_eq!(result, ReturnValue::Int(42)); + + Ok(()) + } + + #[test] + fn test_sandbox_builder_in_process() -> Result<()> { + // Tests building an uninitialized sandbox w/ the sandbox builder + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))? + .set_sandbox_run_options(SandboxRunOptions::RunInProcess(false)); + + cfg_if! { + if #[cfg(inprocess)] { + let mut uninitialized_sandbox = sandbox_builder?.build()?; + + // Tests registering a host function + fn add(a: i32, b: i32) -> Result { + Ok(a + b) + } + let host_function = Arc::new(Mutex::new(add)); + host_function.register(&mut uninitialized_sandbox, "HostAdd")?; + + // Tests evolving to a multi-use sandbox + let mut multi_use_sandbox = uninitialized_sandbox.evolve(Noop::default())?; + + let result = multi_use_sandbox.call_guest_function_by_name( + "Add", + ReturnType::Int, + Some(vec![ParameterValue::Int(1), ParameterValue::Int(41)]), + )?; + + assert_eq!(result, ReturnValue::Int(42)); + + Ok(()) + } else { + assert!(sandbox_builder.is_err()); + Ok(()) + } + } + } + + #[test] + #[cfg(target_os = "windows")] + fn test_sandbox_builder_in_process_load_library() -> Result<()> { + use hyperlight_testing::simple_guest_exe_as_string; + + // Tests building an uninitialized sandbox w/ the sandbox builder + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_exe_as_string()?))? + .set_sandbox_run_options(SandboxRunOptions::RunInProcess(true))?; + + let mut uninitialized_sandbox = sandbox_builder.build()?; + + // Tests registering a host function + fn add(a: i32, b: i32) -> Result { + Ok(a + b) + } + let host_function = Arc::new(Mutex::new(add)); + host_function.register(&mut uninitialized_sandbox, "HostAdd")?; + + // Tests evolving to a multi-use sandbox + let mut multi_use_sandbox = uninitialized_sandbox.evolve(Noop::default())?; + + let result = multi_use_sandbox.call_guest_function_by_name( + "Add", + ReturnType::Int, + Some(vec![ParameterValue::Int(1), ParameterValue::Int(41)]), + )?; + + assert_eq!(result, ReturnValue::Int(42)); + + Ok(()) + } + + #[test] + #[cfg(crashdump)] + fn test_sandbox_builder_crashdump() -> Result<()> { + // Capture list of files in /tmp before the test + let tmp_dir = Path::new("/tmp"); + let before_files: std::collections::HashSet<_> = std::fs::read_dir(tmp_dir) + .expect("Failed to read /tmp directory") + .map(|e| e.unwrap().file_name()) + .collect(); + + // Setup guest sandbox + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + let mut uninitialized_sandbox = sandbox_builder.build()?; + + // Register host function + fn add(a: i32, b: i32) -> Result { + Ok(a + b) + } + let host_function = Arc::new(Mutex::new(add)); + host_function.register(&mut uninitialized_sandbox, "HostAdd")?; + + // Evolve to multi-use sandbox + let mut multi_use_sandbox = uninitialized_sandbox.evolve(Noop::default())?; + + // Call the guest function expected to crash + let result = multi_use_sandbox.call_guest_function_by_name( + "StackOverflow", + ReturnType::Void, + Some(vec![ParameterValue::Int(512)]), + ); + + assert!(result.is_err()); + + // Capture list of files in /tmp after the crash + let after_files: std::collections::HashSet<_> = std::fs::read_dir(tmp_dir) + .expect("Failed to read /tmp directory") + .map(|e| e.unwrap().file_name()) + .collect(); + + // Find the new files created + let new_files: Vec<_> = after_files + .difference(&before_files) + .filter(|f| f.to_string_lossy().ends_with(".dmp")) + .collect(); + + assert!(!new_files.is_empty(), "No crashdump file was created."); + + // Check the crashdump file(s) + for file_name in new_files { + let file_path = tmp_dir.join(file_name); + let metadata = std::fs::metadata(&file_path)?; + assert!( + metadata.len() > 0, + "Crashdump file is empty: {:?}", + file_path + ); + } + + Ok(()) + } + + #[test] + fn test_sandbox_builder_guest_function_fail() -> Result<()> { + // Tests building an uninitialized sandbox w/ the sandbox builder + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + let mut uninitialized_sandbox = sandbox_builder.build()?; + + // Tests registering a host function + fn add(a: i32, b: i32) -> Result { + Ok(a + b) + } + let host_function = Arc::new(Mutex::new(add)); + host_function.register(&mut uninitialized_sandbox, "HostAdd")?; + + // Tests evolving to a multi-use sandbox + let mut multi_use_sandbox = uninitialized_sandbox.evolve(Noop::default())?; + + let result = multi_use_sandbox.call_guest_function_by_name( + "Add", + ReturnType::Int, + // Purposefully passing the wrong parameter types + Some(vec![ParameterValue::Float(1.0), ParameterValue::Int(41)]), + ); + + // Should get Error: GuestError(GuestFunctionParameterTypeMismatch, "Expected parameter type Int for parameter index 0 of function Add but got Float.") + assert!(matches!( + result, + Err(HyperlightError::GuestError( + GuestFunctionParameterTypeMismatch { .. }, + _, + )) + )); + + Ok(()) + } + + #[test] + fn test_sandbox_builder_try_calling_nonexistent_guest_function() -> Result<()> { + // Tests building an uninitialized sandbox w/ the sandbox builder + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + let uninitialized_sandbox = sandbox_builder.build()?; + + // Tests evolving to a multi-use sandbox + let mut multi_use_sandbox = uninitialized_sandbox.evolve(Noop::default())?; + + let result = multi_use_sandbox.call_guest_function_by_name( + "SomeNonExistentFunction", + ReturnType::Void, + None, + ); + + // Should get Error: GuestError(GuestFunctionNotFound, "SomeNonExistentFunction") + assert!(matches!( + result, + Err(HyperlightError::GuestError(GuestFunctionNotFound { .. }, _,)) + )); + + Ok(()) + } + + #[test] + #[ignore] + #[cfg(target_os = "linux")] + fn test_sandbox_builder_violate_seccomp_filters() -> Result<()> { + use crate::func::HostFunction0; + + fn make_get_pid_syscall() -> Result { + let pid = unsafe { libc::syscall(libc::SYS_getpid) }; + Ok(pid as u64) + } + + // Tests two flows: + // 1. Calling a host function with the seccomp feature turned on, but without + // allowing the syscall. This should fail. + // 2. Calling a host function with the seccomp feature turned off. This should succeed. + { + // Tests building an uninitialized sandbox w/ the sandbox builder + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + let mut uninitialized_sandbox = sandbox_builder.build()?; + + let make_get_pid_syscall_func = Arc::new(Mutex::new(make_get_pid_syscall)); + make_get_pid_syscall_func.register(&mut uninitialized_sandbox, "MakeGetpidSyscall")?; + + // Tests evolving to a multi-use sandbox + let mut multi_use_sandbox = uninitialized_sandbox.evolve(Noop::default())?; + + let result = multi_use_sandbox.call_guest_function_by_name( + "ViolateSeccompFilters", + ReturnType::ULong, + None, + ); + + #[cfg(feature = "seccomp")] + match result { + 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 result { + Ok(_) => (), + Err(e) => panic!("Expected to succeed without seccomp: {}", e), + } + } + + // Tests calling a host function with the seccomp feature turned on, but allowing + // the syscall. This should succeed. + #[cfg(feature = "seccomp")] + { + // Tests building an uninitialized sandbox w/ the sandbox builder + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string()?))?; + + let mut uninitialized_sandbox = sandbox_builder.build()?; + + let make_get_pid_syscall_func = Arc::new(Mutex::new(make_get_pid_syscall)); + make_get_pid_syscall_func.register_with_extra_allowed_syscalls( + &mut uninitialized_sandbox, + "MakeGetpidSyscall", + vec![libc::SYS_getpid], + )?; + + // Tests evolving to a multi-use sandbox + let mut multi_use_sandbox = uninitialized_sandbox.evolve(Noop::default())?; + + let result = multi_use_sandbox.call_guest_function_by_name( + "ViolateSeccompFilters", + ReturnType::ULong, + None, + ); + + match result { + Ok(_) => {} + Err(e) => panic!("Expected to succeed due to seccomp violation: {}", e), + } + } + + Ok(()) + } +} diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index c1527a5c2..4d16cceb2 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -15,72 +15,47 @@ limitations under the License. */ use std::fmt::Debug; -use std::option::Option; -use std::path::Path; use std::sync::{Arc, Mutex}; -use std::time::Duration; use log::LevelFilter; use tracing::{instrument, Span}; #[cfg(gdb)] use super::config::DebugInfo; -use super::host_funcs::{default_writer_func, HostFuncsWrapper}; -use super::mem_mgr::MemMgrWrapper; -use super::run_options::SandboxRunOptions; use super::uninitialized_evolve::evolve_impl_multi_use; -use crate::error::HyperlightError::GuestBinaryShouldBeAFile; -use crate::func::host_functions::HostFunction1; -use crate::mem::exe::ExeInfo; -use crate::mem::mgr::{SandboxMemoryManager, STACK_COOKIE_LEN}; +use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::ExclusiveSharedMemory; -use crate::sandbox::SandboxConfiguration; -use crate::sandbox_state::sandbox::EvolvableSandbox; +use crate::sandbox::config::SandboxConfiguration; +use crate::sandbox::host_funcs::HostFuncsWrapper; +use crate::sandbox_state::sandbox::{EvolvableSandbox, Sandbox}; use crate::sandbox_state::transition::Noop; -use crate::{log_build_details, log_then_return, new_error, MultiUseSandbox, Result}; +use crate::{log_then_return, MultiUseSandbox, Result}; /// A preliminary `Sandbox`, not yet ready to execute guest code. /// -/// Prior to initializing a full-fledged `Sandbox`, you must create one of -/// these `UninitializedSandbox`es with the `new` function, register all the +/// Prior to initializing a full-fledged sandbox, you must create a +/// `UninitializedSandbox` with the `new` function, register all the /// host-implemented functions you need to be available to the guest, then -/// call `evolve` to transform your -/// `UninitializedSandbox` into an initialized `Sandbox`. +/// call `evolve` to transform your `UninitializedSandbox` into an initialized +/// sandbox. pub struct UninitializedSandbox { - /// Registered host functions pub(crate) host_funcs: Arc>, - /// The memory manager for the sandbox. - pub(crate) mgr: MemMgrWrapper, - pub(crate) run_inprocess: bool, - pub(crate) max_initialization_time: Duration, - pub(crate) max_execution_time: Duration, - pub(crate) max_wait_for_cancellation: Duration, + pub(crate) mem_mgr: SandboxMemoryManager, + pub(crate) config: SandboxConfiguration, pub(crate) max_guest_log_level: Option, #[cfg(gdb)] pub(crate) debug_info: Option, } -impl crate::sandbox_state::sandbox::UninitializedSandbox for UninitializedSandbox { - #[instrument(skip_all, parent = Span::current(), level = "Trace")] - fn get_uninitialized_sandbox(&self) -> &crate::sandbox::UninitializedSandbox { - self - } - - #[instrument(skip_all, parent = Span::current(), level = "Trace")] - fn get_uninitialized_sandbox_mut(&mut self) -> &mut crate::sandbox::UninitializedSandbox { - self - } -} - impl Debug for UninitializedSandbox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("UninitializedSandbox") - .field("memory_layout", &self.mgr.unwrap_mgr().layout) + .field("Memory Layout", &self.mem_mgr.memory_sections) .finish() } } -impl crate::sandbox_state::sandbox::Sandbox for UninitializedSandbox { +impl Sandbox for UninitializedSandbox { fn check_stack_guard(&self) -> Result { log_then_return!( "Checking the stack cookie before the sandbox is initialized is unsupported" @@ -102,204 +77,20 @@ impl } } -/// A `GuestBinary` is either a buffer containing the binary or a path to the binary -#[derive(Debug)] -pub enum GuestBinary { - /// A buffer containing the guest binary - Buffer(Vec), - /// A path to the guest binary - FilePath(String), -} - impl UninitializedSandbox { - /// Create a new sandbox configured to run the binary at path - /// `bin_path`. - /// - /// The instrument attribute is used to generate tracing spans and also to emit an error should the Result be an error. - /// The skip attribute is used to skip the guest binary from being printed in the tracing span. - /// The name attribute is used to name the tracing span. - /// The err attribute is used to emit an error should the Result be an error, it uses the std::`fmt::Debug trait` to print the error. - #[instrument( - err(Debug), - skip(guest_binary, host_print_writer), - parent = Span::current() - )] - pub fn new( - guest_binary: GuestBinary, - cfg: Option, - sandbox_run_options: Option, - host_print_writer: Option<&dyn HostFunction1>, - ) -> Result { - log_build_details(); - - // hyperlight is only supported on Windows 11 and Windows Server 2022 and later - #[cfg(target_os = "windows")] - check_windows_version()?; - - // If the guest binary is a file make sure it exists - let guest_binary = match guest_binary { - GuestBinary::FilePath(binary_path) => { - let path = Path::new(&binary_path) - .canonicalize() - .map_err(|e| new_error!("GuestBinary not found: '{}': {}", binary_path, e))?; - GuestBinary::FilePath( - path.into_os_string() - .into_string() - .map_err(|e| new_error!("Error converting OsString to String: {:?}", e))?, - ) - } - buffer @ GuestBinary::Buffer(_) => buffer, - }; - - let run_opts = sandbox_run_options.unwrap_or_default(); - - let run_inprocess = run_opts.in_process(); - let use_loadlib = run_opts.use_loadlib(); - - if run_inprocess && cfg!(not(inprocess)) { - log_then_return!( - "Inprocess mode is only available in debug builds, and also requires cargo feature 'inprocess'" - ) - } - - if use_loadlib && cfg!(not(all(inprocess, target_os = "windows"))) { - log_then_return!("Inprocess mode with LoadLibrary is only available on Windows") - } - - let sandbox_cfg = cfg.unwrap_or_default(); - - #[cfg(gdb)] - let debug_info = sandbox_cfg.get_guest_debug_info(); - let mut mem_mgr_wrapper = { - let mut mgr = UninitializedSandbox::load_guest_binary( - sandbox_cfg, - &guest_binary, - run_inprocess, - use_loadlib, - )?; - let stack_guard = Self::create_stack_guard(); - mgr.set_stack_guard(&stack_guard)?; - MemMgrWrapper::new(mgr, stack_guard) - }; - - mem_mgr_wrapper.write_memory_layout(run_inprocess)?; - - let host_funcs = Arc::new(Mutex::new(HostFuncsWrapper::default())); - - let mut sandbox = Self { - host_funcs, - mgr: mem_mgr_wrapper, - run_inprocess, - max_initialization_time: Duration::from_millis( - sandbox_cfg.get_max_initialization_time() as u64, - ), - max_execution_time: Duration::from_millis(sandbox_cfg.get_max_execution_time() as u64), - max_wait_for_cancellation: Duration::from_millis( - sandbox_cfg.get_max_wait_for_cancellation() as u64, - ), + /// Create a new uninitialized sandbox. + pub(crate) fn new( + mem_mgr: SandboxMemoryManager, + config: SandboxConfiguration, + #[cfg(gdb)] debug_info: Option, + ) -> Self { + Self { + host_funcs: Arc::new(Mutex::new(HostFuncsWrapper::default())), + mem_mgr, + config, max_guest_log_level: None, #[cfg(gdb)] debug_info, - }; - - // TODO: These only here to accommodate some writer functions. - // We should modify the `UninitializedSandbox` to follow the builder pattern we use in - // hyperlight-wasm to allow the user to specify what syscalls they need specifically. - - #[cfg(all(target_os = "linux", feature = "seccomp"))] - let extra_allowed_syscalls_for_writer_func = vec![ - // Fuzzing fails without `mmap` being an allowed syscall on our seccomp filter. - // All fuzzing does is call `PrintOutput` (which calls `HostPrint` ). Thing is, `println!` - // is designed to be thread-safe in Rust and the std lib ensures this by using - // buffered I/O, which I think relies on `mmap`. This gets surfaced in fuzzing with an - // OOM error, which I think is happening because `println!` is not being able to allocate - // more memory for its buffers for the fuzzer's huge inputs. - libc::SYS_mmap, - libc::SYS_brk, - libc::SYS_mprotect, - #[cfg(mshv)] - libc::SYS_close, - ]; - - // If we were passed a writer for host print register it otherwise use the default. - match host_print_writer { - Some(writer_func) => { - #[allow(clippy::arc_with_non_send_sync)] - let writer_func = Arc::new(Mutex::new(writer_func)); - - #[cfg(any(target_os = "windows", not(feature = "seccomp")))] - writer_func - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .register(&mut sandbox, "HostPrint")?; - - #[cfg(all(target_os = "linux", feature = "seccomp"))] - writer_func - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .register_with_extra_allowed_syscalls( - &mut sandbox, - "HostPrint", - extra_allowed_syscalls_for_writer_func, - )?; - } - None => { - let default_writer = Arc::new(Mutex::new(default_writer_func)); - - #[cfg(any(target_os = "windows", not(feature = "seccomp")))] - default_writer.register(&mut sandbox, "HostPrint")?; - - #[cfg(all(target_os = "linux", feature = "seccomp"))] - default_writer.register_with_extra_allowed_syscalls( - &mut sandbox, - "HostPrint", - extra_allowed_syscalls_for_writer_func, - )?; - } - } - - crate::debug!("Sandbox created: {:#?}", sandbox); - - Ok(sandbox) - } - - #[instrument(skip_all, parent = Span::current(), level = "Trace")] - fn create_stack_guard() -> [u8; STACK_COOKIE_LEN] { - rand::random::<[u8; STACK_COOKIE_LEN]>() - } - - /// Load the file at `bin_path_str` into a PE file, then attempt to - /// load the PE file into a `SandboxMemoryManager` and return it. - /// - /// If `run_from_guest_binary` is passed as `true`, and this code is - /// running on windows, this function will call - /// `SandboxMemoryManager::load_guest_binary_using_load_library` to - /// create the new `SandboxMemoryManager`. If `run_from_guest_binary` is - /// passed as `true` and we're not running on windows, this function will - /// return an `Err`. Otherwise, if `run_from_guest_binary` is passed - /// as `false`, this function calls `SandboxMemoryManager::load_guest_binary_into_memory`. - #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] - pub(super) fn load_guest_binary( - cfg: SandboxConfiguration, - guest_binary: &GuestBinary, - inprocess: bool, - use_loadlib: bool, - ) -> Result> { - let mut exe_info = match guest_binary { - GuestBinary::FilePath(bin_path_str) => ExeInfo::from_file(bin_path_str)?, - GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer)?, - }; - - if use_loadlib { - let path = match guest_binary { - GuestBinary::FilePath(bin_path_str) => bin_path_str, - GuestBinary::Buffer(_) => { - log_then_return!(GuestBinaryShouldBeAFile()); - } - }; - SandboxMemoryManager::load_guest_binary_using_load_library(cfg, path, &mut exe_info) - } else { - SandboxMemoryManager::load_guest_binary_into_memory(cfg, &mut exe_info, inprocess) } } @@ -310,605 +101,26 @@ impl UninitializedSandbox { self.max_guest_log_level = Some(log_level); } } -// Check to see if the current version of Windows is supported -// Hyperlight is only supported on Windows 11 and Windows Server 2022 and later -#[cfg(target_os = "windows")] -fn check_windows_version() -> Result<()> { - use windows_version::{is_server, OsVersion}; - const WINDOWS_MAJOR: u32 = 10; - const WINDOWS_MINOR: u32 = 0; - const WINDOWS_PACK: u32 = 0; - - // Windows Server 2022 has version numbers 10.0.20348 or greater - if is_server() { - if OsVersion::current() < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 20348) - { - return Err(new_error!( - "Hyperlight Requires Windows Server 2022 or newer" - )); - } - } else if OsVersion::current() - < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 22000) - { - return Err(new_error!("Hyperlight Requires Windows 11 or newer")); - } - Ok(()) -} #[cfg(test)] mod tests { use std::path::PathBuf; - use std::sync::{Arc, Mutex}; - use std::time::Duration; - use std::{fs, thread}; - use crossbeam_queue::ArrayQueue; - use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue}; use hyperlight_testing::logger::{Logger as TestLogger, LOGGER as TEST_LOGGER}; + use hyperlight_testing::simple_guest_as_string; use hyperlight_testing::tracing_subscriber::TracingSubscriber as TestSubscriber; - use hyperlight_testing::{simple_guest_as_string, simple_guest_exe_as_string}; use log::Level; use serde_json::{Map, Value}; - use serial_test::serial; use tracing::Level as tracing_level; use tracing_core::callsite::rebuild_interest_cache; use tracing_core::Subscriber; use uuid::Uuid; - use crate::func::{HostFunction1, HostFunction2}; - use crate::sandbox::uninitialized::GuestBinary; - use crate::sandbox::SandboxConfiguration; + use crate::sandbox::sandbox_builder::SandboxBuilder; use crate::sandbox_state::sandbox::EvolvableSandbox; use crate::sandbox_state::transition::Noop; use crate::testing::log_values::{test_value_as_str, try_to_strings}; - use crate::{new_error, MultiUseSandbox, Result, SandboxRunOptions, UninitializedSandbox}; - - #[test] - fn test_in_process() { - let simple_guest_path = simple_guest_as_string().unwrap(); - let sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_path.clone()), - None, - Some(SandboxRunOptions::RunInProcess(false)), - None, - ); - - // in process should only be enabled with the inprocess feature and on debug builds - assert_eq!(sbox.is_ok(), cfg!(inprocess)); - - let sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_path.clone()), - None, - Some(SandboxRunOptions::RunInProcess(true)), - None, - ); - - // debug mode should fail with an elf executable - assert!(sbox.is_err()); - - let simple_guest_path = simple_guest_exe_as_string().unwrap(); - let sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_path.clone()), - None, - Some(SandboxRunOptions::RunInProcess(false)), - None, - ); - - // in process should only be enabled with the inprocess feature and on debug builds - assert_eq!(sbox.is_ok(), cfg!(all(inprocess))); - - let sbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_path.clone()), - None, - Some(SandboxRunOptions::RunInProcess(true)), - None, - ); - - // debug mode should succeed with a PE executable on windows with inprocess enabled - assert_eq!(sbox.is_ok(), cfg!(all(inprocess, target_os = "windows"))); - } - - #[test] - fn test_new_sandbox() { - // Guest Binary exists at path - - let binary_path = simple_guest_as_string().unwrap(); - let sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), None, None, None); - assert!(sandbox.is_ok()); - - // Guest Binary does not exist at path - - let mut binary_path_does_not_exist = binary_path.clone(); - binary_path_does_not_exist.push_str(".nonexistent"); - let uninitialized_sandbox = UninitializedSandbox::new( - GuestBinary::FilePath(binary_path_does_not_exist), - None, - None, - None, - ); - assert!(uninitialized_sandbox.is_err()); - - // Non default memory configuration - let cfg = { - let mut cfg = SandboxConfiguration::default(); - cfg.set_input_data_size(0x1000); - cfg.set_output_data_size(0x1000); - cfg.set_host_function_definition_size(0x1000); - cfg.set_host_exception_size(0x1000); - cfg.set_guest_error_buffer_size(0x1000); - cfg.set_stack_size(0x1000); - cfg.set_heap_size(0x1000); - cfg.set_max_execution_time(Duration::from_millis(1001)); - cfg.set_max_execution_cancel_wait_time(Duration::from_millis(9)); - Some(cfg) - }; - - let uninitialized_sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(binary_path.clone()), cfg, None, None); - assert!(uninitialized_sandbox.is_ok()); - - let uninitialized_sandbox = - UninitializedSandbox::new(GuestBinary::FilePath(binary_path), None, None, None) - .unwrap(); - - // Get a Sandbox from an uninitialized sandbox without a call back function - - let _sandbox: MultiUseSandbox = uninitialized_sandbox.evolve(Noop::default()).unwrap(); - - // Test with a valid guest binary buffer - - let binary_path = simple_guest_as_string().unwrap(); - let sandbox = UninitializedSandbox::new( - GuestBinary::Buffer(fs::read(binary_path).unwrap()), - None, - None, - None, - ); - assert!(sandbox.is_ok()); - - // Test with a invalid guest binary buffer - - let binary_path = simple_guest_as_string().unwrap(); - let mut bytes = fs::read(binary_path).unwrap(); - let _ = bytes.split_off(100); - let sandbox = UninitializedSandbox::new(GuestBinary::Buffer(bytes), None, None, None); - assert!(sandbox.is_err()); - - // Test with a valid guest binary buffer when trying to load library - #[cfg(target_os = "windows")] - { - let binary_path = simple_guest_as_string().unwrap(); - let sandbox = UninitializedSandbox::new( - GuestBinary::Buffer(fs::read(binary_path).unwrap()), - None, - Some(SandboxRunOptions::RunInProcess(true)), - None, - ); - assert!(sandbox.is_err()); - } - } - - #[test] - fn test_load_guest_binary_manual() { - let cfg = SandboxConfiguration::default(); - - let simple_guest_path = simple_guest_as_string().unwrap(); - - UninitializedSandbox::load_guest_binary( - cfg, - &GuestBinary::FilePath(simple_guest_path), - false, - false, - ) - .unwrap(); - } - - #[test] - fn test_host_functions() { - let uninitialized_sandbox = || { - UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), - None, - None, - None, - ) - .unwrap() - }; - - // simple register + call - { - let mut usbox = uninitialized_sandbox(); - let test0 = |arg: i32| -> Result { Ok(arg + 1) }; - let test_func0 = Arc::new(Mutex::new(test0)); - test_func0.register(&mut usbox, "test0").unwrap(); - - let sandbox: Result = usbox.evolve(Noop::default()); - assert!(sandbox.is_ok()); - let sandbox = sandbox.unwrap(); - - let host_funcs = sandbox - ._host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - let res = host_funcs - .unwrap() - .call_host_function("test0", vec![ParameterValue::Int(1)]) - .unwrap(); - - assert_eq!(res, ReturnValue::Int(2)); - } - - // multiple parameters register + call - { - let mut usbox = uninitialized_sandbox(); - let test1 = |arg1: i32, arg2: i32| -> Result { Ok(arg1 + arg2) }; - let test_func1 = Arc::new(Mutex::new(test1)); - test_func1.register(&mut usbox, "test1").unwrap(); - - let sandbox: Result = usbox.evolve(Noop::default()); - assert!(sandbox.is_ok()); - let sandbox = sandbox.unwrap(); - - let host_funcs = sandbox - ._host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - let res = host_funcs - .unwrap() - .call_host_function( - "test1", - vec![ParameterValue::Int(1), ParameterValue::Int(2)], - ) - .unwrap(); - - assert_eq!(res, ReturnValue::Int(3)); - } - - // incorrect arguments register + call - { - let mut usbox = uninitialized_sandbox(); - let test2 = |arg1: String| -> Result<()> { - println!("test2 called: {}", arg1); - Ok(()) - }; - let test_func2 = Arc::new(Mutex::new(test2)); - test_func2.register(&mut usbox, "test2").unwrap(); - - let sandbox: Result = usbox.evolve(Noop::default()); - assert!(sandbox.is_ok()); - let sandbox = sandbox.unwrap(); - - let host_funcs = sandbox - ._host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - let res = host_funcs.unwrap().call_host_function("test2", vec![]); - assert!(res.is_err()); - } - - // calling a function that doesn't exist - { - let usbox = uninitialized_sandbox(); - let sandbox: Result = usbox.evolve(Noop::default()); - assert!(sandbox.is_ok()); - let sandbox = sandbox.unwrap(); - - let host_funcs = sandbox - ._host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - let res = host_funcs.unwrap().call_host_function("test4", vec![]); - assert!(res.is_err()); - } - } - - #[test] - #[serial] - fn test_load_guest_binary_load_lib() { - let cfg = SandboxConfiguration::default(); - let simple_guest_path = simple_guest_exe_as_string().unwrap(); - let mgr_res = UninitializedSandbox::load_guest_binary( - cfg, - &GuestBinary::FilePath(simple_guest_path), - true, - true, - ); - #[cfg(target_os = "linux")] - { - assert!(mgr_res.is_err()) - } - #[cfg(target_os = "windows")] - { - #[cfg(inprocess)] - { - assert!(mgr_res.is_ok()) - } - #[cfg(not(inprocess))] - { - assert!(mgr_res.is_err()) - } - } - } - - #[test] - fn test_host_print() { - // writer as a FnMut closure mutating a captured variable and then trying to access the captured variable - // after the Sandbox instance has been dropped - // this example is fairly contrived but we should still support such an approach. - - let received_msg = Arc::new(Mutex::new(String::new())); - let received_msg_clone = received_msg.clone(); - - let writer = move |msg| { - let mut received_msg = received_msg_clone - .try_lock() - .map_err(|_| new_error!("Error locking")) - .unwrap(); - *received_msg = msg; - Ok(0) - }; - - let hostfunc = Arc::new(Mutex::new(writer)); - - let sandbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), - None, - None, - Some(&hostfunc), - ) - .expect("Failed to create sandbox"); - - let host_funcs = sandbox - .host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - host_funcs.unwrap().host_print("test".to_string()).unwrap(); - - drop(sandbox); - - assert_eq!( - received_msg - .try_lock() - .map_err(|_| new_error!("Error locking")) - .unwrap() - .as_str(), - "test" - ); - - // There may be cases where a mutable reference to the captured variable is not required to be used outside the closue - // e.g. if the function is writing to a file or a socket etc. - - // writer as a FnMut closure mutating a captured variable but not trying to access the captured variable - - // This seems more realistic as the client is creating a file to be written to in the closure - // and then accessing the file a different handle. - // The problem is that captured_file still needs static lifetime so even though we can access the data through the second file handle - // this still does not work as the captured_file is dropped at the end of the function - - // TODO: Currently, we block any writes that are not to - // the stdout/stderr file handles, so this code is commented - // out until we can register writer functions like any other - // host functions with their own set of extra allowed syscalls. - // In particular, this code should be brought back once we have addressed the issue - - // let captured_file = Arc::new(Mutex::new(NamedTempFile::new().unwrap())); - // let capture_file_clone = captured_file.clone(); - // - // let capture_file_lock = captured_file - // .try_lock() - // .map_err(|_| new_error!("Error locking")) - // .unwrap(); - // let mut file = capture_file_lock.reopen().unwrap(); - // drop(capture_file_lock); - // - // let writer = move |msg: String| -> Result { - // let mut captured_file = capture_file_clone - // .try_lock() - // .map_err(|_| new_error!("Error locking")) - // .unwrap(); - // captured_file.write_all(msg.as_bytes()).unwrap(); - // Ok(0) - // }; - // - // let writer_func = Arc::new(Mutex::new(writer)); - // - // let sandbox = UninitializedSandbox::new( - // GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), - // None, - // None, - // Some(&writer_func), - // ) - // .expect("Failed to create sandbox"); - // - // let host_funcs = sandbox - // .host_funcs - // .try_lock() - // .map_err(|_| new_error!("Error locking")); - // - // assert!(host_funcs.is_ok()); - // - // host_funcs.unwrap().host_print("test2".to_string()).unwrap(); - // - // let mut buffer = String::new(); - // file.read_to_string(&mut buffer).unwrap(); - // assert_eq!(buffer, "test2"); - - // writer as a function - - fn fn_writer(msg: String) -> Result { - assert_eq!(msg, "test2"); - Ok(0) - } - - let writer_func = Arc::new(Mutex::new(fn_writer)); - let sandbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), - None, - None, - Some(&writer_func), - ) - .expect("Failed to create sandbox"); - - let host_funcs = sandbox - .host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - host_funcs.unwrap().host_print("test2".to_string()).unwrap(); - - // writer as a method - - let mut test_host_print = TestHostPrint::new(); - - // create a closure over the struct method - - let writer_closure = move |s| test_host_print.write(s); - - let writer_method = Arc::new(Mutex::new(writer_closure)); - - let sandbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().expect("Guest Binary Missing")), - None, - None, - Some(&writer_method), - ) - .expect("Failed to create sandbox"); - - let host_funcs = sandbox - .host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - host_funcs.unwrap().host_print("test3".to_string()).unwrap(); - } - - struct TestHostPrint {} - - impl TestHostPrint { - fn new() -> Self { - TestHostPrint {} - } - - fn write(&mut self, msg: String) -> Result { - assert_eq!(msg, "test3"); - Ok(0) - } - } - - #[test] - fn check_create_and_use_sandbox_on_different_threads() { - let unintializedsandbox_queue = Arc::new(ArrayQueue::::new(10)); - let sandbox_queue = Arc::new(ArrayQueue::::new(10)); - - for i in 0..10 { - let simple_guest_path = simple_guest_as_string().expect("Guest Binary Missing"); - let unintializedsandbox = { - let err_string = format!("failed to create UninitializedSandbox {i}"); - let err_str = err_string.as_str(); - UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_path), - None, - None, - None, - ) - .expect(err_str) - }; - - { - let err_string = format!("Failed to push UninitializedSandbox {i}"); - let err_str = err_string.as_str(); - - unintializedsandbox_queue - .push(unintializedsandbox) - .expect(err_str); - } - } - - let thread_handles = (0..10) - .map(|i| { - let uq = unintializedsandbox_queue.clone(); - let sq = sandbox_queue.clone(); - thread::spawn(move || { - let uninitialized_sandbox = uq.pop().unwrap_or_else(|| { - panic!("Failed to pop UninitializedSandbox thread {}", i) - }); - - let host_funcs = uninitialized_sandbox - .host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - host_funcs - .unwrap() - .host_print(format!("Print from UninitializedSandbox on Thread {}\n", i)) - .unwrap(); - - let sandbox = uninitialized_sandbox - .evolve(Noop::default()) - .unwrap_or_else(|_| { - panic!("Failed to initialize UninitializedSandbox thread {}", i) - }); - - sq.push(sandbox).unwrap_or_else(|_| { - panic!("Failed to push UninitializedSandbox thread {}", i) - }) - }) - }) - .collect::>(); - - for handle in thread_handles { - handle.join().unwrap(); - } - - let thread_handles = (0..10) - .map(|i| { - let sq = sandbox_queue.clone(); - thread::spawn(move || { - let sandbox = sq - .pop() - .unwrap_or_else(|| panic!("Failed to pop Sandbox thread {}", i)); - - let host_funcs = sandbox - ._host_funcs - .try_lock() - .map_err(|_| new_error!("Error locking")); - - assert!(host_funcs.is_ok()); - - host_funcs - .unwrap() - .host_print(format!("Print from Sandbox on Thread {}\n", i)) - .unwrap(); - }) - }) - .collect::>(); - - for handle in thread_handles { - handle.join().unwrap(); - } - } + use crate::{GuestBinary, MultiUseSandbox, Result}; #[test] // Tests that trace data are emitted when a trace subscriber is set @@ -953,9 +165,9 @@ mod tests { let mut binary_path = simple_guest_as_string().unwrap(); binary_path.push_str("does_not_exist"); - let sbox = - UninitializedSandbox::new(GuestBinary::FilePath(binary_path), None, None, None); - assert!(sbox.is_err()); + let sandbox_builder = SandboxBuilder::new(GuestBinary::FilePath(binary_path)); + + assert!(sandbox_builder.is_err()); // Now we should still be in span 1 but span 2 should be created (we created entered and exited span 2 when we called UninitializedSandbox::new) @@ -971,10 +183,10 @@ mod tests { let span_metadata = subscriber.get_span_metadata(2); assert_eq!(span_metadata.name(), "new"); - // There should be one event for the error that the binary path does not exist plus 14 info events for the logging of the crate info + // There should be one event for the error that the binary path does not exist let events = subscriber.get_events(); - assert_eq!(events.len(), 15); + assert_eq!(events.len(), 1); let mut count_matching_events = 0; @@ -984,7 +196,7 @@ mod tests { event_values.get("metadata").unwrap().as_object().unwrap(); let event_values_map = event_values.as_object().unwrap(); - let expected_error_start = "Error(\"GuestBinary not found:"; + let expected_error_start = "Error(\"Guest binary not found:"; let err_vals_res = try_to_strings([ (metadata_values_map, "level"), @@ -995,15 +207,15 @@ mod tests { if let Ok(err_vals) = err_vals_res { if err_vals[0] == "ERROR" && err_vals[1].starts_with(expected_error_start) - && err_vals[2] == "hyperlight_host::sandbox::uninitialized" - && err_vals[3] == "hyperlight_host::sandbox::uninitialized" + && err_vals[2] == "hyperlight_host::sandbox::sandbox_builder" + && err_vals[3] == "hyperlight_host::sandbox::sandbox_builder" { count_matching_events += 1; } } } - assert!( - count_matching_events == 1, + assert_eq!( + count_matching_events, 1, "Unexpected number of matching events {}", count_matching_events ); @@ -1029,13 +241,9 @@ mod tests { let mut invalid_binary_path = simple_guest_as_string().unwrap(); invalid_binary_path.push_str("does_not_exist"); - let sbox = UninitializedSandbox::new( - GuestBinary::FilePath(invalid_binary_path), - None, - None, - None, - ); - assert!(sbox.is_err()); + let sandbox_builder = SandboxBuilder::new(GuestBinary::FilePath(invalid_binary_path)); + + assert!(sandbox_builder.is_err()); // When tracing is creating log records it will create a log // record for the creation of the span (from the instrument @@ -1051,15 +259,15 @@ mod tests { // load into the sandbox does not exist, plus the 14 info log records let num_calls = TEST_LOGGER.num_log_calls(); - assert_eq!(19, num_calls); + assert_eq!(5, num_calls); // Log record 1 let logcall = TEST_LOGGER.get_log_call(0).unwrap(); assert_eq!(Level::Info, logcall.level); - assert!(logcall.args.starts_with("new; cfg")); - assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target); + assert!(logcall.args.starts_with("new;")); + assert_eq!("tracing::span", logcall.target); // Log record 2 @@ -1070,23 +278,23 @@ mod tests { // Log record 17 - let logcall = TEST_LOGGER.get_log_call(16).unwrap(); + let logcall = TEST_LOGGER.get_log_call(2).unwrap(); assert_eq!(Level::Error, logcall.level); assert!(logcall .args - .starts_with("error=Error(\"GuestBinary not found:")); - assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target); + .starts_with("error=Error(\"Guest binary not found:")); + assert_eq!("hyperlight_host::sandbox::sandbox_builder", logcall.target); // Log record 18 - let logcall = TEST_LOGGER.get_log_call(17).unwrap(); + let logcall = TEST_LOGGER.get_log_call(3).unwrap(); assert_eq!(Level::Trace, logcall.level); assert_eq!(logcall.args, "<- new;"); assert_eq!("tracing::span::active", logcall.target); // Log record 19 - let logcall = TEST_LOGGER.get_log_call(18).unwrap(); + let logcall = TEST_LOGGER.get_log_call(4).unwrap(); assert_eq!(Level::Trace, logcall.level); assert_eq!(logcall.args, "-- new;"); assert_eq!("tracing::span", logcall.target); @@ -1101,13 +309,11 @@ mod tests { valid_binary_path.push("sandbox"); valid_binary_path.push("initialized.rs"); - let sbox = UninitializedSandbox::new( - GuestBinary::FilePath(valid_binary_path.into_os_string().into_string().unwrap()), - None, - None, - None, - ); - assert!(sbox.is_err()); + let sandbox_builder = SandboxBuilder::new(GuestBinary::FilePath( + valid_binary_path.into_os_string().into_string().unwrap(), + )); + + assert!(sandbox_builder.is_err()); // There should be 2 calls this time when we change to the log // LevelFilter to Info. @@ -1119,8 +325,8 @@ mod tests { let logcall = TEST_LOGGER.get_log_call(0).unwrap(); assert_eq!(Level::Info, logcall.level); - assert!(logcall.args.starts_with("new; cfg")); - assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target); + assert!(logcall.args.starts_with("new;")); + assert_eq!("tracing::span", logcall.target); // Log record 2 @@ -1128,21 +334,19 @@ mod tests { assert_eq!(Level::Error, logcall.level); assert!(logcall .args - .starts_with("error=Error(\"GuestBinary not found:")); - assert_eq!("hyperlight_host::sandbox::uninitialized", logcall.target); + .starts_with("error=Error(\"Guest binary not found:")); + assert_eq!("hyperlight_host::sandbox::sandbox_builder", logcall.target); } { TEST_LOGGER.clear_log_calls(); TEST_LOGGER.set_max_level(log::LevelFilter::Error); let sbox = { - let res = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().unwrap()), - None, - None, - None, - ); - res.unwrap() + let sandbox_builder = + SandboxBuilder::new(GuestBinary::FilePath(simple_guest_as_string().unwrap())) + .unwrap(); + + sandbox_builder.build().unwrap() }; let _: Result = sbox.evolve(Noop::default()); @@ -1154,21 +358,16 @@ mod tests { #[test] fn test_invalid_path() { - let invalid_path = "some/path/that/does/not/exist"; - let sbox = UninitializedSandbox::new( - GuestBinary::FilePath(invalid_path.to_string()), - None, - None, - None, - ); - println!("{:?}", sbox); + let invalid_path = "some/path/that/does/not/exist".to_string(); + let sandbox_builder = SandboxBuilder::new(GuestBinary::FilePath(invalid_path)); + #[cfg(target_os = "windows")] assert!( - matches!(sbox, Err(e) if e.to_string().contains("GuestBinary not found: 'some/path/that/does/not/exist': The system cannot find the path specified. (os error 3)")) + matches!(sandbox_builder, Err(e) if e.to_string().contains("Guest binary not found: 'some/path/that/does/not/exist': The system cannot find the path specified. (os error 3)")) ); #[cfg(target_os = "linux")] assert!( - matches!(sbox, Err(e) if e.to_string().contains("GuestBinary not found: 'some/path/that/does/not/exist': No such file or directory (os error 2)")) + matches!(sandbox_builder, Err(e) if e.to_string().contains("Guest binary not found: 'some/path/that/does/not/exist': No such file or directory (os error 2)")) ); } } diff --git a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs index 6ddba5e29..48138f13e 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs @@ -27,14 +27,13 @@ use crate::hypervisor::hypervisor_handler::{ HvHandlerConfig, HypervisorHandler, HypervisorHandlerAction, }; use crate::mem::mgr::SandboxMemoryManager; -use crate::mem::ptr::RawPtr; use crate::mem::shared_mem::GuestSharedMemory; #[cfg(gdb)] use crate::sandbox::config::DebugInfo; use crate::sandbox::host_funcs::HostFuncsWrapper; use crate::sandbox::mem_access::mem_access_handler_wrapper; use crate::sandbox::outb::outb_handler_wrapper; -use crate::sandbox::{HostSharedMemory, MemMgrWrapper}; +use crate::sandbox::HostSharedMemory; use crate::sandbox_state::sandbox::Sandbox; use crate::{new_error, MultiUseSandbox, Result, UninitializedSandbox}; @@ -55,56 +54,42 @@ fn evolve_impl( transform: TransformFunc, ) -> Result where - TransformFunc: Fn( - Arc>, - MemMgrWrapper, - HypervisorHandler, - ) -> Result, + TransformFunc: + Fn(SandboxMemoryManager, HypervisorHandler) -> Result, { - let (hshm, gshm) = u_sbox.mgr.build(); - - let hv_handler = { - let mut hv_handler = hv_init( - &hshm, - gshm, - u_sbox.host_funcs.clone(), - u_sbox.max_initialization_time, - u_sbox.max_execution_time, - u_sbox.max_wait_for_cancellation, - u_sbox.max_guest_log_level, - #[cfg(gdb)] - u_sbox.debug_info, - )?; - - { - let dispatch_function_addr = hshm.as_ref().get_pointer_to_dispatch_function()?; - if dispatch_function_addr == 0 { - return Err(new_error!("Dispatch function address is null")); - } - hv_handler.set_dispatch_function_addr(RawPtr::from(dispatch_function_addr))?; - } - - hv_handler - }; + let (hshm, gshm) = u_sbox.mem_mgr.build(); + + let hv_handler = hv_init( + (hshm.clone(), gshm), + u_sbox.host_funcs.clone(), + Duration::from_millis(u_sbox.config.get_max_initialization_time() as u64), + Duration::from_millis(u_sbox.config.get_max_execution_time() as u64), + Duration::from_millis(u_sbox.config.get_max_wait_for_cancellation() as u64), + u_sbox.max_guest_log_level, + #[cfg(gdb)] + u_sbox.debug_info, + )?; - transform(u_sbox.host_funcs, hshm, hv_handler) + transform(hshm, hv_handler) } #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub(super) fn evolve_impl_multi_use(u_sbox: UninitializedSandbox) -> Result { - evolve_impl(u_sbox, |hf, mut hshm, hv_handler| { + evolve_impl(u_sbox, |mut hshm, hv_handler| { { - hshm.as_mut().push_state()?; + hshm.push_state()?; } - Ok(MultiUseSandbox::from_uninit(hf, hshm, hv_handler)) + Ok(MultiUseSandbox::from_uninit(hshm, hv_handler)) }) } #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] #[allow(clippy::too_many_arguments)] fn hv_init( - hshm: &MemMgrWrapper, - gshm: SandboxMemoryManager, + shm: ( + SandboxMemoryManager, + SandboxMemoryManager, + ), host_funcs: Arc>, max_init_time: Duration, max_exec_time: Duration, @@ -112,6 +97,7 @@ fn hv_init( max_guest_log_level: Option, #[cfg(gdb)] debug_info: Option, ) -> Result { + let (hshm, gshm) = shm; let outb_hdl = outb_handler_wrapper(hshm.clone(), host_funcs); let mem_access_hdl = mem_access_handler_wrapper(hshm.clone()); #[cfg(gdb)] @@ -121,20 +107,17 @@ fn hv_init( let mut rng = rand::rng(); rng.random::() }; - let peb_addr = { - let peb_u64 = u64::try_from(gshm.layout.peb_address)?; - RawPtr::from(peb_u64) - }; - let page_size = u32::try_from(page_size::get())?; + let hv_handler_config = HvHandlerConfig { + hyperlight_peb_guest_memory_region_address: gshm + .memory_sections + .get_hyperlight_peb_section_offset() + .unwrap() as u64, outb_handler: outb_hdl, mem_access_handler: mem_access_hdl, #[cfg(gdb)] dbg_mem_access_handler: dbg_mem_access_hdl, seed, - page_size, - peb_addr, - dispatch_function_addr: Arc::new(Mutex::new(None)), max_init_time, max_exec_time, max_wait_for_cancellation, @@ -160,53 +143,3 @@ fn hv_init( Ok(hv_handler) } - -#[cfg(test)] -mod tests { - use hyperlight_testing::{callback_guest_as_string, simple_guest_as_string}; - - use super::evolve_impl_multi_use; - use crate::sandbox::uninitialized::GuestBinary; - use crate::UninitializedSandbox; - - #[test] - fn test_evolve() { - let guest_bin_paths = vec![ - simple_guest_as_string().unwrap(), - callback_guest_as_string().unwrap(), - ]; - for guest_bin_path in guest_bin_paths { - let u_sbox = UninitializedSandbox::new( - GuestBinary::FilePath(guest_bin_path.clone()), - None, - None, - None, - ) - .unwrap(); - evolve_impl_multi_use(u_sbox).unwrap(); - } - } - - #[test] - #[cfg(target_os = "windows")] - fn test_evolve_in_proc() { - use crate::SandboxRunOptions; - - let guest_bin_paths = vec![ - simple_guest_as_string().unwrap(), - callback_guest_as_string().unwrap(), - ]; - for guest_bin_path in guest_bin_paths { - let u_sbox: UninitializedSandbox = UninitializedSandbox::new( - GuestBinary::FilePath(guest_bin_path.clone()), - None, - Some(SandboxRunOptions::RunInHypervisor), - None, - ) - .unwrap(); - let err = format!("error evolving sandbox with guest binary {guest_bin_path}"); - let err_str = err.as_str(); - evolve_impl_multi_use(u_sbox).expect(err_str); - } - } -} diff --git a/src/hyperlight_host/src/sandbox_state/sandbox.rs b/src/hyperlight_host/src/sandbox_state/sandbox.rs index b6c172226..8731191ea 100644 --- a/src/hyperlight_host/src/sandbox_state/sandbox.rs +++ b/src/hyperlight_host/src/sandbox_state/sandbox.rs @@ -61,12 +61,6 @@ pub trait UninitializedSandbox: Sandbox { /// Retrieves mutable reference to strongly typed `UninitializedSandbox` fn get_uninitialized_sandbox_mut(&mut self) -> &mut crate::sandbox::UninitializedSandbox; - - /// Returns `true` if the Sandbox is configured to run in process otherwise `false` - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn is_running_in_process(&self) -> bool { - self.get_uninitialized_sandbox().run_inprocess - } } /// A `Sandbox` that knows how to "evolve" into a next state. diff --git a/src/hyperlight_host/src/seccomp/guest.rs b/src/hyperlight_host/src/seccomp/guest.rs index 2cc10d655..84694cc09 100644 --- a/src/hyperlight_host/src/seccomp/guest.rs +++ b/src/hyperlight_host/src/seccomp/guest.rs @@ -23,6 +23,7 @@ use seccompiler::{ use crate::sandbox::ExtraAllowedSyscall; use crate::{and, or, Result}; +#[allow(unused)] fn syscalls_allowlist() -> Result)>> { Ok(vec![ // SYS_signalstack, SYS_munmap, SYS_rt_sigprocmask, SYS_madvise, and SYS_exit diff --git a/src/hyperlight_host/src/testing/mod.rs b/src/hyperlight_host/src/testing/mod.rs index 4cbc5ca83..4e1d909c6 100644 --- a/src/hyperlight_host/src/testing/mod.rs +++ b/src/hyperlight_host/src/testing/mod.rs @@ -1,48 +1,44 @@ -/* -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. -*/ - -use std::fs; -use std::path::PathBuf; - -use hyperlight_testing::rust_guest_as_pathbuf; - -use crate::mem::exe::ExeInfo; -use crate::{new_error, Result}; +// /* +// 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. +// */ +// use std::path::PathBuf; +// use hyperlight_testing::rust_guest_as_pathbuf; +// use crate::mem::exe::ExeInfo; +// use crate::{new_error, Result}; pub(crate) mod log_values; -/// Get an `ExeInfo` representing `simpleguest.exe` -pub(crate) fn simple_guest_exe_info() -> Result { - let bytes = bytes_for_path(rust_guest_as_pathbuf("simpleguest"))?; - ExeInfo::from_buf(bytes.as_slice()) -} - -/// Get an `ExeInfo` representing `callbackguest.exe` -pub(crate) fn callback_guest_exe_info() -> Result { - let bytes = bytes_for_path(rust_guest_as_pathbuf("callbackguest"))?; - ExeInfo::from_buf(bytes.as_slice()) -} - -/// Read the file at `path_buf` into a `Vec` and return it, -/// or return `Err` if that went wrong -pub(crate) fn bytes_for_path(path_buf: PathBuf) -> Result> { - let guest_path = path_buf - .as_path() - .to_str() - .ok_or_else(|| new_error!("couldn't convert guest {:?} to a path", path_buf))?; - let guest_bytes = fs::read(guest_path) - .map_err(|e| new_error!("failed to open guest at path {} ({})", guest_path, e))?; - Ok(guest_bytes) -} +// /// Get an `ExeInfo` representing `simpleguest.exe` +// pub(crate) fn simple_guest_exe_info() -> Result { +// let bytes = bytes_for_path(rust_guest_as_pathbuf("simpleguest"))?; +// ExeInfo::from_buf(bytes.as_slice()) +// } +// +// /// Get an `ExeInfo` representing `callbackguest.exe` +// pub(crate) fn callback_guest_exe_info() -> Result { +// let bytes = bytes_for_path(rust_guest_as_pathbuf("callbackguest"))?; +// ExeInfo::from_buf(bytes.as_slice()) +// } +// +// /// Read the file at `path_buf` into a `Vec` and return it, +// /// or return `Err` if that went wrong +// pub(crate) fn bytes_for_path(path_buf: PathBuf) -> Result> { +// let guest_path = path_buf +// .as_path() +// .to_str() +// .ok_or_else(|| new_error!("couldn't convert guest {:?} to a path", path_buf))?; +// let guest_bytes = std::fs::read(guest_path) +// .map_err(|e| new_error!("failed to open guest at path {} ({})", guest_path, e))?; +// Ok(guest_bytes) +// } diff --git a/src/hyperlight_host/tests/common/mod.rs b/src/hyperlight_host/tests/common/mod.rs index a616c3d54..dc93c65e5 100644 --- a/src/hyperlight_host/tests/common/mod.rs +++ b/src/hyperlight_host/tests/common/mod.rs @@ -1,162 +1,163 @@ -/* -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. -*/ - -use hyperlight_host::func::HostFunction1; -use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; -use hyperlight_host::sandbox_state::transition::Noop; -use hyperlight_host::{GuestBinary, MultiUseSandbox, Result, UninitializedSandbox}; -use hyperlight_testing::{ - c_callback_guest_as_string, c_simple_guest_as_string, callback_guest_as_string, - simple_guest_as_string, -}; - -/// Returns a rust/c simpleguest depending on environment variable GUEST. -/// Uses rust guest by default. Run test with environment variable GUEST="c" to use the c version -/// If a test is only applicable to rust, use `new_uninit_rust`` instead -pub fn new_uninit() -> Result { - UninitializedSandbox::new( - GuestBinary::FilePath(get_c_or_rust_simpleguest_path()), - None, - None, - None, - ) -} - -/// Use this instead of the `new_uninit` if you want your test to only run with the rust guest, not the c guest -pub fn new_uninit_rust() -> Result { - UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().unwrap()), - None, - None, - None, - ) -} - -pub fn get_simpleguest_sandboxes( - writer: Option<&dyn HostFunction1>, // An optional writer to make sure correct info is passed to the host printer -) -> Vec { - let elf_path = get_c_or_rust_simpleguest_path(); - let exe_path = format!("{elf_path}.exe"); - - vec![ - // in hypervisor elf - UninitializedSandbox::new(GuestBinary::FilePath(elf_path.clone()), None, None, writer) - .unwrap() - .evolve(Noop::default()) - .unwrap(), - // in hypervisor exe - UninitializedSandbox::new(GuestBinary::FilePath(exe_path.clone()), None, None, writer) - .unwrap() - .evolve(Noop::default()) - .unwrap(), - // in-process elf - #[cfg(inprocess)] - UninitializedSandbox::new( - GuestBinary::FilePath(elf_path.clone()), - None, - Some(hyperlight_host::SandboxRunOptions::RunInProcess(false)), - writer, - ) - .unwrap() - .evolve(Noop::default()) - .unwrap(), - //in-process exe - #[cfg(inprocess)] - UninitializedSandbox::new( - GuestBinary::FilePath(exe_path.clone()), - None, - Some(hyperlight_host::SandboxRunOptions::RunInProcess(false)), - writer, - ) - .unwrap() - .evolve(Noop::default()) - .unwrap(), - // loadlib in process - #[cfg(all(target_os = "windows", inprocess))] - UninitializedSandbox::new( - GuestBinary::FilePath(exe_path.clone()), - None, - Some(hyperlight_host::SandboxRunOptions::RunInProcess(true)), - writer, - ) - .unwrap() - .evolve(Noop::default()) - .unwrap(), - ] -} - -pub fn get_callbackguest_uninit_sandboxes( - writer: Option<&dyn HostFunction1>, // An optional writer to make sure correct info is passed to the host printer -) -> Vec { - let elf_path = get_c_or_rust_callbackguest_path(); - let exe_path = format!("{elf_path}.exe"); - - vec![ - // in hypervisor elf - UninitializedSandbox::new(GuestBinary::FilePath(elf_path.clone()), None, None, writer) - .unwrap(), - // in hypervisor exe - UninitializedSandbox::new(GuestBinary::FilePath(exe_path.clone()), None, None, writer) - .unwrap(), - // in-process elf - #[cfg(inprocess)] - UninitializedSandbox::new( - GuestBinary::FilePath(elf_path.clone()), - None, - Some(hyperlight_host::SandboxRunOptions::RunInProcess(false)), - writer, - ) - .unwrap(), - //in-process exe - #[cfg(inprocess)] - UninitializedSandbox::new( - GuestBinary::FilePath(exe_path.clone()), - None, - Some(hyperlight_host::SandboxRunOptions::RunInProcess(false)), - writer, - ) - .unwrap(), - // loadlib in process - #[cfg(all(target_os = "windows", inprocess))] - UninitializedSandbox::new( - GuestBinary::FilePath(exe_path.clone()), - None, - Some(hyperlight_host::SandboxRunOptions::RunInProcess(true)), - writer, - ) - .unwrap(), - ] -} - -// returns the the path of simpleguest binary. Picks rust/c version depending on environment variable GUEST (or rust by default if unset) -pub(crate) fn get_c_or_rust_simpleguest_path() -> String { - let guest_type = std::env::var("GUEST").unwrap_or("rust".to_string()); - match guest_type.as_str() { - "rust" => simple_guest_as_string().unwrap(), - "c" => c_simple_guest_as_string().unwrap(), - _ => panic!("Unknown guest type '{guest_type}', use either 'rust' or 'c'"), - } -} - -// returns the the path of callbackguest binary. Picks rust/ version depending on environment variable GUEST (or rust by default if unset) -fn get_c_or_rust_callbackguest_path() -> String { - let guest_type = std::env::var("GUEST").unwrap_or("rust".to_string()); - match guest_type.as_str() { - "rust" => callback_guest_as_string().unwrap(), - "c" => c_callback_guest_as_string().unwrap(), - _ => panic!("Unknown guest type '{guest_type}', use either 'rust' or 'c'"), - } -} +// TODO(danbugs:297): bring back +// /* +// 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. +// */ +// +// use hyperlight_host::func::HostFunction1; +// use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; +// use hyperlight_host::sandbox_state::transition::Noop; +// use hyperlight_host::{GuestBinary, MultiUseSandbox, Result, UninitializedSandbox}; +// use hyperlight_testing::{ +// c_callback_guest_as_string, c_simple_guest_as_string, callback_guest_as_string, +// simple_guest_as_string, +// }; +// +// /// Returns a rust/c simpleguest depending on environment variable GUEST. +// /// Uses rust guest by default. Run test with environment variable GUEST="c" to use the c version +// /// If a test is only applicable to rust, use `new_uninit_rust`` instead +// pub fn new_uninit() -> Result { +// UninitializedSandbox::new( +// GuestBinary::FilePath(get_c_or_rust_simpleguest_path()), +// None, +// None, +// None, +// ) +// } +// +// /// Use this instead of the `new_uninit` if you want your test to only run with the rust guest, not the c guest +// pub fn new_uninit_rust() -> Result { +// UninitializedSandbox::new( +// GuestBinary::FilePath(simple_guest_as_string().unwrap()), +// None, +// None, +// None, +// ) +// } +// +// pub fn get_simpleguest_sandboxes( +// writer: Option<&dyn HostFunction1>, // An optional writer to make sure correct info is passed to the host printer +// ) -> Vec { +// let elf_path = get_c_or_rust_simpleguest_path(); +// let exe_path = format!("{elf_path}.exe"); +// +// vec![ +// // in hypervisor elf +// UninitializedSandbox::new(GuestBinary::FilePath(elf_path.clone()), None, None, writer) +// .unwrap() +// .evolve(Noop::default()) +// .unwrap(), +// // in hypervisor exe +// UninitializedSandbox::new(GuestBinary::FilePath(exe_path.clone()), None, None, writer) +// .unwrap() +// .evolve(Noop::default()) +// .unwrap(), +// // in-process elf +// #[cfg(inprocess)] +// UninitializedSandbox::new( +// GuestBinary::FilePath(elf_path.clone()), +// None, +// Some(hyperlight_host::SandboxRunOptions::RunInProcess(false)), +// writer, +// ) +// .unwrap() +// .evolve(Noop::default()) +// .unwrap(), +// //in-process exe +// #[cfg(inprocess)] +// UninitializedSandbox::new( +// GuestBinary::FilePath(exe_path.clone()), +// None, +// Some(hyperlight_host::SandboxRunOptions::RunInProcess(false)), +// writer, +// ) +// .unwrap() +// .evolve(Noop::default()) +// .unwrap(), +// // loadlib in process +// #[cfg(all(target_os = "windows", inprocess))] +// UninitializedSandbox::new( +// GuestBinary::FilePath(exe_path.clone()), +// None, +// Some(hyperlight_host::SandboxRunOptions::RunInProcess(true)), +// writer, +// ) +// .unwrap() +// .evolve(Noop::default()) +// .unwrap(), +// ] +// } +// +// pub fn get_callbackguest_uninit_sandboxes( +// writer: Option<&dyn HostFunction1>, // An optional writer to make sure correct info is passed to the host printer +// ) -> Vec { +// let elf_path = get_c_or_rust_callbackguest_path(); +// let exe_path = format!("{elf_path}.exe"); +// +// vec![ +// // in hypervisor elf +// UninitializedSandbox::new(GuestBinary::FilePath(elf_path.clone()), None, None, writer) +// .unwrap(), +// // in hypervisor exe +// UninitializedSandbox::new(GuestBinary::FilePath(exe_path.clone()), None, None, writer) +// .unwrap(), +// // in-process elf +// #[cfg(inprocess)] +// UninitializedSandbox::new( +// GuestBinary::FilePath(elf_path.clone()), +// None, +// Some(hyperlight_host::SandboxRunOptions::RunInProcess(false)), +// writer, +// ) +// .unwrap(), +// //in-process exe +// #[cfg(inprocess)] +// UninitializedSandbox::new( +// GuestBinary::FilePath(exe_path.clone()), +// None, +// Some(hyperlight_host::SandboxRunOptions::RunInProcess(false)), +// writer, +// ) +// .unwrap(), +// // loadlib in process +// #[cfg(all(target_os = "windows", inprocess))] +// UninitializedSandbox::new( +// GuestBinary::FilePath(exe_path.clone()), +// None, +// Some(hyperlight_host::SandboxRunOptions::RunInProcess(true)), +// writer, +// ) +// .unwrap(), +// ] +// } +// +// // returns the the path of simpleguest binary. Picks rust/c version depending on environment variable GUEST (or rust by default if unset) +// pub(crate) fn get_c_or_rust_simpleguest_path() -> String { +// let guest_type = std::env::var("GUEST").unwrap_or("rust".to_string()); +// match guest_type.as_str() { +// "rust" => simple_guest_as_string().unwrap(), +// "c" => c_simple_guest_as_string().unwrap(), +// _ => panic!("Unknown guest type '{guest_type}', use either 'rust' or 'c'"), +// } +// } +// +// // returns the the path of callbackguest binary. Picks rust/ version depending on environment variable GUEST (or rust by default if unset) +// fn get_c_or_rust_callbackguest_path() -> String { +// let guest_type = std::env::var("GUEST").unwrap_or("rust".to_string()); +// match guest_type.as_str() { +// "rust" => callback_guest_as_string().unwrap(), +// "c" => c_callback_guest_as_string().unwrap(), +// _ => panic!("Unknown guest type '{guest_type}', use either 'rust' or 'c'"), +// } +// } diff --git a/src/hyperlight_host/tests/integration_test.rs b/src/hyperlight_host/tests/integration_test.rs index 6359909b0..a93aa766a 100644 --- a/src/hyperlight_host/tests/integration_test.rs +++ b/src/hyperlight_host/tests/integration_test.rs @@ -1,542 +1,543 @@ -/* -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. -*/ - -use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use hyperlight_common::mem::PAGE_SIZE; -use hyperlight_host::func::{ParameterValue, ReturnType, ReturnValue}; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; -use hyperlight_host::sandbox_state::transition::Noop; -use hyperlight_host::{GuestBinary, HyperlightError, MultiUseSandbox, UninitializedSandbox}; -use hyperlight_testing::simplelogger::{SimpleLogger, LOGGER}; -use hyperlight_testing::{c_simple_guest_as_string, simple_guest_as_string}; -use log::LevelFilter; - -pub mod common; // pub to disable dead_code warning -use crate::common::{new_uninit, new_uninit_rust}; - -#[test] -fn print_four_args_c_guest() { - let path = c_simple_guest_as_string().unwrap(); - let guest_path = GuestBinary::FilePath(path); - let uninit = UninitializedSandbox::new(guest_path, None, None, None); - let mut sbox1 = uninit.unwrap().evolve(Noop::default()).unwrap(); - - let res = sbox1.call_guest_function_by_name( - "PrintFourArgs", - ReturnType::String, - Some(vec![ - ParameterValue::String("Test4".to_string()), - ParameterValue::Int(3_i32), - ParameterValue::Long(4_i64), - ParameterValue::String("Tested".to_string()), - ]), - ); - println!("{:?}", res); - assert!(matches!(res, Ok(ReturnValue::Int(46)))); -} - -// Checks that guest can abort with a specific code. -#[test] -fn guest_abort() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - let error_code: u8 = 13; // this is arbitrary - let res = sbox1 - .call_guest_function_by_name( - "GuestAbortWithCode", - ReturnType::Void, - Some(vec![ParameterValue::Int(error_code as i32)]), - ) - .unwrap_err(); - println!("{:?}", res); - assert!( - matches!(res, HyperlightError::GuestAborted(code, message) if (code == error_code && message.is_empty()) ) - ); -} - -#[test] -fn guest_abort_with_context1() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - - let res = sbox1 - .call_guest_function_by_name( - "GuestAbortWithMessage", - ReturnType::Void, - Some(vec![ - ParameterValue::Int(25), - ParameterValue::String("Oh no".to_string()), - ]), - ) - .unwrap_err(); - println!("{:?}", res); - assert!( - matches!(res, HyperlightError::GuestAborted(code, context) if (code == 25 && context == "Oh no")) - ); -} - -#[test] -fn guest_abort_with_context2() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - - // The buffer size for the panic context is 1024 bytes. - // This test will see what happens if the panic message is longer than that - let abort_message = "Lorem ipsum dolor sit amet, \ - consectetur adipiscing elit, \ - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \ - Nec feugiat nisl pretium fusce. \ - Amet mattis vulputate enim nulla aliquet porttitor lacus. \ - Nunc congue nisi vitae suscipit tellus. \ - Erat imperdiet sed euismod nisi porta lorem mollis aliquam ut. \ - Amet tellus cras adipiscing enim eu turpis egestas. \ - Blandit volutpat maecenas volutpat blandit aliquam etiam erat velit scelerisque. \ - Tristique senectus et netus et malesuada. \ - Eu turpis egestas pretium aenean pharetra magna ac placerat vestibulum. \ - Adipiscing at in tellus integer feugiat. \ - Faucibus vitae aliquet nec ullamcorper sit amet risus. \ - \n\ - Eros in cursus turpis massa tincidunt dui. \ - Purus non enim praesent elementum facilisis leo vel fringilla. \ - Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi. \ - Id leo in vitae turpis. At lectus urna duis convallis convallis tellus id interdum. \ - Purus sit amet volutpat consequat. Egestas purus viverra accumsan in. \ - Sodales ut etiam sit amet nisl. Lacus sed viverra tellus in hac. \ - Nec ullamcorper sit amet risus nullam eget. \ - Adipiscing bibendum est ultricies integer quis auctor. \ - Vitae elementum curabitur vitae nunc sed velit dignissim sodales ut. \ - Auctor neque vitae tempus quam pellentesque nec. \ - Non pulvinar neque laoreet suspendisse interdum consectetur libero. \ - Mollis nunc sed id semper. \ - Et sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. \ - Arcu felis bibendum ut tristique et. \ - Proin sagittis nisl rhoncus mattis rhoncus urna. Magna eget est lorem ipsum."; - - let res = sbox1 - .call_guest_function_by_name( - "GuestAbortWithMessage", - ReturnType::Void, - Some(vec![ - ParameterValue::Int(60), - ParameterValue::String(abort_message.to_string()), - ]), - ) - .unwrap_err(); - println!("{:?}", res); - assert!( - matches!(res, HyperlightError::GuestAborted(_, context) if context.contains(&abort_message[..400])) - ); -} - -// Ensure abort with context works for c guests. -// Just run this manually for now since we only build c guests on Windows and will -// hopefully be removing the c guest library soon. -#[test] -fn guest_abort_c_guest() { - let path = c_simple_guest_as_string().unwrap(); - let guest_path = GuestBinary::FilePath(path); - let uninit = UninitializedSandbox::new(guest_path, None, None, None); - let mut sbox1 = uninit.unwrap().evolve(Noop::default()).unwrap(); - - let res = sbox1 - .call_guest_function_by_name( - "GuestAbortWithMessage", - ReturnType::Void, - Some(vec![ - ParameterValue::Int(75_i32), - ParameterValue::String("This is a test error message".to_string()), - ]), - ) - .unwrap_err(); - println!("{:?}", res); - assert!( - matches!(res, HyperlightError::GuestAborted(code, message) if (code == 75 && message == "This is a test error message")) - ); -} - -#[test] -fn guest_panic() { - // this test is rust-specific - let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); - - let res = sbox1 - .call_guest_function_by_name( - "guest_panic", - ReturnType::Void, - Some(vec![ParameterValue::String( - "Error... error...".to_string(), - )]), - ) - .unwrap_err(); - println!("{:?}", res); - assert!( - matches!(res, HyperlightError::GuestAborted(code, context) if code == ErrorCode::UnknownError as u8 && context.contains("\nError... error...")) - ) -} - -#[test] -fn guest_malloc() { - // this test is rust-only - let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); - - let size_to_allocate = 2000; - let res = sbox1 - .call_guest_function_by_name( - "TestMalloc", - ReturnType::Int, - Some(vec![ParameterValue::Int(size_to_allocate)]), - ) - .unwrap(); - assert!(matches!(res, ReturnValue::Int(_))); -} - -#[test] -fn guest_allocate_vec() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - - let size_to_allocate = 2000; - - let res = sbox1 - .call_guest_function_by_name( - "CallMalloc", // uses the rust allocator to allocate a vector on heap - ReturnType::Int, - Some(vec![ParameterValue::Int(size_to_allocate)]), - ) - .unwrap(); - - assert!(matches!(res, ReturnValue::Int(returned_size) if returned_size == size_to_allocate)); -} - -// checks that malloc failures are captured correctly -#[test] -fn guest_malloc_abort() { - let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); - - let size = 20000000; // some big number that should fail when allocated - - let res = sbox1 - .call_guest_function_by_name( - "TestMalloc", - ReturnType::Int, - Some(vec![ParameterValue::Int(size)]), - ) - .unwrap_err(); - println!("{:?}", res); - assert!( - matches!(res, HyperlightError::GuestAborted(code, _) if code == ErrorCode::MallocFailed as u8) - ); - - // allocate a vector (on heap) that is bigger than the heap - let heap_size = 0x4000; - let size_to_allocate = 0x10000; - assert!(size_to_allocate > heap_size); - - let mut cfg = SandboxConfiguration::default(); - cfg.set_heap_size(heap_size); - let uninit = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().unwrap()), - Some(cfg), - None, - None, - ) - .unwrap(); - let mut sbox2 = uninit.evolve(Noop::default()).unwrap(); - - let res = sbox2.call_guest_function_by_name( - "CallMalloc", // uses the rust allocator to allocate a vector on heap - ReturnType::Int, - Some(vec![ParameterValue::Int(size_to_allocate as i32)]), - ); - println!("{:?}", res); - assert!(matches!( - res.unwrap_err(), - // OOM memory errors in rust allocator are panics. Our panic handler returns ErrorCode::UnknownError on panic - HyperlightError::GuestAborted(code, msg) if code == ErrorCode::UnknownError as u8 && msg.contains("memory allocation of ") - )); -} - -// Tests libc alloca -#[test] -fn dynamic_stack_allocate_c_guest() { - let path = c_simple_guest_as_string().unwrap(); - let guest_path = GuestBinary::FilePath(path); - let uninit = UninitializedSandbox::new(guest_path, None, None, None); - let mut sbox1: MultiUseSandbox = uninit.unwrap().evolve(Noop::default()).unwrap(); - - let res2 = sbox1 - .call_guest_function_by_name( - "StackAllocate", - ReturnType::Int, - Some(vec![ParameterValue::Int(100)]), - ) - .unwrap(); - assert!(matches!(res2, ReturnValue::Int(n) if n == 100)); - - let res = sbox1 - .call_guest_function_by_name( - "StackAllocate", - ReturnType::Int, - Some(vec![ParameterValue::Int(128 * 1024 * 1024)]), - ) - .unwrap_err(); - assert!(matches!(res, HyperlightError::StackOverflow())); -} - -// checks that a small buffer on stack works -#[test] -fn static_stack_allocate() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - - let res = sbox1 - .call_guest_function_by_name("SmallVar", ReturnType::Int, Some(Vec::new())) - .unwrap(); - assert!(matches!(res, ReturnValue::Int(1024))); -} - -// checks that a huge buffer on stack fails with stackoverflow -#[test] -fn static_stack_allocate_overflow() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - let res = sbox1 - .call_guest_function_by_name("LargeVar", ReturnType::Int, Some(Vec::new())) - .unwrap_err(); - assert!(matches!(res, HyperlightError::StackOverflow())); -} - -// checks that a recursive function with stack allocation works, (that chkstk can be called without overflowing) -#[test] -fn recursive_stack_allocate() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - - let iterations = 1; - - sbox1 - .call_guest_function_by_name( - "StackOverflow", - ReturnType::Int, - Some(vec![ParameterValue::Int(iterations)]), - ) - .unwrap(); -} - -// checks stack guard page (between guest stack and heap) -// is properly set up and cannot be written to -#[test] -fn guard_page_check() { - // this test is rust-guest only - let offsets_from_page_guard_start: Vec = vec![ - -1024, - -1, - 0, // should fail - 1, // should fail - 1024, // should fail - PAGE_SIZE as i64 - 1, // should fail - PAGE_SIZE as i64, - PAGE_SIZE as i64 + 1024, - ]; - - let guard_range = 0..PAGE_SIZE as i64; - - for offset in offsets_from_page_guard_start { - // we have to create a sandbox each iteration because can't reuse after MMIO error in release mode - - let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); - let result = sbox1.call_guest_function_by_name( - "test_write_raw_ptr", - ReturnType::String, - Some(vec![ParameterValue::Long(offset)]), - ); - if guard_range.contains(&offset) { - // should have failed - assert!(matches!( - result.unwrap_err(), - HyperlightError::StackOverflow() - )); - } else { - assert!(result.is_ok(), "offset {} should pass", offset) - } - } -} - -#[test] -fn guard_page_check_2() { - // this test is rust-guest only - let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); - - let result = sbox1 - .call_guest_function_by_name("InfiniteRecursion", ReturnType::Void, Some(vec![])) - .unwrap_err(); - assert!(matches!(result, HyperlightError::StackOverflow())); -} - -#[test] -fn execute_on_stack() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - - let result = sbox1 - .call_guest_function_by_name("ExecuteOnStack", ReturnType::String, Some(vec![])) - .unwrap_err(); - - #[cfg(inprocess)] - if let HyperlightError::Error(message) = result { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - assert!(message.starts_with("Unexpected VM Exit") || message.starts_with("unknown Hyper-V run message type")); - } else if #[cfg(target_os = "windows")] { - assert!(message.starts_with("Unexpected VM Exit \"Did not receive a halt from Hypervisor as expected - Received WHV_RUN_VP_EXIT_REASON(4)")); - } else { - panic!("Unexpected"); - } - } - } - - #[cfg(not(inprocess))] - { - let err = result.to_string(); - assert!( - // exception that indicates a page fault - err.contains("EXCEPTION: 0xe") - ); - } -} - -#[test] -#[ignore] // ran from Justfile because requires feature "executable_heap" -fn execute_on_heap() { - let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); - let result = - sbox1.call_guest_function_by_name("ExecuteOnHeap", ReturnType::String, Some(vec![])); - - println!("{:#?}", result); - #[cfg(feature = "executable_heap")] - assert!(result.is_ok()); - - #[cfg(not(feature = "executable_heap"))] - { - assert!(result.is_err()); - let err = result.unwrap_err(); - - assert!(err.to_string().contains("EXCEPTION: 0xe")); - } -} - -#[test] -fn memory_resets_after_failed_guestcall() { - let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); - sbox1 - .call_guest_function_by_name("AddToStaticAndFail", ReturnType::String, None) - .unwrap_err(); - let res = sbox1 - .call_guest_function_by_name("GetStatic", ReturnType::Int, None) - .unwrap(); - assert!( - matches!(res, ReturnValue::Int(0)), - "Expected 0, got {:?}", - res - ); -} - -// checks that a recursive function with stack allocation eventually fails with stackoverflow -#[test] -fn recursive_stack_allocate_overflow() { - let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - - let iterations = 10; - - let res = sbox1 - .call_guest_function_by_name( - "StackOverflow", - ReturnType::Void, - Some(vec![ParameterValue::Int(iterations)]), - ) - .unwrap_err(); - println!("{:?}", res); - assert!(matches!(res, HyperlightError::StackOverflow())); -} - -// Check that log messages are emitted correctly from the guest -// This test is ignored as it sets a logger and therefore maybe impacted by other tests running concurrently -// or it may impact other tests. -// It will run from the command just test-rust as it is included in that target -// It can also be run explicitly with `cargo test --test integration_test log_message -- --ignored` -#[test] -#[ignore] -fn log_message() { - // internal_dispatch_function does a log::trace! in debug mode, and we call it 6 times in `log_test_messages` - let num_fixed_trace_log = if cfg!(debug_assertions) { 6 } else { 0 }; - - let tests = vec![ - (LevelFilter::Trace, 5 + num_fixed_trace_log), - (LevelFilter::Debug, 4), - (LevelFilter::Info, 3), - (LevelFilter::Warn, 2), - (LevelFilter::Error, 1), - (LevelFilter::Off, 0), - ]; - - // init - SimpleLogger::initialize_test_logger(); - - for test in tests { - let (level, expected) = test; - - // Test setting max log level via method on uninit sandbox - log_test_messages(Some(level)); - assert_eq!(expected, LOGGER.num_log_calls()); - - // Set the log level via env var - std::env::set_var("RUST_LOG", format!("hyperlight_guest={}", level)); - log_test_messages(None); - assert_eq!(expected, LOGGER.num_log_calls()); - - std::env::set_var("RUST_LOG", format!("hyperlight_host={}", level)); - log_test_messages(None); - assert_eq!(expected, LOGGER.num_log_calls()); - - std::env::set_var("RUST_LOG", format!("{}", level)); - log_test_messages(None); - assert_eq!(expected, LOGGER.num_log_calls()); - - std::env::remove_var("RUST_LOG"); - } - - // Test that if no log level is set, the default is error - log_test_messages(None); - assert_eq!(1, LOGGER.num_log_calls()); -} - -fn log_test_messages(levelfilter: Option) { - LOGGER.clear_log_calls(); - assert_eq!(0, LOGGER.num_log_calls()); - for level in log::LevelFilter::iter() { - let mut sbox = new_uninit().unwrap(); - if let Some(levelfilter) = levelfilter { - sbox.set_max_guest_log_level(levelfilter); - } - - let mut sbox1 = sbox.evolve(Noop::default()).unwrap(); - - let message = format!("Hello from log_message level {}", level as i32); - sbox1 - .call_guest_function_by_name( - "LogMessage", - ReturnType::Void, - Some(vec![ - ParameterValue::String(message.to_string()), - ParameterValue::Int(level as i32), - ]), - ) - .unwrap(); - } -} +// TODO(danbugs:297): bring back +// /* +// 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. +// */ +// +// use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; +// use hyperlight_common::mem::PAGE_SIZE; +// use hyperlight_host::func::{ParameterValue, ReturnType, ReturnValue}; +// use hyperlight_host::sandbox::SandboxConfiguration; +// use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; +// use hyperlight_host::sandbox_state::transition::Noop; +// use hyperlight_host::{GuestBinary, HyperlightError, MultiUseSandbox, UninitializedSandbox}; +// use hyperlight_testing::simplelogger::{SimpleLogger, LOGGER}; +// use hyperlight_testing::{c_simple_guest_as_string, simple_guest_as_string}; +// use log::LevelFilter; +// +// pub mod common; // pub to disable dead_code warning +// use crate::common::{new_uninit, new_uninit_rust}; +// +// #[test] +// fn print_four_args_c_guest() { +// let path = c_simple_guest_as_string().unwrap(); +// let guest_path = GuestBinary::FilePath(path); +// let uninit = UninitializedSandbox::new(guest_path, None, None, None); +// let mut sbox1 = uninit.unwrap().evolve(Noop::default()).unwrap(); +// +// let res = sbox1.call_guest_function_by_name( +// "PrintFourArgs", +// ReturnType::String, +// Some(vec![ +// ParameterValue::String("Test4".to_string()), +// ParameterValue::Int(3_i32), +// ParameterValue::Long(4_i64), +// ParameterValue::String("Tested".to_string()), +// ]), +// ); +// println!("{:?}", res); +// assert!(matches!(res, Ok(ReturnValue::Int(46)))); +// } +// +// // Checks that guest can abort with a specific code. +// #[test] +// fn guest_abort() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// let error_code: u8 = 13; // this is arbitrary +// let res = sbox1 +// .call_guest_function_by_name( +// "GuestAbortWithCode", +// ReturnType::Void, +// Some(vec![ParameterValue::Int(error_code as i32)]), +// ) +// .unwrap_err(); +// println!("{:?}", res); +// assert!( +// matches!(res, HyperlightError::GuestAborted(code, message) if (code == error_code && message.is_empty()) ) +// ); +// } +// +// #[test] +// fn guest_abort_with_context1() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// +// let res = sbox1 +// .call_guest_function_by_name( +// "GuestAbortWithMessage", +// ReturnType::Void, +// Some(vec![ +// ParameterValue::Int(25), +// ParameterValue::String("Oh no".to_string()), +// ]), +// ) +// .unwrap_err(); +// println!("{:?}", res); +// assert!( +// matches!(res, HyperlightError::GuestAborted(code, context) if (code == 25 && context == "Oh no")) +// ); +// } +// +// #[test] +// fn guest_abort_with_context2() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// +// // The buffer size for the panic context is 1024 bytes. +// // This test will see what happens if the panic message is longer than that +// let abort_message = "Lorem ipsum dolor sit amet, \ +// consectetur adipiscing elit, \ +// sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \ +// Nec feugiat nisl pretium fusce. \ +// Amet mattis vulputate enim nulla aliquet porttitor lacus. \ +// Nunc congue nisi vitae suscipit tellus. \ +// Erat imperdiet sed euismod nisi porta lorem mollis aliquam ut. \ +// Amet tellus cras adipiscing enim eu turpis egestas. \ +// Blandit volutpat maecenas volutpat blandit aliquam etiam erat velit scelerisque. \ +// Tristique senectus et netus et malesuada. \ +// Eu turpis egestas pretium aenean pharetra magna ac placerat vestibulum. \ +// Adipiscing at in tellus integer feugiat. \ +// Faucibus vitae aliquet nec ullamcorper sit amet risus. \ +// \n\ +// Eros in cursus turpis massa tincidunt dui. \ +// Purus non enim praesent elementum facilisis leo vel fringilla. \ +// Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi. \ +// Id leo in vitae turpis. At lectus urna duis convallis convallis tellus id interdum. \ +// Purus sit amet volutpat consequat. Egestas purus viverra accumsan in. \ +// Sodales ut etiam sit amet nisl. Lacus sed viverra tellus in hac. \ +// Nec ullamcorper sit amet risus nullam eget. \ +// Adipiscing bibendum est ultricies integer quis auctor. \ +// Vitae elementum curabitur vitae nunc sed velit dignissim sodales ut. \ +// Auctor neque vitae tempus quam pellentesque nec. \ +// Non pulvinar neque laoreet suspendisse interdum consectetur libero. \ +// Mollis nunc sed id semper. \ +// Et sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. \ +// Arcu felis bibendum ut tristique et. \ +// Proin sagittis nisl rhoncus mattis rhoncus urna. Magna eget est lorem ipsum."; +// +// let res = sbox1 +// .call_guest_function_by_name( +// "GuestAbortWithMessage", +// ReturnType::Void, +// Some(vec![ +// ParameterValue::Int(60), +// ParameterValue::String(abort_message.to_string()), +// ]), +// ) +// .unwrap_err(); +// println!("{:?}", res); +// assert!( +// matches!(res, HyperlightError::GuestAborted(_, context) if context.contains(&abort_message[..400])) +// ); +// } +// +// // Ensure abort with context works for c guests. +// // Just run this manually for now since we only build c guests on Windows and will +// // hopefully be removing the c guest library soon. +// #[test] +// fn guest_abort_c_guest() { +// let path = c_simple_guest_as_string().unwrap(); +// let guest_path = GuestBinary::FilePath(path); +// let uninit = UninitializedSandbox::new(guest_path, None, None, None); +// let mut sbox1 = uninit.unwrap().evolve(Noop::default()).unwrap(); +// +// let res = sbox1 +// .call_guest_function_by_name( +// "GuestAbortWithMessage", +// ReturnType::Void, +// Some(vec![ +// ParameterValue::Int(75_i32), +// ParameterValue::String("This is a test error message".to_string()), +// ]), +// ) +// .unwrap_err(); +// println!("{:?}", res); +// assert!( +// matches!(res, HyperlightError::GuestAborted(code, message) if (code == 75 && message == "This is a test error message")) +// ); +// } +// +// #[test] +// fn guest_panic() { +// // this test is rust-specific +// let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); +// +// let res = sbox1 +// .call_guest_function_by_name( +// "guest_panic", +// ReturnType::Void, +// Some(vec![ParameterValue::String( +// "Error... error...".to_string(), +// )]), +// ) +// .unwrap_err(); +// println!("{:?}", res); +// assert!( +// matches!(res, HyperlightError::GuestAborted(code, context) if code == ErrorCode::UnknownError as u8 && context.contains("\nError... error...")) +// ) +// } +// +// #[test] +// fn guest_malloc() { +// // this test is rust-only +// let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); +// +// let size_to_allocate = 2000; +// let res = sbox1 +// .call_guest_function_by_name( +// "TestMalloc", +// ReturnType::Int, +// Some(vec![ParameterValue::Int(size_to_allocate)]), +// ) +// .unwrap(); +// assert!(matches!(res, ReturnValue::Int(_))); +// } +// +// #[test] +// fn guest_allocate_vec() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// +// let size_to_allocate = 2000; +// +// let res = sbox1 +// .call_guest_function_by_name( +// "CallMalloc", // uses the rust allocator to allocate a vector on heap +// ReturnType::Int, +// Some(vec![ParameterValue::Int(size_to_allocate)]), +// ) +// .unwrap(); +// +// assert!(matches!(res, ReturnValue::Int(returned_size) if returned_size == size_to_allocate)); +// } +// +// // checks that malloc failures are captured correctly +// #[test] +// fn guest_malloc_abort() { +// let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); +// +// let size = 20000000; // some big number that should fail when allocated +// +// let res = sbox1 +// .call_guest_function_by_name( +// "TestMalloc", +// ReturnType::Int, +// Some(vec![ParameterValue::Int(size)]), +// ) +// .unwrap_err(); +// println!("{:?}", res); +// assert!( +// matches!(res, HyperlightError::GuestAborted(code, _) if code == ErrorCode::MallocFailed as u8) +// ); +// +// // allocate a vector (on heap) that is bigger than the heap +// let heap_size = 0x4000; +// let size_to_allocate = 0x10000; +// assert!(size_to_allocate > heap_size); +// +// let mut cfg = SandboxConfiguration::default(); +// cfg.set_heap_size(heap_size); +// let uninit = UninitializedSandbox::new( +// GuestBinary::FilePath(simple_guest_as_string().unwrap()), +// Some(cfg), +// None, +// None, +// ) +// .unwrap(); +// let mut sbox2 = uninit.evolve(Noop::default()).unwrap(); +// +// let res = sbox2.call_guest_function_by_name( +// "CallMalloc", // uses the rust allocator to allocate a vector on heap +// ReturnType::Int, +// Some(vec![ParameterValue::Int(size_to_allocate as i32)]), +// ); +// println!("{:?}", res); +// assert!(matches!( +// res.unwrap_err(), +// // OOM memory errors in rust allocator are panics. Our panic handler returns ErrorCode::UnknownError on panic +// HyperlightError::GuestAborted(code, msg) if code == ErrorCode::UnknownError as u8 && msg.contains("memory allocation of ") +// )); +// } +// +// // Tests libc alloca +// #[test] +// fn dynamic_stack_allocate_c_guest() { +// let path = c_simple_guest_as_string().unwrap(); +// let guest_path = GuestBinary::FilePath(path); +// let uninit = UninitializedSandbox::new(guest_path, None, None, None); +// let mut sbox1: MultiUseSandbox = uninit.unwrap().evolve(Noop::default()).unwrap(); +// +// let res2 = sbox1 +// .call_guest_function_by_name( +// "StackAllocate", +// ReturnType::Int, +// Some(vec![ParameterValue::Int(100)]), +// ) +// .unwrap(); +// assert!(matches!(res2, ReturnValue::Int(n) if n == 100)); +// +// let res = sbox1 +// .call_guest_function_by_name( +// "StackAllocate", +// ReturnType::Int, +// Some(vec![ParameterValue::Int(128 * 1024 * 1024)]), +// ) +// .unwrap_err(); +// assert!(matches!(res, HyperlightError::StackOverflow())); +// } +// +// // checks that a small buffer on stack works +// #[test] +// fn static_stack_allocate() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// +// let res = sbox1 +// .call_guest_function_by_name("SmallVar", ReturnType::Int, Some(Vec::new())) +// .unwrap(); +// assert!(matches!(res, ReturnValue::Int(1024))); +// } +// +// // checks that a huge buffer on stack fails with stackoverflow +// #[test] +// fn static_stack_allocate_overflow() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// let res = sbox1 +// .call_guest_function_by_name("LargeVar", ReturnType::Int, Some(Vec::new())) +// .unwrap_err(); +// assert!(matches!(res, HyperlightError::StackOverflow())); +// } +// +// // checks that a recursive function with stack allocation works, (that chkstk can be called without overflowing) +// #[test] +// fn recursive_stack_allocate() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// +// let iterations = 1; +// +// sbox1 +// .call_guest_function_by_name( +// "StackOverflow", +// ReturnType::Int, +// Some(vec![ParameterValue::Int(iterations)]), +// ) +// .unwrap(); +// } +// +// // checks stack guard page (between guest stack and heap) +// // is properly set up and cannot be written to +// #[test] +// fn guard_page_check() { +// // this test is rust-guest only +// let offsets_from_page_guard_start: Vec = vec![ +// -1024, +// -1, +// 0, // should fail +// 1, // should fail +// 1024, // should fail +// PAGE_SIZE as i64 - 1, // should fail +// PAGE_SIZE as i64, +// PAGE_SIZE as i64 + 1024, +// ]; +// +// let guard_range = 0..PAGE_SIZE as i64; +// +// for offset in offsets_from_page_guard_start { +// // we have to create a sandbox each iteration because can't reuse after MMIO error in release mode +// +// let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); +// let result = sbox1.call_guest_function_by_name( +// "test_write_raw_ptr", +// ReturnType::String, +// Some(vec![ParameterValue::Long(offset)]), +// ); +// if guard_range.contains(&offset) { +// // should have failed +// assert!(matches!( +// result.unwrap_err(), +// HyperlightError::StackOverflow() +// )); +// } else { +// assert!(result.is_ok(), "offset {} should pass", offset) +// } +// } +// } +// +// #[test] +// fn guard_page_check_2() { +// // this test is rust-guest only +// let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); +// +// let result = sbox1 +// .call_guest_function_by_name("InfiniteRecursion", ReturnType::Void, Some(vec![])) +// .unwrap_err(); +// assert!(matches!(result, HyperlightError::StackOverflow())); +// } +// +// #[test] +// fn execute_on_stack() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// +// let result = sbox1 +// .call_guest_function_by_name("ExecuteOnStack", ReturnType::String, Some(vec![])) +// .unwrap_err(); +// +// #[cfg(inprocess)] +// if let HyperlightError::Error(message) = result { +// cfg_if::cfg_if! { +// if #[cfg(target_os = "linux")] { +// assert!(message.starts_with("Unexpected VM Exit") || message.starts_with("unknown Hyper-V run message type")); +// } else if #[cfg(target_os = "windows")] { +// assert!(message.starts_with("Unexpected VM Exit \"Did not receive a halt from Hypervisor as expected - Received WHV_RUN_VP_EXIT_REASON(4)")); +// } else { +// panic!("Unexpected"); +// } +// } +// } +// +// #[cfg(not(inprocess))] +// { +// let err = result.to_string(); +// assert!( +// // exception that indicates a page fault +// err.contains("EXCEPTION: 0xe") +// ); +// } +// } +// +// #[test] +// #[ignore] // ran from Justfile because requires feature "executable_heap" +// fn execute_on_heap() { +// let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); +// let result = +// sbox1.call_guest_function_by_name("ExecuteOnHeap", ReturnType::String, Some(vec![])); +// +// println!("{:#?}", result); +// #[cfg(feature = "executable_heap")] +// assert!(result.is_ok()); +// +// #[cfg(not(feature = "executable_heap"))] +// { +// assert!(result.is_err()); +// let err = result.unwrap_err(); +// +// assert!(err.to_string().contains("EXCEPTION: 0xe")); +// } +// } +// +// #[test] +// fn memory_resets_after_failed_guestcall() { +// let mut sbox1 = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap(); +// sbox1 +// .call_guest_function_by_name("AddToStaticAndFail", ReturnType::String, None) +// .unwrap_err(); +// let res = sbox1 +// .call_guest_function_by_name("GetStatic", ReturnType::Int, None) +// .unwrap(); +// assert!( +// matches!(res, ReturnValue::Int(0)), +// "Expected 0, got {:?}", +// res +// ); +// } +// +// // checks that a recursive function with stack allocation eventually fails with stackoverflow +// #[test] +// fn recursive_stack_allocate_overflow() { +// let mut sbox1 = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// +// let iterations = 10; +// +// let res = sbox1 +// .call_guest_function_by_name( +// "StackOverflow", +// ReturnType::Void, +// Some(vec![ParameterValue::Int(iterations)]), +// ) +// .unwrap_err(); +// println!("{:?}", res); +// assert!(matches!(res, HyperlightError::StackOverflow())); +// } +// +// // Check that log messages are emitted correctly from the guest +// // This test is ignored as it sets a logger and therefore maybe impacted by other tests running concurrently +// // or it may impact other tests. +// // It will run from the command just test-rust as it is included in that target +// // It can also be run explicitly with `cargo test --test integration_test log_message -- --ignored` +// #[test] +// #[ignore] +// fn log_message() { +// // internal_dispatch_function does a log::trace! in debug mode, and we call it 6 times in `log_test_messages` +// let num_fixed_trace_log = if cfg!(debug_assertions) { 6 } else { 0 }; +// +// let tests = vec![ +// (LevelFilter::Trace, 5 + num_fixed_trace_log), +// (LevelFilter::Debug, 4), +// (LevelFilter::Info, 3), +// (LevelFilter::Warn, 2), +// (LevelFilter::Error, 1), +// (LevelFilter::Off, 0), +// ]; +// +// // init +// SimpleLogger::initialize_test_logger(); +// +// for test in tests { +// let (level, expected) = test; +// +// // Test setting max log level via method on uninit sandbox +// log_test_messages(Some(level)); +// assert_eq!(expected, LOGGER.num_log_calls()); +// +// // Set the log level via env var +// std::env::set_var("RUST_LOG", format!("hyperlight_guest={}", level)); +// log_test_messages(None); +// assert_eq!(expected, LOGGER.num_log_calls()); +// +// std::env::set_var("RUST_LOG", format!("hyperlight_host={}", level)); +// log_test_messages(None); +// assert_eq!(expected, LOGGER.num_log_calls()); +// +// std::env::set_var("RUST_LOG", format!("{}", level)); +// log_test_messages(None); +// assert_eq!(expected, LOGGER.num_log_calls()); +// +// std::env::remove_var("RUST_LOG"); +// } +// +// // Test that if no log level is set, the default is error +// log_test_messages(None); +// assert_eq!(1, LOGGER.num_log_calls()); +// } +// +// fn log_test_messages(levelfilter: Option) { +// LOGGER.clear_log_calls(); +// assert_eq!(0, LOGGER.num_log_calls()); +// for level in log::LevelFilter::iter() { +// let mut sbox = new_uninit().unwrap(); +// if let Some(levelfilter) = levelfilter { +// sbox.set_max_guest_log_level(levelfilter); +// } +// +// let mut sbox1 = sbox.evolve(Noop::default()).unwrap(); +// +// let message = format!("Hello from log_message level {}", level as i32); +// sbox1 +// .call_guest_function_by_name( +// "LogMessage", +// ReturnType::Void, +// Some(vec![ +// ParameterValue::String(message.to_string()), +// ParameterValue::Int(level as i32), +// ]), +// ) +// .unwrap(); +// } +// } diff --git a/src/hyperlight_host/tests/sandbox_host_tests.rs b/src/hyperlight_host/tests/sandbox_host_tests.rs index cb8452cac..cd4995c08 100644 --- a/src/hyperlight_host/tests/sandbox_host_tests.rs +++ b/src/hyperlight_host/tests/sandbox_host_tests.rs @@ -1,631 +1,632 @@ -/* -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. -*/ - -use core::f64; -use std::sync::{Arc, Mutex}; - -use common::new_uninit; -use hyperlight_host::func::{HostFunction1, ParameterValue, ReturnType, ReturnValue}; -use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; -use hyperlight_host::sandbox_state::transition::Noop; -use hyperlight_host::{ - new_error, GuestBinary, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox, -}; -use hyperlight_testing::simple_guest_as_string; -#[cfg(target_os = "windows")] -use serial_test::serial; // using LoadLibrary requires serial tests - -pub mod common; // pub to disable dead_code warning -use crate::common::{get_callbackguest_uninit_sandboxes, get_simpleguest_sandboxes}; - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn pass_byte_array() { - for sandbox in get_simpleguest_sandboxes(None).into_iter() { - let mut ctx = sandbox.new_call_context(); - const LEN: usize = 10; - let bytes = vec![1u8; LEN]; - let res = ctx.call( - "SetByteArrayToZero", - ReturnType::VecBytes, - Some(vec![ParameterValue::VecBytes(bytes.clone())]), - ); - - match res.unwrap() { - ReturnValue::VecBytes(res_bytes) => { - assert_eq!(res_bytes.len(), LEN); - assert!(res_bytes.iter().all(|&b| b == 0)); - } - _ => panic!("Expected VecBytes"), - } - - let res = ctx.call( - "SetByteArrayToZeroNoLength", - ReturnType::Int, - Some(vec![ParameterValue::VecBytes(bytes.clone())]), - ); - assert!(res.is_err()); // missing length param - } -} - -#[test] -#[ignore = "Fails with mismatched float only when c .exe guest?!"] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn float_roundtrip() { - let doubles = [ - 0.0, - -0.0, - 1.0, - -1.0, - std::f64::consts::PI, - -std::f64::consts::PI, - -1231.43821, - f64::MAX, - f64::MIN, - f64::EPSILON, - f64::INFINITY, - -f64::INFINITY, - f64::NAN, - -f64::NAN, - ]; - let floats = [ - 0.0, - -0.0, - 1.0, - -1.0, - std::f32::consts::PI, - -std::f32::consts::PI, - -1231.4382, - f32::MAX, - f32::MIN, - f32::EPSILON, - f32::INFINITY, - -f32::INFINITY, - f32::NAN, - -f32::NAN, - ]; - let mut sandbox: MultiUseSandbox = new_uninit().unwrap().evolve(Noop::default()).unwrap(); - for f in doubles.iter() { - let res = sandbox.call_guest_function_by_name( - "EchoDouble", - ReturnType::Double, - Some(vec![ParameterValue::Double(*f)]), - ); - - assert!( - matches!(res, Ok(ReturnValue::Double(f2)) if f2 == *f || f2.is_nan() && f.is_nan()), - "Expected {:?} but got {:?}", - f, - res - ); - } - for f in floats.iter() { - let res = sandbox.call_guest_function_by_name( - "EchoFloat", - ReturnType::Float, - Some(vec![ParameterValue::Float(*f)]), - ); - - assert!( - matches!(res, Ok(ReturnValue::Float(f2)) if f2 == *f || f2.is_nan() && f.is_nan()), - "Expected {:?} but got {:?}", - f, - res - ); - } -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn invalid_guest_function_name() { - for mut sandbox in get_simpleguest_sandboxes(None).into_iter() { - let fn_name = "FunctionDoesntExist"; - let res = sandbox.call_guest_function_by_name(fn_name, ReturnType::Int, None); - println!("{:?}", res); - assert!( - matches!(res.unwrap_err(), HyperlightError::GuestError(hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode::GuestFunctionNotFound, error_name) if error_name == fn_name) - ); - } -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn set_static() { - for mut sandbox in get_simpleguest_sandboxes(None).into_iter() { - let fn_name = "SetStatic"; - let res = sandbox.call_guest_function_by_name(fn_name, ReturnType::Int, None); - println!("{:?}", res); - assert!(res.is_ok()); - // the result is the size of the static array in the guest - assert_eq!(res.unwrap(), ReturnValue::Int(1024 * 1024)); - } -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn multiple_parameters() { - let messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = messages.clone(); - let writer = move |msg| { - let mut lock = messages_clone - .try_lock() - .map_err(|_| new_error!("Error locking")) - .unwrap(); - lock.push(msg); - Ok(0) - }; - - let writer_func = Arc::new(Mutex::new(writer)); - - let test_cases = vec![ - ( - "PrintTwoArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ], - format!("Message: arg1:{} arg2:{}.", "1", 2), - ), - ( - "PrintThreeArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ], - format!("Message: arg1:{} arg2:{} arg3:{}.", "1", 2, 3), - ), - ( - "PrintFourArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ParameterValue::String("4".to_string()), - ], - format!("Message: arg1:{} arg2:{} arg3:{} arg4:{}.", "1", 2, 3, "4"), - ), - ( - "PrintFiveArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ParameterValue::String("4".to_string()), - ParameterValue::String("5".to_string()), - ], - format!( - "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{}.", - "1", 2, 3, "4", "5" - ), - ), - ( - "PrintSixArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ParameterValue::String("4".to_string()), - ParameterValue::String("5".to_string()), - ParameterValue::Bool(true), - ], - format!( - "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{}.", - "1", 2, 3, "4", "5", true - ), - ), - ( - "PrintSevenArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ParameterValue::String("4".to_string()), - ParameterValue::String("5".to_string()), - ParameterValue::Bool(true), - ParameterValue::Bool(false), - ], - format!( - "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{}.", - "1", 2, 3, "4", "5", true, false - ), - ), - ( - "PrintEightArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ParameterValue::String("4".to_string()), - ParameterValue::String("5".to_string()), - ParameterValue::Bool(true), - ParameterValue::Bool(false), - ParameterValue::UInt(8), - ], - format!( - "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{} arg8:{}.", - "1", 2, 3, "4", "5", true, false, 8 - ), - ), - ( - "PrintNineArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ParameterValue::String("4".to_string()), - ParameterValue::String("5".to_string()), - ParameterValue::Bool(true), - ParameterValue::Bool(false), - ParameterValue::UInt(8), - ParameterValue::ULong(9), - ], - format!( - "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{} arg8:{} arg9:{}.", - "1", 2, 3, "4", "5", true, false, 8, 9 - ), - ), - ( - "PrintTenArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ParameterValue::String("4".to_string()), - ParameterValue::String("5".to_string()), - ParameterValue::Bool(true), - ParameterValue::Bool(false), - ParameterValue::UInt(8), - ParameterValue::ULong(9), - ParameterValue::Int(10), - ], - format!( - "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{} arg8:{} arg9:{} arg10:{}.", - "1", 2, 3, "4", "5", true, false, 8, 9, 10 - ), - ), - ( - "PrintElevenArgs", - vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ParameterValue::Long(3), - ParameterValue::String("4".to_string()), - ParameterValue::String("5".to_string()), - ParameterValue::Bool(true), - ParameterValue::Bool(false), - ParameterValue::UInt(8), - ParameterValue::ULong(9), - ParameterValue::Int(10), - ParameterValue::Float(3.123), - ], - format!( - "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{} arg8:{} arg9:{} arg10:{} arg11:{}.", - "1", 2, 3, "4", "5", true, false, 8, 9, 10, 3.123 - ), - ) - ]; - - for mut sandbox in get_simpleguest_sandboxes(Some(&writer_func)).into_iter() { - for (fn_name, args, _expected) in test_cases.clone().into_iter() { - let res = sandbox.call_guest_function_by_name(fn_name, ReturnType::Int, Some(args)); - println!("{:?}", res); - assert!(res.is_ok()); - } - } - - let lock = messages - .try_lock() - .map_err(|_| new_error!("Error locking")) - .unwrap(); - lock.clone() - .into_iter() - .zip(test_cases) - .for_each(|(printed_msg, expected)| { - println!("{:?}", printed_msg); - assert_eq!(printed_msg, expected.2); - }); -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn incorrect_parameter_type() { - for mut sandbox in get_simpleguest_sandboxes(None) { - let res = sandbox.call_guest_function_by_name( - "Echo", - ReturnType::Int, - Some(vec![ - ParameterValue::Int(2), // should be string - ]), - ); - - assert!(matches!( - res.unwrap_err(), - HyperlightError::GuestError( - hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode::GuestFunctionParameterTypeMismatch, - msg - ) if msg == "Expected parameter type String for parameter index 0 of function Echo but got Int." - )); - } -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn incorrect_parameter_num() { - for mut sandbox in get_simpleguest_sandboxes(None).into_iter() { - let res = sandbox.call_guest_function_by_name( - "Echo", - ReturnType::Int, - Some(vec![ - ParameterValue::String("1".to_string()), - ParameterValue::Int(2), - ]), - ); - assert!(matches!( - res.unwrap_err(), - HyperlightError::GuestError( - hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode::GuestFunctionIncorrecNoOfParameters, - msg - ) if msg == "Called function Echo with 2 parameters but it takes 1." - )); - } -} - -#[test] -fn max_memory_sandbox() { - let mut cfg = SandboxConfiguration::default(); - cfg.set_input_data_size(0x40000000); - let a = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_as_string().unwrap()), - Some(cfg), - None, - None, - ); - - assert!(matches!( - a.unwrap_err(), - HyperlightError::MemoryRequestTooBig(..) - )); -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn iostack_is_working() { - for mut sandbox in get_simpleguest_sandboxes(None).into_iter() { - let res = sandbox.call_guest_function_by_name( - "ThisIsNotARealFunctionButTheNameIsImportant", - ReturnType::Int, - None, - ); - println!("{:?}", res); - assert!(res.is_ok()); - assert_eq!(res.unwrap(), ReturnValue::Int(99)); - } -} - -fn simple_test_helper() -> Result<()> { - let messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = messages.clone(); - let writer = move |msg: String| { - let len = msg.len(); - let mut lock = messages_clone - .try_lock() - .map_err(|_| new_error!("Error locking")) - .unwrap(); - lock.push(msg); - Ok(len as i32) - }; - - let message = "hello"; - let message2 = "world"; - - let writer_func = Arc::new(Mutex::new(writer)); - for mut sandbox in get_simpleguest_sandboxes(Some(&writer_func)).into_iter() { - let res = sandbox.call_guest_function_by_name( - "PrintOutput", - ReturnType::Int, - Some(vec![ParameterValue::String(message.to_string())]), - ); - println!("res: {:?}", res); - assert!(matches!(res, Ok(ReturnValue::Int(5)))); - - let res2 = sandbox.call_guest_function_by_name( - "Echo", - ReturnType::String, - Some(vec![ParameterValue::String(message2.to_string())]), - ); - println!("res2: {:?}", res2); - assert!(matches!(res2, Ok(ReturnValue::String(s)) if s == "world")); - - let buffer = vec![1u8, 2, 3, 4, 5, 6]; - let res3 = sandbox.call_guest_function_by_name( - "GetSizePrefixedBuffer", - ReturnType::Int, - Some(vec![ParameterValue::VecBytes(buffer.clone())]), - ); - println!("res3: {:?}", res3); - assert!(matches!(res3, Ok(ReturnValue::VecBytes(v)) if v == buffer)); - } - - let expected_calls = { - if cfg!(all(target_os = "windows", inprocess)) { - // windows debug build - 5 - } else if cfg!(inprocess) { - // linux debug build - 4 - } else { - // {windows,linux} release build - 2 - } - }; - - assert_eq!( - messages - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .len(), - expected_calls - ); - - assert!(messages - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .iter() - .all(|msg| msg == message)); - Ok(()) -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn simple_test() { - simple_test_helper().unwrap(); -} - -#[test] -#[cfg(target_os = "linux")] -fn simple_test_parallel() { - let handles: Vec<_> = (0..50) - .map(|_| { - std::thread::spawn(|| { - simple_test_helper().unwrap(); - }) - }) - .collect(); - - for handle in handles { - handle.join().unwrap(); - } -} - -#[test] -#[serial] -#[cfg(all(target_os = "windows", inprocess))] -fn only_one_sandbox_instance_with_loadlib() { - use hyperlight_host::SandboxRunOptions; - use hyperlight_testing::simple_guest_exe_as_string; - - let _sandbox = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_exe_as_string().unwrap()), - None, - Some(SandboxRunOptions::RunInProcess(true)), - None, - ) - .unwrap(); - - let err = UninitializedSandbox::new( - GuestBinary::FilePath(simple_guest_exe_as_string().unwrap()), - None, - Some(SandboxRunOptions::RunInProcess(true)), - None, - ) - .unwrap_err(); //should fail - - assert!( - matches!(err, HyperlightError::Error(msg) if msg.starts_with("LoadedLib: Only one guest binary can be loaded at any single time")) - ); -} - -fn callback_test_helper() -> Result<()> { - for mut sandbox in get_callbackguest_uninit_sandboxes(None).into_iter() { - // create host function - let vec = Arc::new(Mutex::new(vec![])); - let vec_cloned = vec.clone(); - let host_func1 = Arc::new(Mutex::new(move |msg: String| { - let len = msg.len(); - vec_cloned - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .push(msg); - Ok(len as i32) - })); - - host_func1.register(&mut sandbox, "HostMethod1").unwrap(); - - // call guest function that calls host function - let mut init_sandbox: MultiUseSandbox = sandbox.evolve(Noop::default())?; - let msg = "Hello world"; - init_sandbox.call_guest_function_by_name( - "GuestMethod1", - ReturnType::Int, - Some(vec![ParameterValue::String(msg.to_string())]), - )?; - - assert_eq!( - vec.try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .len(), - 1 - ); - assert_eq!( - vec.try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .remove(0), - format!("Hello from GuestFunction1, {}", msg) - ); - } - Ok(()) -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn callback_test() { - callback_test_helper().unwrap(); -} - -#[test] -#[cfg(target_os = "linux")] // windows can't run parallel with LoadLibrary -fn callback_test_parallel() { - let handles: Vec<_> = (0..100) - .map(|_| { - std::thread::spawn(|| { - callback_test_helper().unwrap(); - }) - }) - .collect(); - - for handle in handles { - handle.join().unwrap(); - } -} - -#[test] -#[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests -fn host_function_error() -> Result<()> { - // TODO: Remove the `.take(1)`, which makes this test only run in hypervisor. - // This test does not work when running in process. This is because when running in-process, - // when a host function returns an error, an infinite loop is created. - for mut sandbox in get_callbackguest_uninit_sandboxes(None).into_iter().take(1) { - // create host function - let host_func1 = Arc::new(Mutex::new(|_msg: String| -> Result { - Err(new_error!("Host function error!")) - })); - host_func1.register(&mut sandbox, "HostMethod1").unwrap(); - - // call guest function that calls host function - let mut init_sandbox: MultiUseSandbox = sandbox.evolve(Noop::default())?; - let msg = "Hello world"; - let res = init_sandbox.call_guest_function_by_name( - "GuestMethod1", - ReturnType::Int, - Some(vec![ParameterValue::String(msg.to_string())]), - ); - println!("res {:?}", res); - assert!(matches!(res, Err(HyperlightError::Error(msg)) if msg == "Host function error!")); - } - Ok(()) -} +// TODO(danbugs:297): bring back +// /* +// 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. +// */ +// +// use core::f64; +// use std::sync::{Arc, Mutex}; +// +// use common::new_uninit; +// use hyperlight_host::func::{HostFunction1, ParameterValue, ReturnType, ReturnValue}; +// use hyperlight_host::sandbox::SandboxConfiguration; +// use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; +// use hyperlight_host::sandbox_state::transition::Noop; +// use hyperlight_host::{ +// new_error, GuestBinary, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox, +// }; +// use hyperlight_testing::simple_guest_as_string; +// #[cfg(target_os = "windows")] +// use serial_test::serial; // using LoadLibrary requires serial tests +// +// pub mod common; // pub to disable dead_code warning +// use crate::common::{get_callbackguest_uninit_sandboxes, get_simpleguest_sandboxes}; +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn pass_byte_array() { +// for sandbox in get_simpleguest_sandboxes(None).into_iter() { +// let mut ctx = sandbox.new_call_context(); +// const LEN: usize = 10; +// let bytes = vec![1u8; LEN]; +// let res = ctx.call( +// "SetByteArrayToZero", +// ReturnType::VecBytes, +// Some(vec![ParameterValue::VecBytes(bytes.clone())]), +// ); +// +// match res.unwrap() { +// ReturnValue::VecBytes(res_bytes) => { +// assert_eq!(res_bytes.len(), LEN); +// assert!(res_bytes.iter().all(|&b| b == 0)); +// } +// _ => panic!("Expected VecBytes"), +// } +// +// let res = ctx.call( +// "SetByteArrayToZeroNoLength", +// ReturnType::Int, +// Some(vec![ParameterValue::VecBytes(bytes.clone())]), +// ); +// assert!(res.is_err()); // missing length param +// } +// } +// +// #[test] +// #[ignore = "Fails with mismatched float only when c .exe guest?!"] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn float_roundtrip() { +// let doubles = [ +// 0.0, +// -0.0, +// 1.0, +// -1.0, +// std::f64::consts::PI, +// -std::f64::consts::PI, +// -1231.43821, +// f64::MAX, +// f64::MIN, +// f64::EPSILON, +// f64::INFINITY, +// -f64::INFINITY, +// f64::NAN, +// -f64::NAN, +// ]; +// let floats = [ +// 0.0, +// -0.0, +// 1.0, +// -1.0, +// std::f32::consts::PI, +// -std::f32::consts::PI, +// -1231.4382, +// f32::MAX, +// f32::MIN, +// f32::EPSILON, +// f32::INFINITY, +// -f32::INFINITY, +// f32::NAN, +// -f32::NAN, +// ]; +// let mut sandbox: MultiUseSandbox = new_uninit().unwrap().evolve(Noop::default()).unwrap(); +// for f in doubles.iter() { +// let res = sandbox.call_guest_function_by_name( +// "EchoDouble", +// ReturnType::Double, +// Some(vec![ParameterValue::Double(*f)]), +// ); +// +// assert!( +// matches!(res, Ok(ReturnValue::Double(f2)) if f2 == *f || f2.is_nan() && f.is_nan()), +// "Expected {:?} but got {:?}", +// f, +// res +// ); +// } +// for f in floats.iter() { +// let res = sandbox.call_guest_function_by_name( +// "EchoFloat", +// ReturnType::Float, +// Some(vec![ParameterValue::Float(*f)]), +// ); +// +// assert!( +// matches!(res, Ok(ReturnValue::Float(f2)) if f2 == *f || f2.is_nan() && f.is_nan()), +// "Expected {:?} but got {:?}", +// f, +// res +// ); +// } +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn invalid_guest_function_name() { +// for mut sandbox in get_simpleguest_sandboxes(None).into_iter() { +// let fn_name = "FunctionDoesntExist"; +// let res = sandbox.call_guest_function_by_name(fn_name, ReturnType::Int, None); +// println!("{:?}", res); +// assert!( +// matches!(res.unwrap_err(), HyperlightError::GuestError(hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode::GuestFunctionNotFound, error_name) if error_name == fn_name) +// ); +// } +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn set_static() { +// for mut sandbox in get_simpleguest_sandboxes(None).into_iter() { +// let fn_name = "SetStatic"; +// let res = sandbox.call_guest_function_by_name(fn_name, ReturnType::Int, None); +// println!("{:?}", res); +// assert!(res.is_ok()); +// // the result is the size of the static array in the guest +// assert_eq!(res.unwrap(), ReturnValue::Int(1024 * 1024)); +// } +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn multiple_parameters() { +// let messages = Arc::new(Mutex::new(Vec::new())); +// let messages_clone = messages.clone(); +// let writer = move |msg| { +// let mut lock = messages_clone +// .try_lock() +// .map_err(|_| new_error!("Error locking")) +// .unwrap(); +// lock.push(msg); +// Ok(0) +// }; +// +// let writer_func = Arc::new(Mutex::new(writer)); +// +// let test_cases = vec![ +// ( +// "PrintTwoArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ], +// format!("Message: arg1:{} arg2:{}.", "1", 2), +// ), +// ( +// "PrintThreeArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ], +// format!("Message: arg1:{} arg2:{} arg3:{}.", "1", 2, 3), +// ), +// ( +// "PrintFourArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ParameterValue::String("4".to_string()), +// ], +// format!("Message: arg1:{} arg2:{} arg3:{} arg4:{}.", "1", 2, 3, "4"), +// ), +// ( +// "PrintFiveArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ParameterValue::String("4".to_string()), +// ParameterValue::String("5".to_string()), +// ], +// format!( +// "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{}.", +// "1", 2, 3, "4", "5" +// ), +// ), +// ( +// "PrintSixArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ParameterValue::String("4".to_string()), +// ParameterValue::String("5".to_string()), +// ParameterValue::Bool(true), +// ], +// format!( +// "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{}.", +// "1", 2, 3, "4", "5", true +// ), +// ), +// ( +// "PrintSevenArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ParameterValue::String("4".to_string()), +// ParameterValue::String("5".to_string()), +// ParameterValue::Bool(true), +// ParameterValue::Bool(false), +// ], +// format!( +// "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{}.", +// "1", 2, 3, "4", "5", true, false +// ), +// ), +// ( +// "PrintEightArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ParameterValue::String("4".to_string()), +// ParameterValue::String("5".to_string()), +// ParameterValue::Bool(true), +// ParameterValue::Bool(false), +// ParameterValue::UInt(8), +// ], +// format!( +// "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{} arg8:{}.", +// "1", 2, 3, "4", "5", true, false, 8 +// ), +// ), +// ( +// "PrintNineArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ParameterValue::String("4".to_string()), +// ParameterValue::String("5".to_string()), +// ParameterValue::Bool(true), +// ParameterValue::Bool(false), +// ParameterValue::UInt(8), +// ParameterValue::ULong(9), +// ], +// format!( +// "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{} arg8:{} arg9:{}.", +// "1", 2, 3, "4", "5", true, false, 8, 9 +// ), +// ), +// ( +// "PrintTenArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ParameterValue::String("4".to_string()), +// ParameterValue::String("5".to_string()), +// ParameterValue::Bool(true), +// ParameterValue::Bool(false), +// ParameterValue::UInt(8), +// ParameterValue::ULong(9), +// ParameterValue::Int(10), +// ], +// format!( +// "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{} arg8:{} arg9:{} arg10:{}.", +// "1", 2, 3, "4", "5", true, false, 8, 9, 10 +// ), +// ), +// ( +// "PrintElevenArgs", +// vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ParameterValue::Long(3), +// ParameterValue::String("4".to_string()), +// ParameterValue::String("5".to_string()), +// ParameterValue::Bool(true), +// ParameterValue::Bool(false), +// ParameterValue::UInt(8), +// ParameterValue::ULong(9), +// ParameterValue::Int(10), +// ParameterValue::Float(3.123), +// ], +// format!( +// "Message: arg1:{} arg2:{} arg3:{} arg4:{} arg5:{} arg6:{} arg7:{} arg8:{} arg9:{} arg10:{} arg11:{}.", +// "1", 2, 3, "4", "5", true, false, 8, 9, 10, 3.123 +// ), +// ) +// ]; +// +// for mut sandbox in get_simpleguest_sandboxes(Some(&writer_func)).into_iter() { +// for (fn_name, args, _expected) in test_cases.clone().into_iter() { +// let res = sandbox.call_guest_function_by_name(fn_name, ReturnType::Int, Some(args)); +// println!("{:?}", res); +// assert!(res.is_ok()); +// } +// } +// +// let lock = messages +// .try_lock() +// .map_err(|_| new_error!("Error locking")) +// .unwrap(); +// lock.clone() +// .into_iter() +// .zip(test_cases) +// .for_each(|(printed_msg, expected)| { +// println!("{:?}", printed_msg); +// assert_eq!(printed_msg, expected.2); +// }); +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn incorrect_parameter_type() { +// for mut sandbox in get_simpleguest_sandboxes(None) { +// let res = sandbox.call_guest_function_by_name( +// "Echo", +// ReturnType::Int, +// Some(vec![ +// ParameterValue::Int(2), // should be string +// ]), +// ); +// +// assert!(matches!( +// res.unwrap_err(), +// HyperlightError::GuestError( +// hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode::GuestFunctionParameterTypeMismatch, +// msg +// ) if msg == "Expected parameter type String for parameter index 0 of function Echo but got Int." +// )); +// } +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn incorrect_parameter_num() { +// for mut sandbox in get_simpleguest_sandboxes(None).into_iter() { +// let res = sandbox.call_guest_function_by_name( +// "Echo", +// ReturnType::Int, +// Some(vec![ +// ParameterValue::String("1".to_string()), +// ParameterValue::Int(2), +// ]), +// ); +// assert!(matches!( +// res.unwrap_err(), +// HyperlightError::GuestError( +// hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode::GuestFunctionIncorrecNoOfParameters, +// msg +// ) if msg == "Called function Echo with 2 parameters but it takes 1." +// )); +// } +// } +// +// #[test] +// fn max_memory_sandbox() { +// let mut cfg = SandboxConfiguration::default(); +// cfg.set_input_data_size(0x40000000); +// let a = UninitializedSandbox::new( +// GuestBinary::FilePath(simple_guest_as_string().unwrap()), +// Some(cfg), +// None, +// None, +// ); +// +// assert!(matches!( +// a.unwrap_err(), +// HyperlightError::MemoryRequestTooBig(..) +// )); +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn iostack_is_working() { +// for mut sandbox in get_simpleguest_sandboxes(None).into_iter() { +// let res = sandbox.call_guest_function_by_name( +// "ThisIsNotARealFunctionButTheNameIsImportant", +// ReturnType::Int, +// None, +// ); +// println!("{:?}", res); +// assert!(res.is_ok()); +// assert_eq!(res.unwrap(), ReturnValue::Int(99)); +// } +// } +// +// fn simple_test_helper() -> Result<()> { +// let messages = Arc::new(Mutex::new(Vec::new())); +// let messages_clone = messages.clone(); +// let writer = move |msg: String| { +// let len = msg.len(); +// let mut lock = messages_clone +// .try_lock() +// .map_err(|_| new_error!("Error locking")) +// .unwrap(); +// lock.push(msg); +// Ok(len as i32) +// }; +// +// let message = "hello"; +// let message2 = "world"; +// +// let writer_func = Arc::new(Mutex::new(writer)); +// for mut sandbox in get_simpleguest_sandboxes(Some(&writer_func)).into_iter() { +// let res = sandbox.call_guest_function_by_name( +// "PrintOutput", +// ReturnType::Int, +// Some(vec![ParameterValue::String(message.to_string())]), +// ); +// println!("res: {:?}", res); +// assert!(matches!(res, Ok(ReturnValue::Int(5)))); +// +// let res2 = sandbox.call_guest_function_by_name( +// "Echo", +// ReturnType::String, +// Some(vec![ParameterValue::String(message2.to_string())]), +// ); +// println!("res2: {:?}", res2); +// assert!(matches!(res2, Ok(ReturnValue::String(s)) if s == "world")); +// +// let buffer = vec![1u8, 2, 3, 4, 5, 6]; +// let res3 = sandbox.call_guest_function_by_name( +// "GetSizePrefixedBuffer", +// ReturnType::Int, +// Some(vec![ParameterValue::VecBytes(buffer.clone())]), +// ); +// println!("res3: {:?}", res3); +// assert!(matches!(res3, Ok(ReturnValue::VecBytes(v)) if v == buffer)); +// } +// +// let expected_calls = { +// if cfg!(all(target_os = "windows", inprocess)) { +// // windows debug build +// 5 +// } else if cfg!(inprocess) { +// // linux debug build +// 4 +// } else { +// // {windows,linux} release build +// 2 +// } +// }; +// +// assert_eq!( +// messages +// .try_lock() +// .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? +// .len(), +// expected_calls +// ); +// +// assert!(messages +// .try_lock() +// .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? +// .iter() +// .all(|msg| msg == message)); +// Ok(()) +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn simple_test() { +// simple_test_helper().unwrap(); +// } +// +// #[test] +// #[cfg(target_os = "linux")] +// fn simple_test_parallel() { +// let handles: Vec<_> = (0..50) +// .map(|_| { +// std::thread::spawn(|| { +// simple_test_helper().unwrap(); +// }) +// }) +// .collect(); +// +// for handle in handles { +// handle.join().unwrap(); +// } +// } +// +// #[test] +// #[serial] +// #[cfg(all(target_os = "windows", inprocess))] +// fn only_one_sandbox_instance_with_loadlib() { +// use hyperlight_host::SandboxRunOptions; +// use hyperlight_testing::simple_guest_exe_as_string; +// +// let _sandbox = UninitializedSandbox::new( +// GuestBinary::FilePath(simple_guest_exe_as_string().unwrap()), +// None, +// Some(SandboxRunOptions::RunInProcess(true)), +// None, +// ) +// .unwrap(); +// +// let err = UninitializedSandbox::new( +// GuestBinary::FilePath(simple_guest_exe_as_string().unwrap()), +// None, +// Some(SandboxRunOptions::RunInProcess(true)), +// None, +// ) +// .unwrap_err(); //should fail +// +// assert!( +// matches!(err, HyperlightError::Error(msg) if msg.starts_with("LoadedLib: Only one guest binary can be loaded at any single time")) +// ); +// } +// +// fn callback_test_helper() -> Result<()> { +// for mut sandbox in get_callbackguest_uninit_sandboxes(None).into_iter() { +// // create host function +// let vec = Arc::new(Mutex::new(vec![])); +// let vec_cloned = vec.clone(); +// let host_func1 = Arc::new(Mutex::new(move |msg: String| { +// let len = msg.len(); +// vec_cloned +// .try_lock() +// .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? +// .push(msg); +// Ok(len as i32) +// })); +// +// host_func1.register(&mut sandbox, "HostMethod1").unwrap(); +// +// // call guest function that calls host function +// let mut init_sandbox: MultiUseSandbox = sandbox.evolve(Noop::default())?; +// let msg = "Hello world"; +// init_sandbox.call_guest_function_by_name( +// "GuestMethod1", +// ReturnType::Int, +// Some(vec![ParameterValue::String(msg.to_string())]), +// )?; +// +// assert_eq!( +// vec.try_lock() +// .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? +// .len(), +// 1 +// ); +// assert_eq!( +// vec.try_lock() +// .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? +// .remove(0), +// format!("Hello from GuestFunction1, {}", msg) +// ); +// } +// Ok(()) +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn callback_test() { +// callback_test_helper().unwrap(); +// } +// +// #[test] +// #[cfg(target_os = "linux")] // windows can't run parallel with LoadLibrary +// fn callback_test_parallel() { +// let handles: Vec<_> = (0..100) +// .map(|_| { +// std::thread::spawn(|| { +// callback_test_helper().unwrap(); +// }) +// }) +// .collect(); +// +// for handle in handles { +// handle.join().unwrap(); +// } +// } +// +// #[test] +// #[cfg_attr(target_os = "windows", serial)] // using LoadLibrary requires serial tests +// fn host_function_error() -> Result<()> { +// // TODO: Remove the `.take(1)`, which makes this test only run in hypervisor. +// // This test does not work when running in process. This is because when running in-process, +// // when a host function returns an error, an infinite loop is created. +// for mut sandbox in get_callbackguest_uninit_sandboxes(None).into_iter().take(1) { +// // create host function +// let host_func1 = Arc::new(Mutex::new(|_msg: String| -> Result { +// Err(new_error!("Host function error!")) +// })); +// host_func1.register(&mut sandbox, "HostMethod1").unwrap(); +// +// // call guest function that calls host function +// let mut init_sandbox: MultiUseSandbox = sandbox.evolve(Noop::default())?; +// let msg = "Hello world"; +// let res = init_sandbox.call_guest_function_by_name( +// "GuestMethod1", +// ReturnType::Int, +// Some(vec![ParameterValue::String(msg.to_string())]), +// ); +// println!("res {:?}", res); +// assert!(matches!(res, Err(HyperlightError::Error(msg)) if msg == "Host function error!")); +// } +// Ok(()) +// } diff --git a/src/tests/rust_guests/callbackguest/src/main.rs b/src/tests/rust_guests/callbackguest/src/main.rs index c787599d5..e0f4f397a 100644 --- a/src/tests/rust_guests/callbackguest/src/main.rs +++ b/src/tests/rust_guests/callbackguest/src/main.rs @@ -31,12 +31,10 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel; use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; +use hyperlight_common::host_calling::{call_host_function, get_host_return_value, print}; use hyperlight_guest::error::{HyperlightGuestError, Result}; use hyperlight_guest::guest_function_definition::GuestFunctionDefinition; use hyperlight_guest::guest_function_register::register_function; -use hyperlight_guest::host_function_call::{ - call_host_function, get_host_return_value, print_output_as_guest_function, -}; use hyperlight_guest::logging::log_message; fn send_message_to_host_method( @@ -161,13 +159,25 @@ fn call_host_spin(_: &FunctionCall) -> Result> { Ok(get_flatbuffer_result(())) } +fn print_output(function_call: &FunctionCall) -> Result> { + if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() { + print(&message); + Ok(get_flatbuffer_result(())) + } else { + Err(HyperlightGuestError::new( + ErrorCode::GuestError, + "Wrong Parameters passed to print_output_as_guest_function".to_string(), + )) + } +} + #[no_mangle] pub extern "C" fn hyperlight_main() { let print_output_def = GuestFunctionDefinition::new( "PrintOutput".to_string(), Vec::from(&[ParameterType::String]), ReturnType::Int, - print_output_as_guest_function as usize, + print_output as usize, ); register_function(print_output_def); diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index 45dd558c7..222620ebe 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -40,12 +40,12 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel; use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; -use hyperlight_common::mem::PAGE_SIZE; +use hyperlight_common::host_calling::{call_host_function, get_host_return_value, print}; +use hyperlight_common::PAGE_SIZE; use hyperlight_guest::entrypoint::{abort_with_code, abort_with_code_and_message}; use hyperlight_guest::error::{HyperlightGuestError, Result}; use hyperlight_guest::guest_function_definition::GuestFunctionDefinition; use hyperlight_guest::guest_function_register::register_function; -use hyperlight_guest::host_function_call::{call_host_function, get_host_return_value}; use hyperlight_guest::memory::malloc; use hyperlight_guest::{logging, MIN_STACK_ADDRESS}; use log::{error, LevelFilter}; @@ -86,13 +86,8 @@ fn echo_float(function_call: &FunctionCall) -> Result> { } fn print_output(message: &str) -> Result> { - call_host_function( - "HostPrint", - Some(Vec::from(&[ParameterValue::String(message.to_string())])), - ReturnType::Int, - )?; - let result = get_host_return_value::()?; - Ok(get_flatbuffer_result(result)) + print(message); + Ok(get_flatbuffer_result(())) } fn simple_print_output(function_call: &FunctionCall) -> Result> { @@ -557,7 +552,7 @@ fn test_guest_panic(function_call: &FunctionCall) -> Result> { fn test_write_raw_ptr(function_call: &FunctionCall) -> Result> { if let ParameterValue::Long(offset) = function_call.parameters.clone().unwrap()[0].clone() { let min_stack_addr = unsafe { MIN_STACK_ADDRESS }; - let page_guard_start = min_stack_addr - PAGE_SIZE; + let page_guard_start = min_stack_addr - PAGE_SIZE as u64; let addr = { let abs = u64::try_from(offset.abs()) .map_err(|_| error!("Invalid offset")) @@ -1132,12 +1127,7 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { 1, ); - call_host_function( - "HostPrint", - Some(Vec::from(&[ParameterValue::String(message.to_string())])), - ReturnType::Int, - )?; - let result = get_host_return_value::()?; + print(message); let function_name = function_call.function_name.clone(); let param_len = function_call.parameters.clone().unwrap_or_default().len(); let call_type = function_call.function_call_type().clone(); @@ -1145,7 +1135,6 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { if function_name != "ThisIsNotARealFunctionButTheNameIsImportant" || param_len != 0 || call_type != FunctionCallType::Guest - || result != 100 { return Err(HyperlightGuestError::new( ErrorCode::GuestFunctionNotFound,