From ffa7d8ddcd636d2d6641936f7663bc3aa00b46db Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Thu, 18 Dec 2025 16:12:15 +0000 Subject: [PATCH 1/2] Which came first, the snapshot or the sandbox? This commit changes the Hyperlight API so that every sandbox is created from a snapshot. This is useful for several reasons; most immediately, in the same commit, note that it allows us to avoid precommitting to a size for the page table region, so we no longer need to estimate that regin's size. Signed-off-by: Simon Davies Co-authored-by: Lucy Menon <168595099+syntactically@users.noreply.github.com> --- .../src/arch/amd64/layout.rs | 18 + src/hyperlight_common/src/arch/amd64/vmem.rs | 2 +- src/hyperlight_common/src/layout.rs | 24 + src/hyperlight_common/src/lib.rs | 3 + src/hyperlight_guest_bin/src/paging.rs | 36 +- src/hyperlight_host/src/error.rs | 4 +- src/hyperlight_host/src/mem/exe.rs | 9 + src/hyperlight_host/src/mem/layout.rs | 319 ++++++------ src/hyperlight_host/src/mem/memory_region.rs | 74 ++- src/hyperlight_host/src/mem/mgr.rs | 477 ++++++------------ .../src/sandbox/initialized_multi_use.rs | 14 +- src/hyperlight_host/src/sandbox/outb.rs | 21 +- src/hyperlight_host/src/sandbox/snapshot.rs | 217 +++++++- .../src/sandbox/uninitialized.rs | 398 +++++++++++---- .../src/sandbox/uninitialized_evolve.rs | 44 +- src/hyperlight_host/src/testing/mod.rs | 26 - 16 files changed, 986 insertions(+), 700 deletions(-) create mode 100644 src/hyperlight_common/src/arch/amd64/layout.rs create mode 100644 src/hyperlight_common/src/layout.rs diff --git a/src/hyperlight_common/src/arch/amd64/layout.rs b/src/hyperlight_common/src/arch/amd64/layout.rs new file mode 100644 index 000000000..be4065732 --- /dev/null +++ b/src/hyperlight_common/src/arch/amd64/layout.rs @@ -0,0 +1,18 @@ +/* +Copyright 2025 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. + */ + +// Keep in mind that the minimum upper half GVA is 0xffff_8000_0000_0000 +pub const SNAPSHOT_PT_GVA: usize = 0xffff_ff00_0000_0000; diff --git a/src/hyperlight_common/src/arch/amd64/vmem.rs b/src/hyperlight_common/src/arch/amd64/vmem.rs index 683d59338..e292a0381 100644 --- a/src/hyperlight_common/src/arch/amd64/vmem.rs +++ b/src/hyperlight_common/src/arch/amd64/vmem.rs @@ -54,7 +54,7 @@ pub const PAGE_RW: u64 = 1 << 1; pub const PAGE_NX: u64 = 1 << 63; /// Mask to extract the physical address from a PTE (bits 51:12) /// This masks out the lower 12 flag bits AND the upper bits including NX (bit 63) -pub(crate) const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000; +pub const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000; const PAGE_USER_ACCESS_DISABLED: u64 = 0 << 2; // U/S bit not set - supervisor mode only (no code runs in user mode for now) const PAGE_DIRTY_CLEAR: u64 = 0 << 6; // D - dirty bit cleared (set by CPU when written) const PAGE_ACCESSED_CLEAR: u64 = 0 << 5; // A - accessed bit cleared (set by CPU when accessed) diff --git a/src/hyperlight_common/src/layout.rs b/src/hyperlight_common/src/layout.rs new file mode 100644 index 000000000..87d072715 --- /dev/null +++ b/src/hyperlight_common/src/layout.rs @@ -0,0 +1,24 @@ +/* +Copyright 2025 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. + */ + +// The constraint on the feature is temporary and will be removed when other arch i686 is added +#[cfg_attr(target_arch = "x86_64", path = "arch/amd64/layout.rs")] +#[cfg(feature = "init-paging")] +mod arch; + +// The constraint on the feature is temporary and will be removed when other arch i686 is added +#[cfg(feature = "init-paging")] +pub use arch::SNAPSHOT_PT_GVA; diff --git a/src/hyperlight_common/src/lib.rs b/src/hyperlight_common/src/lib.rs index ea7c8ab47..da9294c19 100644 --- a/src/hyperlight_common/src/lib.rs +++ b/src/hyperlight_common/src/lib.rs @@ -27,6 +27,9 @@ pub mod flatbuffer_wrappers; /// FlatBuffers-related utilities and (mostly) generated code #[allow(clippy::all, warnings)] mod flatbuffers; +// cbindgen:ignore +pub mod layout; + /// cbindgen:ignore pub mod mem; diff --git a/src/hyperlight_guest_bin/src/paging.rs b/src/hyperlight_guest_bin/src/paging.rs index 08614b0f8..09cb85cf3 100644 --- a/src/hyperlight_guest_bin/src/paging.rs +++ b/src/hyperlight_guest_bin/src/paging.rs @@ -38,7 +38,37 @@ pub fn ptov(x: u64) -> *mut u8 { // virtual address 0, and Rust raw pointer operations can't be // used to read/write from address 0. -struct GuestMappingOperations {} +// We get this out of CR3 the first time that we do any mapping +// operation. In the future, if snapshot/restore changes to be able to +// change the snapshot pt base, we will need to modify this. +static SNAPSHOT_PT_GPA: spin::Once = spin::Once::new(); + +struct GuestMappingOperations { + snapshot_pt_base_gpa: u64, + snapshot_pt_base_gva: u64, +} +impl GuestMappingOperations { + fn new() -> Self { + Self { + snapshot_pt_base_gpa: *SNAPSHOT_PT_GPA.call_once(|| { + let snapshot_pt_base_gpa: u64; + unsafe { + asm!("mov {}, cr3", out(reg) snapshot_pt_base_gpa); + }; + snapshot_pt_base_gpa + }), + snapshot_pt_base_gva: hyperlight_common::layout::SNAPSHOT_PT_GVA as u64, + } + } + fn phys_to_virt(&self, addr: u64) -> u64 { + if addr >= self.snapshot_pt_base_gpa { + self.snapshot_pt_base_gva + (addr - self.snapshot_pt_base_gpa) + } else { + // Assume for now that any of our own PTs are identity mapped. + addr + } + } +} impl hyperlight_common::vmem::TableOps for GuestMappingOperations { type TableAddr = u64; unsafe fn alloc_table(&self) -> u64 { @@ -50,6 +80,7 @@ impl hyperlight_common::vmem::TableOps for GuestMappingOperations { addr + offset } unsafe fn read_entry(&self, addr: u64) -> u64 { + let addr = self.phys_to_virt(addr); let ret: u64; unsafe { asm!("mov {}, qword ptr [{}]", out(reg) ret, in(reg) addr); @@ -57,6 +88,7 @@ impl hyperlight_common::vmem::TableOps for GuestMappingOperations { ret } unsafe fn write_entry(&self, addr: u64, entry: u64) { + let addr = self.phys_to_virt(addr); unsafe { asm!("mov qword ptr [{}], {}", in(reg) addr, in(reg) entry); } @@ -90,7 +122,7 @@ pub unsafe fn map_region(phys_base: u64, virt_base: *mut u8, len: u64) { use hyperlight_common::vmem; unsafe { vmem::map( - &GuestMappingOperations {}, + &GuestMappingOperations::new(), vmem::Mapping { phys_base, virt_base: virt_base as u64, diff --git a/src/hyperlight_host/src/error.rs b/src/hyperlight_host/src/error.rs index 97b6ccc19..7da9a6eea 100644 --- a/src/hyperlight_host/src/error.rs +++ b/src/hyperlight_host/src/error.rs @@ -32,7 +32,7 @@ use thiserror::Error; #[cfg(target_os = "windows")] use crate::hypervisor::wrappers::HandleWrapper; -use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; +use crate::mem::memory_region::MemoryRegionFlags; use crate::mem::ptr::RawPtr; /// The error type for Hyperlight operations @@ -150,7 +150,7 @@ pub enum HyperlightError { /// Memory region size mismatch #[error("Memory region size mismatch: host size {0:?}, guest size {1:?} region {2:?}")] - MemoryRegionSizeMismatch(usize, usize, MemoryRegion), + MemoryRegionSizeMismatch(usize, usize, String), /// The memory request exceeds the maximum size allowed #[error("Memory requested {0} exceeds maximum size allowed {1}")] diff --git a/src/hyperlight_host/src/mem/exe.rs b/src/hyperlight_host/src/mem/exe.rs index ae204f714..95a21ed31 100644 --- a/src/hyperlight_host/src/mem/exe.rs +++ b/src/hyperlight_host/src/mem/exe.rs @@ -64,6 +64,15 @@ pub(crate) struct LoadInfo { pub(crate) info: Arc, } +impl LoadInfo { + pub(crate) fn dummy() -> Self { + LoadInfo { + #[cfg(feature = "mem_profile")] + info: Arc::new(DummyUnwindInfo {}), + } + } +} + impl ExeInfo { pub fn from_file(path: &str) -> Result { let mut file = File::open(path)?; diff --git a/src/hyperlight_host/src/mem/layout.rs b/src/hyperlight_host/src/mem/layout.rs index 0c6a9d892..00b1e58d3 100644 --- a/src/hyperlight_host/src/mem/layout.rs +++ b/src/hyperlight_host/src/mem/layout.rs @@ -12,7 +12,64 @@ 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. -*/ + */ +//! This module describes the virtual and physical addresses of a +//! number of special regions in the hyperlight VM, although we hope +//! to reduce the number of these over time. +//! +//! A snapshot freshly created from an empty VM will result in roughly +//! the following physical layout: +//! +//! +-------------------------------------------+ +//! | Guest Page Tables | +//! +-------------------------------------------+ +//! | Init Data | (GuestBlob size) +//! +-------------------------------------------+ +//! | Guest (User) Stack | +//! +-------------------------------------------+ +//! | Guard Page (4KiB) | +//! +-------------------------------------------+ +//! | Guest Heap | +//! +-------------------------------------------+ +//! | Output Data | +//! +-------------------------------------------+ +//! | Input Data | +//! +-------------------------------------------+ +//! | Host Function Definitions | +//! +-------------------------------------------+ +//! | PEB Struct | (HyperlightPEB size) +//! +-------------------------------------------+ +//! | Guest Code | +//! +-------------------------------------------+ 0x1_000 +//! | NULL guard page | +//! +-------------------------------------------+ 0x0_000 +//! +//! Everything except for the guest page tables is currently +//! identity-mapped; the guest page tables themselves are mapped at +//! [`hyperlight_common::layout::SNAPSHOT_PT_GVA`] = +//! 0xffff_0000_0000_0000. +//! +//! - `InitData` - some extra data that can be loaded onto the sandbox during +//! initialization. +//! +//! - `HostDefinitions` - the length of this is the `HostFunctionDefinitionSize` +//! field 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 + use std::fmt::Debug; use std::mem::{offset_of, size_of}; @@ -26,65 +83,14 @@ use super::memory_region::MemoryRegionType::{ Code, GuardPage, Heap, HostFunctionDefinitions, InitData, InputData, OutputData, Peb, Stack, }; use super::memory_region::{ - DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegion, MemoryRegionFlags, MemoryRegionVecBuilder, + DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegion, MemoryRegion_, MemoryRegionFlags, MemoryRegionKind, + MemoryRegionVecBuilder, }; use super::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, SharedMemory}; use crate::error::HyperlightError::{GuestOffsetIsInvalid, MemoryRequestTooBig}; use crate::sandbox::SandboxConfiguration; use crate::{Result, new_error}; -// +-------------------------------------------+ -// | Init Data | (GuestBlob size) -// +-------------------------------------------+ -// | Guest (User) Stack | -// +-------------------------------------------+ -// | Guard Page (4KiB) | -// +-------------------------------------------+ -// | Guest Heap | -// +-------------------------------------------+ -// | Output Data | -// +-------------------------------------------+ -// | Input Data | -// +-------------------------------------------+ -// | Host Function Definitions | -// +-------------------------------------------+ -// | PEB Struct | (HyperlightPEB size) -// +-------------------------------------------+ -// | Guest Code | -// +-------------------------------------------+ -// | PT | -// +-------------------------------------------+ 0x3_000 -// | PD | -// +-------------------------------------------+ 0x2_000 -// | PDPT | -// +-------------------------------------------+ 0x1_000 -// | PML4 | -// +-------------------------------------------+ 0x0_000 - -/// - `InitData` - some extra data that can be loaded onto the sandbox during -/// initialization. -/// -/// - `HostDefinitions` - the length of this is the `HostFunctionDefinitionSize` -/// field 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 -// The amount of memory that can be mapped per page table -#[cfg(feature = "init-paging")] -const AMOUNT_OF_MEMORY_PER_PT: usize = 0x200_000; - #[derive(Copy, Clone)] pub(crate) struct SandboxMemoryLayout { pub(super) sandbox_memory_config: SandboxConfiguration, @@ -115,12 +121,12 @@ pub(crate) struct SandboxMemoryLayout { guard_page_offset: usize, guest_user_stack_buffer_offset: usize, // the lowest address of the user stack init_data_offset: usize, + pt_offset: usize, + pt_size: Option, // 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, pub(crate) init_data_permissions: Option, @@ -202,10 +208,8 @@ impl Debug for SandboxMemoryLayout { "Init Data Offset", &format_args!("{:#x}", self.init_data_offset), ) - .field( - "Page Table Size", - &format_args!("{:#x}", self.total_page_table_size), - ) + .field("PT Offset", &format_args!("{:#x}", self.pt_offset)) + .field("PT Size", &format_args!("{:#x}", self.pt_size.unwrap_or(0))) .field( "Guest Code Offset", &format_args!("{:#x}", self.guest_code_offset), @@ -215,16 +219,13 @@ impl Debug for SandboxMemoryLayout { } 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 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 = 0x0; + pub(crate) const BASE_ADDRESS: usize = 0x1000; // the offset into a sandbox's input/output buffer where the stack starts const STACK_POINTER_SIZE_BYTES: u64 = 8; @@ -232,7 +233,7 @@ impl SandboxMemoryLayout { /// 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( + pub(crate) fn new( cfg: SandboxConfiguration, code_size: usize, stack_size: usize, @@ -240,13 +241,9 @@ impl SandboxMemoryLayout { init_data_size: usize, init_data_permissions: Option, ) -> Result { - #[cfg(feature = "init-paging")] - let base = Self::get_total_page_table_size(cfg, code_size, stack_size, heap_size); - #[cfg(not(feature = "init-paging"))] - let base = Self::BASE_ADDRESS; - let guest_code_offset = base; + let guest_code_offset = 0; // The following offsets are to the fields of the PEB struct itself! - let peb_offset = base + round_up_to(code_size, PAGE_SIZE_USIZE); + let peb_offset = 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 = @@ -286,6 +283,7 @@ impl SandboxMemoryLayout { // 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 init_data_offset = guest_user_stack_buffer_offset + stack_size_rounded; + let pt_offset = round_up_to(init_data_offset + init_data_size, PAGE_SIZE_USIZE); Ok(Self { peb_offset, @@ -308,11 +306,12 @@ impl SandboxMemoryLayout { guest_user_stack_buffer_offset, peb_address, guard_page_offset, - total_page_table_size: base, guest_code_offset, init_data_offset, init_data_size, init_data_permissions, + pt_offset, + pt_size: None, }) } @@ -419,7 +418,8 @@ impl SandboxMemoryLayout { /// 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 { + #[cfg(feature = "init-paging")] + pub(crate) fn get_top_of_user_stack_offset(&self) -> usize { self.guest_user_stack_buffer_offset } @@ -435,13 +435,13 @@ impl SandboxMemoryLayout { /// layout. #[instrument(skip_all, parent = Span::current(), level= "Trace")] fn get_unaligned_memory_size(&self) -> usize { - self.init_data_offset + self.init_data_size + self.pt_offset + self.pt_size.unwrap_or(0) } /// 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 { + pub(crate) fn get_guest_code_offset(&self) -> usize { self.guest_code_offset } @@ -451,67 +451,10 @@ impl SandboxMemoryLayout { Self::BASE_ADDRESS + self.guest_code_offset } - #[cfg(test)] - #[cfg(feature = "init-paging")] - /// 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 up to 512 PTs to map the memory if the memory size is 1GB) - // - // We can calculate the amount of memory needed for the PTs by calculating how much memory is needed for the sandbox configuration in total, - // 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). - // 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. - // TODO: This over-counts on small sandboxes (because not all 512 - // PTs may be required), under-counts on sandboxes with more than - // 1GiB memory - which are not currently supported as we only support MAX_MEMORY_SIZE, and would get unreasonably complicated if we - // needed to support hugepages. - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - #[cfg(feature = "init-paging")] - 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_function_definition_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(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.div_ceil(AMOUNT_OF_MEMORY_PER_PT) + 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 { + pub(crate) fn get_memory_size(&self) -> Result { let total_memory = self.get_unaligned_memory_size(); // Size should be a multiple of page size. @@ -529,29 +472,38 @@ impl SandboxMemoryLayout { } } - /// Returns the memory regions associated with this memory layout, - /// suitable for passing to a hypervisor for mapping into memory + /// Get the offset into the snapshot region of the page tables + #[instrument(skip_all, parent = Span::current(), level= "Trace")] + pub(crate) fn get_pt_offset(&self) -> usize { + self.pt_offset + } + + /// Get the offset into the snapshot region of the page tables + #[instrument(skip_all, parent = Span::current(), level= "Trace")] + #[cfg(feature = "init-paging")] + pub(crate) fn set_pt_size(&mut self, size: usize) { + self.pt_size = Some(size); + } + + /// Get the offset into the snapshot region of the guest user stack + /// pointer, to be used when entering the guest + #[instrument(skip_all, parent = Span::current(), level= "Trace")] + #[cfg(feature = "init-paging")] + pub(crate) fn get_rsp_offset(&self) -> usize { + self.get_top_of_user_stack_offset() + self.stack_size - 0x28 + } + pub fn get_memory_regions(&self, shared_mem: &GuestSharedMemory) -> Result> { - let mut builder = MemoryRegionVecBuilder::new(Self::BASE_ADDRESS, shared_mem.base_addr()); + self.get_memory_regions_(shared_mem.base_addr()) + } - cfg_if::cfg_if! { - if #[cfg(feature = "init-paging")] { - // 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 - )); - } - } - } + /// Returns the memory regions associated with this memory layout, + /// suitable for passing to a hypervisor for mapping into memory + pub(crate) fn get_memory_regions_( + &self, + host_base: K::HostBaseType, + ) -> Result>> { + let mut builder = MemoryRegionVecBuilder::new(Self::BASE_ADDRESS, host_base); // code let peb_offset = builder.push_page_aligned( @@ -700,7 +652,7 @@ impl SandboxMemoryLayout { )); } - let final_offset = if self.init_data_size > 0 { + let after_init_offset = if self.init_data_size > 0 { let mem_flags = self .init_data_permissions .unwrap_or(DEFAULT_GUEST_BLOB_MEM_FLAGS); @@ -709,6 +661,32 @@ impl SandboxMemoryLayout { init_data_offset }; + #[cfg(feature = "init-paging")] + let final_offset = { + let expected_pt_offset = TryInto::::try_into(self.pt_offset)?; + + if after_init_offset != expected_pt_offset { + return Err(new_error!( + "Page table offset does not match expected: {}, actual: {}", + expected_init_data_offset, + init_data_offset + )); + } + + if let Some(pt_size) = self.pt_size { + builder.push_page_aligned( + pt_size, + MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, + PageTables, + ) + } else { + after_init_offset + } + }; + + #[cfg(not(feature = "init-paging"))] + let final_offset = after_init_offset; + let expected_final_offset = TryInto::::try_into(self.get_memory_size()?)?; if final_offset != expected_final_offset { @@ -723,12 +701,9 @@ impl SandboxMemoryLayout { } #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn write_init_data( - &self, - shared_mem: &mut ExclusiveSharedMemory, - bytes: &[u8], - ) -> Result<()> { - shared_mem.copy_from_slice(bytes, self.init_data_offset)?; + pub(crate) fn write_init_data(&self, out: &mut [u8], bytes: &[u8]) -> Result<()> { + out[self.init_data_offset..self.init_data_offset + self.init_data_size] + .copy_from_slice(bytes); Ok(()) } @@ -742,7 +717,8 @@ impl SandboxMemoryLayout { &self, shared_mem: &mut ExclusiveSharedMemory, guest_offset: usize, - size: usize, + //TODO: Unused remove + _size: usize, ) -> Result<()> { macro_rules! get_address { ($something:ident) => { @@ -813,24 +789,17 @@ impl SandboxMemoryLayout { // 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. - - let bottom = guest_offset + size; - let min_user_stack_address = bottom - self.stack_size; - - // Top of user stack + // Bottom of user stack shared_mem.write_u64( self.get_min_guest_stack_address_offset(), - min_user_stack_address.try_into()?, + get_address!(guest_user_stack_buffer_offset), )?; // Start of user stack - let start_of_user_stack: u64 = (min_user_stack_address + self.stack_size).try_into()?; + let start_of_user_stack: u64 = + get_address!(guest_user_stack_buffer_offset) + self.stack_size as u64; shared_mem.write_u64(self.get_user_stack_pointer_offset(), start_of_user_stack)?; @@ -885,10 +854,6 @@ mod tests { let cfg = layout.sandbox_memory_config; let mut expected_size = 0; // in order of layout - #[cfg(feature = "init-paging")] - { - expected_size += layout.get_page_table_size(); - } expected_size += layout.code_size; expected_size += round_up_to(size_of::(), PAGE_SIZE_USIZE); diff --git a/src/hyperlight_host/src/mem/memory_region.rs b/src/hyperlight_host/src/mem/memory_region.rs index c7ae6b629..4d03d83c4 100644 --- a/src/hyperlight_host/src/mem/memory_region.rs +++ b/src/hyperlight_host/src/mem/memory_region.rs @@ -137,28 +137,77 @@ pub enum MemoryRegionType { Stack, } +/// A trait that distinguishes between different kinds of memory region representations. +/// +/// This trait is used to parameterize [`MemoryRegion_`] +pub(crate) trait MemoryRegionKind { + /// The type used to represent host memory addresses. + /// + type HostBaseType: Copy; + + /// Computes an address by adding a size to a base address. + /// + /// # Arguments + /// * `base` - The starting address + /// * `size` - The size in bytes to add + /// + /// # Returns + /// The computed end address (`base + size` for host-guest regions, + /// `()` for guest-only regions). + fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType; +} + +/// Type for memory regions that track both host and guest addresses. +/// +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +pub(crate) struct HostGuestMemoryRegion {} + +impl MemoryRegionKind for HostGuestMemoryRegion { + type HostBaseType = usize; + + fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType { + base + size + } +} + +/// Type for memory regions that only track guest addresses. +/// +#[cfg_attr(not(feature = "init-paging"), allow(dead_code))] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +pub(crate) struct GuestMemoryRegion {} + +impl MemoryRegionKind for GuestMemoryRegion { + type HostBaseType = (); + + fn add(_base: Self::HostBaseType, _size: usize) -> Self::HostBaseType {} +} + /// represents a single memory region inside the guest. All memory within a region has /// the same memory permissions #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct MemoryRegion { +pub(crate) struct MemoryRegion_ { /// the range of guest memory addresses pub guest_region: Range, /// the range of host memory addresses - pub host_region: Range, + /// + /// Note that Range<()> = () x () = (). + pub host_region: Range, /// memory access flags for the given region pub flags: MemoryRegionFlags, /// the type of memory region pub region_type: MemoryRegionType, } -pub(crate) struct MemoryRegionVecBuilder { +pub(crate) type MemoryRegion = MemoryRegion_; + +pub(crate) struct MemoryRegionVecBuilder { guest_base_phys_addr: usize, - host_base_virt_addr: usize, - regions: Vec, + host_base_virt_addr: K::HostBaseType, + regions: Vec>, } -impl MemoryRegionVecBuilder { - pub(crate) fn new(guest_base_phys_addr: usize, host_base_virt_addr: usize) -> Self { +impl MemoryRegionVecBuilder { + pub(crate) fn new(guest_base_phys_addr: usize, host_base_virt_addr: K::HostBaseType) -> Self { Self { guest_base_phys_addr, host_base_virt_addr, @@ -174,8 +223,8 @@ impl MemoryRegionVecBuilder { ) -> 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 { + let host_end = ::add(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, @@ -187,9 +236,10 @@ impl MemoryRegionVecBuilder { #[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 { + let host_end = ::add(last_region.host_region.end, size); + 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, + host_region: last_region.host_region.end..host_end, flags, region_type, }; @@ -214,7 +264,7 @@ impl MemoryRegionVecBuilder { /// 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 { + pub(crate) fn build(self) -> Vec> { self.regions } } diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index a61a57ff2..7c9b96f83 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -use std::cmp::Ordering; - use flatbuffers::FlatBufferBuilder; use hyperlight_common::flatbuffer_wrappers::function_call::{ FunctionCall, validate_guest_function_call_buffer, @@ -24,22 +22,17 @@ use hyperlight_common::flatbuffer_wrappers::function_types::FunctionCallResult; use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData; use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails; #[cfg(feature = "init-paging")] -use hyperlight_common::vmem::{ - self, BasicMapping, Mapping, MappingKind, PAGE_TABLE_SIZE, PageTableEntry, PhysAddr, -}; +use hyperlight_common::vmem; +#[cfg(feature = "init-paging")] +use hyperlight_common::vmem::{PAGE_TABLE_SIZE, PageTableEntry, PhysAddr}; use tracing::{Span, instrument}; -use super::exe::ExeInfo; use super::layout::SandboxMemoryLayout; use super::memory_region::MemoryRegion; -#[cfg(feature = "init-paging")] -use super::memory_region::MemoryRegionFlags; use super::ptr::{GuestPtr, RawPtr}; use super::ptr_offset::Offset; use super::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemory, SharedMemory}; -use crate::sandbox::SandboxConfiguration; use crate::sandbox::snapshot::Snapshot; -use crate::sandbox::uninitialized::GuestBlob; use crate::{Result, log_then_return, new_error}; /// The size of stack guard cookies @@ -56,7 +49,7 @@ pub(crate) struct SandboxMemoryManager { /// Pointer to where to load memory from pub(crate) load_addr: RawPtr, /// Offset for the execution entrypoint from `load_addr` - pub(crate) entrypoint_offset: Offset, + pub(crate) entrypoint_offset: Option, /// How many memory regions were mapped after sandbox creation pub(crate) mapped_rgns: u64, /// Stack cookie for stack guard verification @@ -66,8 +59,9 @@ pub(crate) struct SandboxMemoryManager { } #[cfg(feature = "init-paging")] -struct GuestPageTableBuffer { +pub(crate) struct GuestPageTableBuffer { buffer: std::cell::RefCell>, + phys_base: usize, } #[cfg(feature = "init-paging")] @@ -79,17 +73,19 @@ impl vmem::TableOps for GuestPageTableBuffer { let table_index = b.len() / PAGE_TABLE_SIZE; let new_len = b.len() + PAGE_TABLE_SIZE; b.resize(new_len, 0); - (table_index, 0) + (self.phys_base / PAGE_TABLE_SIZE + table_index, 0) } fn entry_addr(addr: (usize, usize), offset: u64) -> (usize, usize) { + // Convert to physical address, add offset, convert back let phys = Self::to_phys(addr) + offset; Self::from_phys(phys) } unsafe fn read_entry(&self, addr: (usize, usize)) -> PageTableEntry { let b = self.buffer.borrow(); - let byte_offset = addr.0 * PAGE_TABLE_SIZE + addr.1 * 8; + let byte_offset = + (addr.0 - self.phys_base / PAGE_TABLE_SIZE) * PAGE_TABLE_SIZE + addr.1 * 8; b.get(byte_offset..byte_offset + 8) .and_then(|s| <[u8; 8]>::try_from(s).ok()) .map(u64::from_ne_bytes) @@ -98,7 +94,8 @@ impl vmem::TableOps for GuestPageTableBuffer { unsafe fn write_entry(&self, addr: (usize, usize), x: PageTableEntry) { let mut b = self.buffer.borrow_mut(); - let byte_offset = addr.0 * PAGE_TABLE_SIZE + addr.1 * 8; + let byte_offset = + (addr.0 - self.phys_base / PAGE_TABLE_SIZE) * PAGE_TABLE_SIZE + addr.1 * 8; if let Some(slice) = b.get_mut(byte_offset..byte_offset + 8) { slice.copy_from_slice(&x.to_ne_bytes()); } @@ -116,18 +113,23 @@ impl vmem::TableOps for GuestPageTableBuffer { } fn root_table(&self) -> (usize, usize) { - (0, 0) + (self.phys_base / PAGE_TABLE_SIZE, 0) } } #[cfg(feature = "init-paging")] impl GuestPageTableBuffer { - fn new() -> Self { + pub(crate) fn new(phys_base: usize) -> Self { GuestPageTableBuffer { buffer: std::cell::RefCell::new(vec![0u8; PAGE_TABLE_SIZE]), + phys_base, } } + pub(crate) fn size(&self) -> usize { + self.buffer.borrow().len() * PAGE_TABLE_SIZE + } + pub(crate) fn into_bytes(self) -> Box<[u8]> { self.buffer.into_inner().into_boxed_slice() } @@ -143,7 +145,7 @@ where layout: SandboxMemoryLayout, shared_mem: S, load_addr: RawPtr, - entrypoint_offset: Offset, + entrypoint_offset: Option, stack_cookie: [u8; STACK_COOKIE_LEN], ) -> Self { Self { @@ -174,61 +176,19 @@ where &mut self.shared_mem } - /// Set up the guest page tables in the given `SharedMemory` parameter - /// `shared_mem` - // TODO: This should perhaps happen earlier and use an - // ExclusiveSharedMemory from the beginning. - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - #[cfg(feature = "init-paging")] - pub(crate) fn set_up_shared_memory(&mut self, regions: &mut [MemoryRegion]) -> Result { - let rsp: u64 = self.layout.get_top_of_user_stack_offset() as u64 - + SandboxMemoryLayout::BASE_ADDRESS as u64 - + self.layout.stack_size as u64 - // TODO: subtracting 0x28 was a requirement for MSVC. It should no longer be - // necessary now, but, for some reason, without this, the `multiple_parameters` - // test from `sandbox_host_tests` fails. We should investigate this further. - // See issue #498 for more details. - - 0x28; - - self.shared_mem.with_exclusivity(|shared_mem| { - let buffer = GuestPageTableBuffer::new(); - for region in regions.iter() { - let readable = region.flags.contains(MemoryRegionFlags::READ); - let writable = region.flags.contains(MemoryRegionFlags::WRITE) - // Temporary hack: the stack guard page is - // currently checked for in the host, rather than - // the guest, so we need to mark it writable in - // the Stage 1 translation so that the fault - // exception on a write is taken to the - // hypervisor, rather than the guest kernel - || region.flags.contains(MemoryRegionFlags::STACK_GUARD); - let executable = region.flags.contains(MemoryRegionFlags::EXECUTE); - let mapping = Mapping { - phys_base: region.guest_region.start as u64, - virt_base: region.guest_region.start as u64, - len: region.guest_region.len() as u64, - kind: MappingKind::BasicMapping(BasicMapping { - readable, - writable, - executable, - }), - }; - unsafe { vmem::map(&buffer, mapping) }; - } - shared_mem.copy_from_slice(&buffer.into_bytes(), SandboxMemoryLayout::PML4_OFFSET)?; - Ok::<(), crate::HyperlightError>(()) - })??; - - Ok(rsp) - } - /// Create a snapshot with the given mapped regions pub(crate) fn snapshot( &mut self, sandbox_id: u64, mapped_regions: Vec, ) -> Result { - Snapshot::new(&mut self.shared_mem, sandbox_id, mapped_regions) + Snapshot::new( + &mut self.shared_mem, + sandbox_id, + self.layout, + crate::mem::exe::LoadInfo::dummy(), + mapped_regions, + ) } /// This function restores a memory snapshot from a given snapshot. @@ -239,61 +199,20 @@ where } 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. - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn load_guest_binary_into_memory( - cfg: SandboxConfiguration, - exe_info: ExeInfo, - guest_blob: Option<&GuestBlob>, - ) -> Result<(Self, super::exe::LoadInfo)> { - let guest_blob_size = guest_blob.map(|b| b.data.len()).unwrap_or(0); - let guest_blob_mem_flags = guest_blob.map(|b| b.permissions); - - let layout = SandboxMemoryLayout::new( - cfg, - exe_info.loaded_size(), - usize::try_from(cfg.get_stack_size())?, - usize::try_from(cfg.get_heap_size())?, - guest_blob_size, - guest_blob_mem_flags, - )?; - let mut shared_mem = ExclusiveSharedMemory::new(layout.get_memory_size()?)?; - + pub(crate) fn from_snapshot(s: &Snapshot) -> Result { + let layout = *s.layout(); + let mut shared_mem = ExclusiveSharedMemory::new(s.mem_size())?; + shared_mem.copy_from_slice(s.memory(), 0)?; let load_addr: RawPtr = RawPtr::try_from(layout.get_guest_code_address())?; - - let entrypoint_offset = exe_info.entrypoint(); - - // The load method returns a LoadInfo which can also be a different type once the - // `mem_profile` feature is enabled. - #[allow(clippy::let_unit_value)] - let load_info = exe_info.load( - load_addr.clone().try_into()?, - &mut shared_mem.as_mut_slice()[layout.get_guest_code_offset()..], - )?; - let stack_cookie = rand::random::<[u8; STACK_COOKIE_LEN]>(); - let stack_offset = layout.get_top_of_user_stack_offset(); - shared_mem.copy_from_slice(&stack_cookie, stack_offset)?; - - Ok(( - Self::new( - layout, - shared_mem, - load_addr, - entrypoint_offset, - stack_cookie, - ), - load_info, + let entrypoint_gva = s.preinitialise(); + let entrypoint_offset = entrypoint_gva.map(|x| (x - u64::from(&load_addr)).into()); + Ok(Self::new( + layout, + shared_mem, + load_addr, + entrypoint_offset, + stack_cookie, )) } @@ -344,14 +263,6 @@ impl SandboxMemoryManager { ) } - /// Write init data - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn write_init_data(&mut self, user_memory: &[u8]) -> Result<()> { - self.layout - .write_init_data(&mut self.shared_mem, user_memory)?; - Ok(()) - } - /// Wraps ExclusiveSharedMemory::build pub fn build( self, @@ -398,11 +309,7 @@ impl SandboxMemoryManager { /// of why it isn't. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn check_stack_guard(&self) -> Result { - let expected = self.stack_cookie; - let offset = self.layout.get_top_of_user_stack_offset(); - let actual: [u8; STACK_COOKIE_LEN] = self.shared_mem.read(offset)?; - let cmp_res = expected.iter().cmp(actual.iter()); - Ok(cmp_res == Ordering::Equal) + Ok(true) } /// Get the address of the dispatch function in memory @@ -502,239 +409,155 @@ impl SandboxMemoryManager { } } +// ...existing code... + #[cfg(test)] #[cfg(all(feature = "init-paging", target_arch = "x86_64"))] mod tests { - use hyperlight_common::vmem::arch::{PAGE_NX, PAGE_PRESENT, PAGE_RW}; + use hyperlight_common::vmem::PAGE_TABLE_SIZE; + use hyperlight_common::vmem::arch::{PAGE_NX, PAGE_PRESENT, PAGE_RW, PTE_ADDR_MASK}; use hyperlight_testing::sandbox_sizes::{LARGE_HEAP_SIZE, MEDIUM_HEAP_SIZE, SMALL_HEAP_SIZE}; use hyperlight_testing::simple_guest_as_string; - use super::*; use crate::GuestBinary; - use crate::mem::memory_region::MemoryRegionType; - use crate::mem::shared_mem::GuestSharedMemory; + use crate::mem::memory_region::MemoryRegionFlags; use crate::sandbox::SandboxConfiguration; - use crate::sandbox::uninitialized::UninitializedSandbox; - - pub(crate) const PML4_OFFSET: usize = 0x0000; - pub(super) const PDPT_OFFSET: usize = 0x1000; - pub(super) const PD_OFFSET: usize = 0x2000; - pub(super) const PT_OFFSET: usize = 0x3000; - pub(super) const PD_GUEST_ADDRESS: usize = SandboxMemoryLayout::BASE_ADDRESS + PD_OFFSET; - pub(super) const PDPT_GUEST_ADDRESS: usize = SandboxMemoryLayout::BASE_ADDRESS + PDPT_OFFSET; - pub(super) const PT_GUEST_ADDRESS: usize = SandboxMemoryLayout::BASE_ADDRESS + PT_OFFSET; - - /// Helper to create a sandbox with page tables set up and return the manager - fn create_sandbox_with_page_tables( - config: Option, - ) -> Result> { - let path = simple_guest_as_string().expect("failed to get simple guest path"); - let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(path), config) - .expect("failed to create sandbox"); - - // Build the shared memory to get GuestSharedMemory - let (_host_mem, guest_mem) = sandbox.mgr.shared_mem.build(); - let mut mgr = SandboxMemoryManager { - shared_mem: guest_mem, - layout: sandbox.mgr.layout, - load_addr: sandbox.mgr.load_addr, - entrypoint_offset: sandbox.mgr.entrypoint_offset, - mapped_rgns: sandbox.mgr.mapped_rgns, - stack_cookie: sandbox.mgr.stack_cookie, - abort_buffer: sandbox.mgr.abort_buffer, - }; - - // Get regions and set up page tables - let mut regions = mgr.layout.get_memory_regions(&mgr.shared_mem)?; - // set_up_shared_memory builds the page tables in shared memory - mgr.set_up_shared_memory(&mut regions)?; - - Ok(mgr) - } - - /// Verify a range of pages all have the same expected flags - fn verify_page_range( - excl_mem: &mut ExclusiveSharedMemory, - start_addr: usize, - end_addr: usize, - expected_flags: u64, - region_name: &str, - ) -> Result<()> { - let mut addr = start_addr; - - while addr < end_addr { - let p = addr >> 21; - let i = (addr >> 12) & 0x1ff; - let pte_idx = p * 512 + i; - let offset = PT_OFFSET + (pte_idx * 8); - - let pte_val = excl_mem.read_u64(offset)?; - let expected_pte = (addr as u64) | expected_flags; - - if pte_val != expected_pte { - return Err(new_error!( - "{} region: addr 0x{:x}: expected PTE 0x{:x}, got 0x{:x}", - region_name, - addr, - expected_pte, - pte_val - )); - } - - addr += 0x1000; + use crate::sandbox::snapshot::Snapshot; + + /// Convert MemoryRegionFlags to expected PTE flags. + fn expected_pte_flags(flags: MemoryRegionFlags) -> u64 { + let mut pte = PAGE_PRESENT; + + // Writable if WRITE or STACK_GUARD (stack guard needs to be writable + // so faults go to hypervisor, not guest kernel) + if flags.contains(MemoryRegionFlags::WRITE) + || flags.contains(MemoryRegionFlags::STACK_GUARD) + { + pte |= PAGE_RW; } - Ok(()) - } - - /// Get expected flags for a memory region type - /// we dont set User RW flag since (at present) we do not run code in user mode. - fn get_expected_flags(region: &MemoryRegion) -> u64 { - match region.region_type { - MemoryRegionType::Code => PAGE_PRESENT | PAGE_RW, - MemoryRegionType::Stack => PAGE_PRESENT | PAGE_RW | PAGE_NX, - #[cfg(feature = "executable_heap")] - MemoryRegionType::Heap => PAGE_PRESENT | PAGE_RW, - #[cfg(not(feature = "executable_heap"))] - MemoryRegionType::Heap => PAGE_PRESENT | PAGE_RW | PAGE_NX, - MemoryRegionType::GuardPage => PAGE_PRESENT | PAGE_RW | 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, - MemoryRegionType::HostFunctionDefinitions => PAGE_PRESENT | PAGE_NX, - MemoryRegionType::PageTables => PAGE_PRESENT | PAGE_RW | PAGE_NX, - MemoryRegionType::InitData => translate_flags(region.flags), + // NX unless EXECUTE is set + if !flags.contains(MemoryRegionFlags::EXECUTE) { + pte |= PAGE_NX; } - } - - fn translate_flags(flags: MemoryRegionFlags) -> u64 { - let mut page_flags = 0; - page_flags |= PAGE_PRESENT | PAGE_RW; // Mark page as present and writeable + pte + } - if !flags.contains(MemoryRegionFlags::EXECUTE) { - page_flags |= PAGE_NX; // Mark as non-executable if EXECUTE is not set - } + /// Walk page tables to get PTE for a virtual address. + /// Returns None if address is not mapped. + fn get_pte(memory: &[u8], pt_offset: usize, pt_base_gpa: usize, vaddr: u64) -> Option { + let pml4_idx = ((vaddr >> 39) & 0x1ff) as usize; + let pdpt_idx = ((vaddr >> 30) & 0x1ff) as usize; + let pd_idx = ((vaddr >> 21) & 0x1ff) as usize; + let pt_idx = ((vaddr >> 12) & 0x1ff) as usize; - page_flags - } - - /// Verify the complete paging structure for a sandbox configuration - fn verify_paging_structure(name: &str, config: Option) -> Result<()> { - let mut mgr = create_sandbox_with_page_tables(config)?; - - let regions = mgr.layout.get_memory_regions(&mgr.shared_mem)?; - let mem_size = mgr.layout.get_memory_size()?; - - // Calculate how many PD entries should exist based on memory size - // Each PD entry covers 2MB (0x200000 bytes) - // we write enough PD entries to cover all memory, so we need - // enough entries to cover the actual memory size. - let num_pd_entries_needed = mem_size.div_ceil(0x200000); - - mgr.shared_mem.with_exclusivity(|excl_mem| { - // Verify PML4 entry (single entry pointing to PDPT) - let pml4_val = excl_mem.read_u64(PML4_OFFSET)?; - let expected_pml4 = PDPT_GUEST_ADDRESS as u64 | PAGE_PRESENT | PAGE_RW; - if pml4_val != expected_pml4 { - return Err(new_error!( - "{}: PML4[0] incorrect: expected 0x{:x}, got 0x{:x}", - name, - expected_pml4, - pml4_val - )); + let read_entry = |table_gpa: usize, index: usize| -> Option { + let entry_gpa = table_gpa + index * 8; + let offset = entry_gpa - pt_base_gpa + pt_offset; + if offset + 8 > memory.len() { + return None; } - - // Verify PDPT entry (single entry pointing to PD) - let pdpt_val = excl_mem.read_u64(PDPT_OFFSET)?; - let expected_pdpt = PD_GUEST_ADDRESS as u64 | PAGE_PRESENT | PAGE_RW; - if pdpt_val != expected_pdpt { - return Err(new_error!( - "{}: PDPT[0] incorrect: expected 0x{:x}, got 0x{:x}", - name, - expected_pdpt, - pdpt_val - )); + let entry = u64::from_ne_bytes(memory[offset..offset + 8].try_into().ok()?); + if entry & PAGE_PRESENT != 0 { + Some(entry) + } else { + None } + }; - // Verify PD entries that should be present (based on memory size) - for i in 0..num_pd_entries_needed { - let offset = PD_OFFSET + (i * 8); - let pd_val = excl_mem.read_u64(offset)?; - let expected_pt_addr = PT_GUEST_ADDRESS as u64 + (i as u64 * 4096); - let expected_pd = expected_pt_addr | PAGE_PRESENT | PAGE_RW; - if pd_val != expected_pd { - return Err(new_error!( - "{}: PD[{}] incorrect: expected 0x{:x}, got 0x{:x}", - name, - i, - expected_pd, - pd_val - )); - } - } + let pml4e = read_entry(pt_base_gpa, pml4_idx)?; + let pdpt_gpa = (pml4e & PTE_ADDR_MASK) as usize; - // Verify remaining PD entries are not present (0) - for i in num_pd_entries_needed..512 { - let offset = PD_OFFSET + (i * 8); - let pd_val = excl_mem.read_u64(offset)?; - if pd_val != 0 { - return Err(new_error!( - "{}: PD[{}] should be 0 (not present), got 0x{:x}", - name, - i, - pd_val - )); - } - } + let pdpte = read_entry(pdpt_gpa, pdpt_idx)?; + let pd_gpa = (pdpte & PTE_ADDR_MASK) as usize; - // Verify PTEs for each memory region - for region in ®ions { - let start = region.guest_region.start; - let end = region.guest_region.end; - let expected_flags = get_expected_flags(region); - - verify_page_range( - excl_mem, - start, - end, - expected_flags, - &format!("{} {:?}", name, region.region_type), - )?; - } + let pde = read_entry(pd_gpa, pd_idx)?; + let pt_gpa = (pde & PTE_ADDR_MASK) as usize; - Ok(()) - })??; + read_entry(pt_gpa, pt_idx) + } - Ok(()) + /// Verify page tables for a given configuration. + /// Creates a Snapshot and verifies every page in every region has correct PTEs. + fn verify_page_tables(name: &str, config: SandboxConfiguration) { + let path = simple_guest_as_string().expect("failed to get simple guest path"); + let snapshot = Snapshot::from_env(GuestBinary::FilePath(path), config) + .unwrap_or_else(|e| panic!("{}: failed to create snapshot: {}", name, e)); + + let memory = snapshot.memory(); + let layout = snapshot.layout(); + let pt_offset = layout.get_pt_offset(); + let pt_base_gpa = super::super::layout::SandboxMemoryLayout::BASE_ADDRESS + pt_offset; + let regions = snapshot.regions(); + + // Mask for the flags we care about (PRESENT, RW, NX) + const FLAG_MASK: u64 = PAGE_PRESENT | PAGE_RW | PAGE_NX; + + // Verify NULL page (0x0) is NOT mapped + assert!( + get_pte(memory, pt_offset, pt_base_gpa, 0).is_none(), + "{}: NULL page (0x0) should NOT be mapped", + name + ); + + // Verify every page in every region + for region in regions { + let expected = expected_pte_flags(region.flags); + let mut addr = region.guest_region.start as u64; + + while addr < region.guest_region.end as u64 { + let pte = get_pte(memory, pt_offset, pt_base_gpa, addr).unwrap_or_else(|| { + panic!( + "{}: {:?} region: address 0x{:x} is not mapped", + name, region.region_type, addr + ) + }); + + // Verify identity mapping (phys == virt for low memory) + let phys = pte & PTE_ADDR_MASK; + assert_eq!( + phys, addr, + "{}: {:?} region: address 0x{:x} should identity map, got phys 0x{:x}", + name, region.region_type, addr, phys + ); + + // Verify flags + let actual = pte & FLAG_MASK; + assert_eq!( + actual, expected, + "{}: {:?} region: address 0x{:x} has flags 0x{:x}, expected 0x{:x} (region flags: {:?})", + name, region.region_type, addr, actual, expected, region.flags + ); + + addr += PAGE_TABLE_SIZE as u64; + } + } } - /// Test the complete paging structure (PML4, PDPT, PD, and all PTEs) for - /// sandboxes of different sizes: default, small (8MB), medium (64MB), and large (256MB) #[test] - fn test_page_table_contents() { - let test_cases: [(&str, Option); 4] = [ - ("default", None), + fn test_page_tables_for_various_configurations() { + let test_cases: [(&str, SandboxConfiguration); 4] = [ + ("default", { SandboxConfiguration::default() }), ("small (8MB heap)", { let mut cfg = SandboxConfiguration::default(); cfg.set_heap_size(SMALL_HEAP_SIZE); - Some(cfg) + cfg }), ("medium (64MB heap)", { let mut cfg = SandboxConfiguration::default(); cfg.set_heap_size(MEDIUM_HEAP_SIZE); - Some(cfg) + cfg }), ("large (256MB heap)", { let mut cfg = SandboxConfiguration::default(); cfg.set_heap_size(LARGE_HEAP_SIZE); - Some(cfg) + cfg }), ]; for (name, config) in test_cases { - verify_paging_structure(name, config) - .unwrap_or_else(|e| panic!("Page table verification failed for {}: {}", name, e)); + verify_page_tables(name, config); } } } diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index dd7bc8dff..349536cfc 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -20,7 +20,7 @@ use std::os::fd::AsRawFd; #[cfg(unix)] use std::os::linux::fs::MetadataExt; use std::path::Path; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; use flatbuffers::FlatBufferBuilder; @@ -39,9 +39,9 @@ use crate::HyperlightError::{self, SnapshotSandboxMismatch}; use crate::func::{ParameterTuple, SupportedReturnType}; use crate::hypervisor::InterruptHandle; use crate::hypervisor::hyperlight_vm::HyperlightVm; +use crate::mem::memory_region::MemoryRegion; #[cfg(unix)] -use crate::mem::memory_region::MemoryRegionType; -use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; +use crate::mem::memory_region::{MemoryRegionFlags, MemoryRegionType}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::RawPtr; use crate::mem::shared_mem::HostSharedMemory; @@ -50,9 +50,6 @@ use crate::metrics::{ }; use crate::{Result, log_then_return}; -/// Global counter for assigning unique IDs to sandboxes -static SANDBOX_ID_COUNTER: AtomicU64 = AtomicU64::new(0); - /// A fully initialized sandbox that can execute guest functions multiple times. /// /// Guest functions can be called repeatedly while maintaining state between calls. @@ -122,7 +119,7 @@ impl MultiUseSandbox { #[cfg(gdb)] dbg_mem_access_fn: Arc>>, ) -> MultiUseSandbox { Self { - id: SANDBOX_ID_COUNTER.fetch_add(1, Ordering::Relaxed), + id: super::snapshot::SANDBOX_CONFIGURATION_COUNTER.fetch_add(1, Ordering::Relaxed), poisoned: false, host_funcs, mem_mgr: mgr, @@ -475,7 +472,8 @@ impl MultiUseSandbox { /// The caller must ensure the host memory region remains valid and unmodified /// for the lifetime of `self`. #[instrument(err(Debug), skip(self, rgn), parent = Span::current())] - pub unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()> { + #[cfg(target_os = "linux")] + unsafe fn map_region(&mut self, rgn: &MemoryRegion) -> Result<()> { if self.poisoned { return Err(crate::HyperlightError::PoisonedSandbox); } diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 087162a4a..62d8d4c17 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -197,17 +197,18 @@ pub(crate) fn handle_outb( mod tests { use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel; use hyperlight_testing::logger::{LOGGER, Logger}; + use hyperlight_testing::simple_guest_as_string; use log::Level; use tracing_core::callsite::rebuild_interest_cache; use super::outb_log; + use crate::GuestBinary; use crate::mem::layout::SandboxMemoryLayout; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::SharedMemory; use crate::sandbox::SandboxConfiguration; use crate::sandbox::outb::GuestLogData; 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( @@ -229,10 +230,9 @@ mod tests { let sandbox_cfg = SandboxConfiguration::default(); let new_mgr = || { - let exe_info = simple_guest_exe_info().unwrap(); - let (mut mgr, _) = - SandboxMemoryManager::load_guest_binary_into_memory(sandbox_cfg, exe_info, None) - .unwrap(); + let bin = GuestBinary::FilePath(simple_guest_as_string().unwrap()); + let snapshot = crate::sandbox::snapshot::Snapshot::from_env(bin, sandbox_cfg).unwrap(); + let mut mgr = SandboxMemoryManager::from_snapshot(&snapshot).unwrap(); let mem_size = mgr.get_shared_mem_mut().mem_size(); let layout = mgr.layout; let shared_mem = mgr.get_shared_mem_mut(); @@ -341,13 +341,10 @@ mod tests { let sandbox_cfg = SandboxConfiguration::default(); tracing::subscriber::with_default(subscriber.clone(), || { let new_mgr = || { - let exe_info = simple_guest_exe_info().unwrap(); - let (mut mgr, _) = SandboxMemoryManager::load_guest_binary_into_memory( - sandbox_cfg, - exe_info, - None, - ) - .unwrap(); + let bin = GuestBinary::FilePath(simple_guest_as_string().unwrap()); + let snapshot = + crate::sandbox::snapshot::Snapshot::from_env(bin, sandbox_cfg).unwrap(); + let mut mgr = SandboxMemoryManager::from_snapshot(&snapshot).unwrap(); let mem_size = mgr.get_shared_mem_mut().mem_size(); let layout = mgr.layout; let shared_mem = mgr.get_shared_mem_mut(); diff --git a/src/hyperlight_host/src/sandbox/snapshot.rs b/src/hyperlight_host/src/sandbox/snapshot.rs index b8be04732..936886d72 100644 --- a/src/hyperlight_host/src/sandbox/snapshot.rs +++ b/src/hyperlight_host/src/sandbox/snapshot.rs @@ -14,22 +14,45 @@ See the License for the specific language governing permissions and limitations under the License. */ +use std::sync::atomic::{AtomicU64, Ordering}; + use tracing::{Span, instrument}; use crate::HyperlightError::MemoryRegionSizeMismatch; use crate::Result; +use crate::mem::exe::LoadInfo; use crate::mem::memory_region::MemoryRegion; use crate::mem::shared_mem::SharedMemory; +use crate::sandbox::SandboxConfiguration; +use crate::sandbox::uninitialized::{GuestBinary, GuestEnvironment}; +pub(super) static SANDBOX_CONFIGURATION_COUNTER: AtomicU64 = AtomicU64::new(0); /// A wrapper around a `SharedMemory` reference and a snapshot /// of the memory therein pub struct Snapshot { - // Unique ID of the sandbox this snapshot was taken from + /// Unique ID of the sandbox configuration for sandboxes where + /// this snapshot may be restored. sandbox_id: u64, - // Memory of the sandbox at the time this snapshot was taken + /// Layout object for the sandbox. TODO: get rid of this and + /// replace with something saner and set up from the guest (early + /// on?). + /// + /// Not checked on restore, since any sandbox with the same + /// configuration id will share the same layout + layout: crate::mem::layout::SandboxMemoryLayout, + /// Memory of the sandbox at the time this snapshot was taken memory: Vec, - /// The memory regions that were mapped when this snapshot was taken (excluding initial sandbox regions) + /// The memory regions that were mapped when this snapshot was + /// taken (excluding initial sandbox regions) regions: Vec, + /// Extra debug information about the binary in this snapshot, + /// from when the binary was first loaded into the snapshot. + /// + /// This information is provided on a best-effort basis, and there + /// is a pretty good chance that it does not exist; generally speaking, + /// things like persisting a snapshot and reloading it are likely + /// to destroy this information. + load_info: LoadInfo, /// The hash of the other portions of the snapshot. Morally, this /// is just a memoization cache for [`hash`], below, but it is not /// a [`std::sync::OnceLock`] because it may be persisted to disk @@ -38,8 +61,22 @@ pub struct Snapshot { /// It is not a [`blake3::Hash`] because we do not presently /// require constant-time equality checking hash: [u8; 32], + + /// TODO: this should not necessarily be around in the long term... + /// + /// When creating a snapshot directly from a guest binary, this + /// tracks the address that we need to call into before actually + /// using a sandbox from this snapshot in order to do + /// preinitialisation. Ideally we would either not need to do this + /// at all, or do it as part of the snapshot creation process and + /// never need this. + preinitialise: Option, } +/// Compute a deterministic hash of a snapshot. +/// +/// This does not include the load info from the snapshot, because +/// that is only used for debugging builds. fn hash(memory: &[u8], regions: &[MemoryRegion]) -> Result<[u8; 32]> { let mut hasher = blake3::Hasher::new(); hasher.update(memory); @@ -49,21 +86,140 @@ fn hash(memory: &[u8], regions: &[MemoryRegion]) -> Result<[u8; 32]> { hasher.update(&usize::to_le_bytes(rgn.host_region.start)); let host_len = rgn.host_region.end - rgn.host_region.start; if guest_len != host_len { - return Err(MemoryRegionSizeMismatch(host_len, guest_len, rgn.clone())); + return Err(MemoryRegionSizeMismatch( + host_len, + guest_len, + format!("{:?}", rgn), + )); } hasher.update(&usize::to_le_bytes(guest_len)); hasher.update(&u32::to_le_bytes(rgn.flags.bits())); + // Ignore [`MemoryRegion::region_type`], since it is extra + // information for debugging rather than a core part of the + // identity of the snapshot/workload. } + // Ignore [`load_info`], since it is extra information for + // debugging rather than a core part of the identity of the + // snapshot/workload. Ok(hasher.finalize().into()) } impl Snapshot { + /// Create a new snapshot that runs the guest binary identified by env + pub(crate) fn from_env<'a, 'b>( + env: impl Into>, + cfg: SandboxConfiguration, + ) -> Result { + let env = env.into(); + let mut bin = env.guest_binary; + bin.canonicalize()?; + let blob = env.init_data; + + use crate::mem::exe::ExeInfo; + let exe_info = match bin { + GuestBinary::FilePath(bin_path_str) => ExeInfo::from_file(&bin_path_str)?, + GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer)?, + }; + + let guest_blob_size = blob.as_ref().map(|b| b.data.len()).unwrap_or(0); + let guest_blob_mem_flags = blob.as_ref().map(|b| b.permissions); + + #[cfg_attr(not(feature = "init-paging"), allow(unused_mut))] + let mut layout = crate::mem::layout::SandboxMemoryLayout::new( + cfg, + exe_info.loaded_size(), + usize::try_from(cfg.get_stack_size())?, + usize::try_from(cfg.get_heap_size())?, + guest_blob_size, + guest_blob_mem_flags, + )?; + + let load_addr = layout.get_guest_code_address() as u64; + let entrypoint_offset: u64 = exe_info.entrypoint().into(); + + let mut memory = vec![0; layout.get_memory_size()?]; + + let load_info = exe_info.load( + load_addr.try_into()?, + &mut memory[layout.get_guest_code_offset()..], + )?; + + blob.map(|x| layout.write_init_data(&mut memory, x.data)) + .transpose()?; + + #[cfg(feature = "init-paging")] + { + let pt_base_gpa = + crate::mem::layout::SandboxMemoryLayout::BASE_ADDRESS + layout.get_pt_offset(); + let pt_buf = crate::mem::mgr::GuestPageTableBuffer::new(pt_base_gpa); + use hyperlight_common::vmem::{self, BasicMapping, Mapping, MappingKind}; + + use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags}; + for rgn in layout.get_memory_regions_::(())?.iter() { + let readable = rgn.flags.contains(MemoryRegionFlags::READ); + let writable = rgn.flags.contains(MemoryRegionFlags::WRITE) + // Temporary hack: the stack guard page is + // currently checked for in the host, rather than + // the guest, so we need to mark it writable in + // the Stage 1 translation so that the fault + // exception on a write is taken to the + // hypervisor, rather than the guest kernel + || rgn.flags.contains(MemoryRegionFlags::STACK_GUARD); + let executable = rgn.flags.contains(MemoryRegionFlags::EXECUTE); + let mapping = Mapping { + phys_base: rgn.guest_region.start as u64, + virt_base: rgn.guest_region.start as u64, + len: rgn.guest_region.len() as u64, + kind: MappingKind::BasicMapping(BasicMapping { + readable, + writable, + executable, + }), + }; + unsafe { vmem::map(&pt_buf, mapping) }; + } + let mut pt_size_mapped = 0; + while pt_buf.size() > pt_size_mapped { + let mapping = Mapping { + phys_base: (pt_base_gpa + pt_size_mapped) as u64, + virt_base: (hyperlight_common::layout::SNAPSHOT_PT_GVA + pt_size_mapped) as u64, + len: (pt_buf.size() - pt_size_mapped) as u64, + kind: MappingKind::BasicMapping(BasicMapping { + readable: true, + writable: true, + executable: false, + }), + }; + unsafe { vmem::map(&pt_buf, mapping) }; + pt_size_mapped = pt_buf.size(); + } + let pt_bytes = pt_buf.into_bytes(); + layout.set_pt_size(pt_bytes.len()); + memory.extend(&pt_bytes); + } + + let extra_regions = Vec::new(); + let hash = hash(&memory, &extra_regions)?; + + Ok(Self { + sandbox_id: SANDBOX_CONFIGURATION_COUNTER.fetch_add(1, Ordering::Relaxed), + memory, + layout, + regions: extra_regions, + load_info, + hash, + preinitialise: Some(load_addr + entrypoint_offset), + }) + } + /// Take a snapshot of the memory in `shared_mem`, then create a new /// instance of `Self` with the snapshot stored therein. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn new( shared_mem: &mut S, sandbox_id: u64, + layout: crate::mem::layout::SandboxMemoryLayout, + load_info: LoadInfo, regions: Vec, ) -> Result { // TODO: Track dirty pages instead of copying entire memory @@ -71,9 +227,12 @@ impl Snapshot { let hash = hash(&memory, ®ions)?; Ok(Self { sandbox_id, + layout, memory, regions, + load_info, hash, + preinitialise: None, }) } @@ -98,6 +257,19 @@ impl Snapshot { pub(crate) fn memory(&self) -> &[u8] { &self.memory } + + /// Return a copy of the load info for the exe in the snapshot + pub(crate) fn load_info(&self) -> LoadInfo { + self.load_info.clone() + } + + pub(crate) fn layout(&self) -> &crate::mem::layout::SandboxMemoryLayout { + &self.layout + } + + pub(crate) fn preinitialise(&self) -> Option { + self.preinitialise + } } impl PartialEq for Snapshot { @@ -110,6 +282,7 @@ impl PartialEq for Snapshot { mod tests { use hyperlight_common::mem::PAGE_SIZE_USIZE; + use crate::mem::exe::LoadInfo; use crate::mem::shared_mem::{ExclusiveSharedMemory, SharedMemory}; #[test] @@ -121,8 +294,19 @@ mod tests { let mut gm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap(); gm.copy_from_slice(&data1, 0).unwrap(); + let cfg = crate::sandbox::SandboxConfiguration::default(); + let layout = + crate::mem::layout::SandboxMemoryLayout::new(cfg, 4096, 2048, 4096, 0, None).unwrap(); + // Take snapshot of data1 - let snapshot = super::Snapshot::new(&mut gm, 0, Vec::new()).unwrap(); + let snapshot = super::Snapshot::new( + &mut gm, + 0, + layout, + crate::mem::exe::LoadInfo::dummy(), + Vec::new(), + ) + .unwrap(); // Modify memory to data2 gm.copy_from_slice(&data2, 0).unwrap(); @@ -138,7 +322,18 @@ mod tests { let size = PAGE_SIZE_USIZE * 2; let mut gm = ExclusiveSharedMemory::new(size).unwrap(); - let snapshot = super::Snapshot::new(&mut gm, 0, Vec::new()).unwrap(); + let cfg = crate::sandbox::SandboxConfiguration::default(); + let layout = + crate::mem::layout::SandboxMemoryLayout::new(cfg, 4096, 2048, 4096, 0, None).unwrap(); + + let snapshot = super::Snapshot::new( + &mut gm, + 0, + layout, + crate::mem::exe::LoadInfo::dummy(), + Vec::new(), + ) + .unwrap(); assert_eq!(snapshot.mem_size(), size); } @@ -146,15 +341,21 @@ mod tests { fn multiple_snapshots_independent() { let mut gm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap(); + let cfg = crate::sandbox::SandboxConfiguration::default(); + let layout = + crate::mem::layout::SandboxMemoryLayout::new(cfg, 4096, 2048, 4096, 0, None).unwrap(); + // Create first snapshot with pattern A let pattern_a = vec![0xAA; PAGE_SIZE_USIZE]; gm.copy_from_slice(&pattern_a, 0).unwrap(); - let snapshot_a = super::Snapshot::new(&mut gm, 1, Vec::new()).unwrap(); + let snapshot_a = + super::Snapshot::new(&mut gm, 1, layout, LoadInfo::dummy(), Vec::new()).unwrap(); // Create second snapshot with pattern B let pattern_b = vec![0xBB; PAGE_SIZE_USIZE]; gm.copy_from_slice(&pattern_b, 0).unwrap(); - let snapshot_b = super::Snapshot::new(&mut gm, 2, Vec::new()).unwrap(); + let snapshot_b = + super::Snapshot::new(&mut gm, 2, layout, LoadInfo::dummy(), Vec::new()).unwrap(); // Clear memory gm.copy_from_slice(&[0; PAGE_SIZE_USIZE], 0).unwrap(); diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index cd7077062..9cd2b49c6 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -23,12 +23,12 @@ use log::LevelFilter; use tracing::{Span, instrument}; use super::host_funcs::{FunctionRegistry, default_writer_func}; +use super::snapshot::Snapshot; use super::uninitialized_evolve::evolve_impl_multi_use; use crate::func::host_functions::{HostFunction, register_host_function}; use crate::func::{ParameterTuple, SupportedReturnType}; #[cfg(feature = "build-metadata")] use crate::log_build_details; -use crate::mem::exe::ExeInfo; use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags}; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::shared_mem::ExclusiveSharedMemory; @@ -85,6 +85,25 @@ pub enum GuestBinary<'a> { /// A path to the GuestBinary FilePath(String), } +impl<'a> GuestBinary<'a> { + /// If the guest binary is identified by a file, canonicalise the path + /// + /// TODO: Maybe we should make the GuestEnvironment or + /// GuestBinary constructors crate-private and turn this + /// into an invariant on one of those types. + pub fn canonicalize(&mut self) -> Result<()> { + if let GuestBinary::FilePath(p) = self { + let canon = Path::new(&p) + .canonicalize() + .map_err(|e| new_error!("GuestBinary not found: '{}': {}", p, e))? + .into_os_string() + .into_string() + .map_err(|e| new_error!("Error converting OsString to String: {:?}", e))?; + *self = GuestBinary::FilePath(canon) + } + Ok(()) + } +} /// A `GuestBlob` containing data and the permissions for its use. #[derive(Debug)] @@ -137,20 +156,16 @@ impl<'a> From> for GuestEnvironment<'a, '_> { } impl UninitializedSandbox { - /// Creates a new uninitialized sandbox for the given guest environment. - /// - /// The guest binary can be provided as either a file path or memory buffer. - /// An optional configuration can customize memory sizes and sandbox settings. - /// After creation, register host functions using [`register`](Self::register) - /// before calling [`evolve`](Self::evolve) to complete initialization and create the VM. - #[instrument( - err(Debug), - skip(env), - parent = Span::current() - )] - pub fn new<'a, 'b>( - env: impl Into>, + // Creates a new uninitialized sandbox from a pre-built snapshot. + // Note that since memory configuration is part of the snapshot the only configuration + // that can be changed (from the original snapshot) is the configuration defines the behaviour of + // `InterruptHandler` on Linux. + // + // This is ok for now as this is not a public + fn from_snapshot( + snapshot: Arc, cfg: Option, + #[cfg(crashdump)] binary_path: Option, ) -> Result { #[cfg(feature = "build-metadata")] log_build_details(); @@ -159,25 +174,6 @@ impl UninitializedSandbox { #[cfg(target_os = "windows")] check_windows_version()?; - let env: GuestEnvironment<'_, '_> = env.into(); - let guest_binary = env.guest_binary; - let guest_blob = env.init_data; - - // 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))? - .into_os_string() - .into_string() - .map_err(|e| new_error!("Error converting OsString to String: {:?}", e))?; - - GuestBinary::FilePath(path) - } - buffer @ GuestBinary::Buffer(_) => buffer, - }; - let sandbox_cfg = cfg.unwrap_or_default(); #[cfg(any(crashdump, gdb))] @@ -188,13 +184,6 @@ impl UninitializedSandbox { #[cfg(gdb)] let debug_info = sandbox_cfg.get_guest_debug_info(); - #[cfg(crashdump)] - let binary_path = if let GuestBinary::FilePath(ref path) = guest_binary { - Some(path.clone()) - } else { - None - }; - SandboxRuntimeConfig { #[cfg(crashdump)] binary_path, @@ -205,19 +194,11 @@ impl UninitializedSandbox { } }; - let (mut mem_mgr_wrapper, load_info) = UninitializedSandbox::load_guest_binary( - sandbox_cfg, - &guest_binary, - guest_blob.as_ref(), - )?; + let mut mem_mgr_wrapper = + SandboxMemoryManager::::from_snapshot(snapshot.as_ref())?; mem_mgr_wrapper.write_memory_layout()?; - // if env has a guest blob, load it into shared mem - if let Some(blob) = guest_blob { - mem_mgr_wrapper.write_init_data(blob.data)?; - } - let host_funcs = Arc::new(Mutex::new(FunctionRegistry::default())); let mut sandbox = Self { @@ -227,7 +208,7 @@ impl UninitializedSandbox { config: sandbox_cfg, #[cfg(any(crashdump, gdb))] rt_cfg, - load_info, + load_info: snapshot.load_info(), }; // If we were passed a writer for host print register it otherwise use the default. @@ -238,6 +219,37 @@ impl UninitializedSandbox { Ok(sandbox) } + /// Creates a new uninitialized sandbox for the given guest environment. + /// + /// The guest binary can be provided as either a file path or memory buffer. + /// An optional configuration can customize memory sizes and sandbox settings. + /// After creation, register host functions using [`register`](Self::register) + /// before calling [`evolve`](Self::evolve) to complete initialization and create the VM. + #[instrument( + err(Debug), + skip(env), + parent = Span::current() + )] + pub fn new<'a, 'b>( + env: impl Into>, + cfg: Option, + ) -> Result { + let cfg = cfg.unwrap_or_default(); + let env = env.into(); + #[cfg(crashdump)] + let binary_path = match &env.guest_binary { + GuestBinary::FilePath(path) => Some(path.clone()), + GuestBinary::Buffer(_) => None, + }; + let snapshot = Snapshot::from_env(env, cfg)?; + Self::from_snapshot( + Arc::new(snapshot), + Some(cfg), + #[cfg(crashdump)] + binary_path, + ) + } + /// Creates and initializes the virtual machine, transforming this into a ready-to-use sandbox. /// /// This method consumes the `UninitializedSandbox` and performs the final initialization @@ -248,33 +260,6 @@ impl UninitializedSandbox { evolve_impl_multi_use(self) } - /// 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, - guest_blob: Option<&GuestBlob>, - ) -> Result<( - SandboxMemoryManager, - crate::mem::exe::LoadInfo, - )> { - let exe_info = match guest_binary { - GuestBinary::FilePath(bin_path_str) => ExeInfo::from_file(bin_path_str)?, - GuestBinary::Buffer(buffer) => ExeInfo::from_buf(buffer)?, - }; - - SandboxMemoryManager::load_guest_binary_into_memory(cfg, exe_info, guest_blob) - } - /// Sets the maximum log level for guest code execution. /// /// If not set, the log level is determined by the `RUST_LOG` environment variable, @@ -413,20 +398,6 @@ mod tests { 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), - None.as_ref(), - ) - .unwrap(); - } - #[test] fn test_host_functions() { let uninitialized_sandbox = || { @@ -860,7 +831,7 @@ mod tests { // 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 let events = subscriber.get_events(); - assert_eq!(events.len(), 15); + assert_eq!(events.len(), 1); let mut count_matching_events = 0; @@ -938,7 +909,7 @@ 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!(13, num_calls); // Log record 1 @@ -957,7 +928,7 @@ mod tests { // Log record 17 - let logcall = TEST_LOGGER.get_log_call(16).unwrap(); + let logcall = TEST_LOGGER.get_log_call(10).unwrap(); assert_eq!(Level::Error, logcall.level); assert!( logcall @@ -968,14 +939,14 @@ mod tests { // Log record 18 - let logcall = TEST_LOGGER.get_log_call(17).unwrap(); + let logcall = TEST_LOGGER.get_log_call(11).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(12).unwrap(); assert_eq!(Level::Trace, logcall.level); assert_eq!(logcall.args, "-- new;"); assert_eq!("tracing::span", logcall.target); @@ -1053,4 +1024,237 @@ mod tests { 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)")) ); } + + #[test] + fn test_from_snapshot_various_configurations() { + use crate::sandbox::snapshot::Snapshot; + + let binary_path = simple_guest_as_string().unwrap(); + + // Test 1: Create snapshot with default config, create multiple sandboxes from it + { + let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None); + + let snapshot = Arc::new( + Snapshot::from_env(env, Default::default()) + .expect("Failed to create snapshot with default config"), + ); + + // Create first sandbox from snapshot + let sandbox1 = UninitializedSandbox::from_snapshot( + snapshot.clone(), + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create first sandbox from snapshot"); + + // Create second sandbox from same snapshot + let sandbox2 = UninitializedSandbox::from_snapshot( + snapshot.clone(), + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create second sandbox from snapshot"); + + // Both should be able to evolve independently + let _evolved1: MultiUseSandbox = sandbox1.evolve().expect("Failed to evolve sandbox1"); + let _evolved2: MultiUseSandbox = sandbox2.evolve().expect("Failed to evolve sandbox2"); + } + + // Test 2: Create snapshot with custom heap size + { + let mut cfg = SandboxConfiguration::default(); + cfg.set_heap_size(16 * 1024 * 1024); // 16MB heap + + let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None); + + let snapshot = Arc::new( + Snapshot::from_env(env, cfg) + .expect("Failed to create snapshot with custom heap size"), + ); + + let sandbox = UninitializedSandbox::from_snapshot( + snapshot, + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create sandbox from snapshot with custom heap"); + + let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + } + + // Test 3: Create snapshot with custom stack size + { + let mut cfg = SandboxConfiguration::default(); + cfg.set_stack_size(128 * 1024); // 128KB stack + + let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None); + + let snapshot = Arc::new( + Snapshot::from_env(env, cfg) + .expect("Failed to create snapshot with custom stack size"), + ); + + let sandbox = UninitializedSandbox::from_snapshot( + snapshot, + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create sandbox from snapshot with custom stack"); + + let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + } + + // Test 4: Create snapshot with custom input/output buffer sizes + { + let mut cfg = SandboxConfiguration::default(); + cfg.set_input_data_size(64 * 1024); // 64KB input + cfg.set_output_data_size(64 * 1024); // 64KB output + + let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None); + + let snapshot = Arc::new( + Snapshot::from_env(env, cfg) + .expect("Failed to create snapshot with custom buffer sizes"), + ); + + let sandbox = UninitializedSandbox::from_snapshot( + snapshot, + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create sandbox from snapshot with custom buffers"); + + let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + } + + // Test 5: Create snapshot with all custom settings + { + let mut cfg = SandboxConfiguration::default(); + cfg.set_heap_size(32 * 1024 * 1024); // 32MB heap + cfg.set_stack_size(256 * 1024); // 256KB stack + cfg.set_input_data_size(128 * 1024); // 128KB input + cfg.set_output_data_size(128 * 1024); // 128KB output + + let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None); + + let snapshot = Arc::new( + Snapshot::from_env(env, cfg) + .expect("Failed to create snapshot with all custom settings"), + ); + + // Create multiple sandboxes from the same snapshot + let sandbox1 = UninitializedSandbox::from_snapshot( + snapshot.clone(), + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create sandbox1 from fully customized snapshot"); + let sandbox2 = UninitializedSandbox::from_snapshot( + snapshot.clone(), + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create sandbox2 from fully customized snapshot"); + let sandbox3 = UninitializedSandbox::from_snapshot( + snapshot.clone(), + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create sandbox3 from fully customized snapshot"); + + let _evolved1: MultiUseSandbox = sandbox1.evolve().expect("Failed to evolve sandbox1"); + let _evolved2: MultiUseSandbox = sandbox2.evolve().expect("Failed to evolve sandbox2"); + let _evolved3: MultiUseSandbox = sandbox3.evolve().expect("Failed to evolve sandbox3"); + } + + // Test 6: Create snapshot from binary buffer instead of file path + { + let binary_bytes = fs::read(&binary_path).expect("Failed to read binary file"); + + let snapshot = Arc::new( + Snapshot::from_env(GuestBinary::Buffer(&binary_bytes), Default::default()) + .expect("Failed to create snapshot from buffer"), + ); + + let sandbox = UninitializedSandbox::from_snapshot( + snapshot, + None, + #[cfg(crashdump)] + None, + ) + .expect("Failed to create sandbox from buffer-based snapshot"); + + let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + } + + // Test 7: Register host functions on sandboxes created from snapshot + { + let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None); + + let snapshot = Arc::new( + Snapshot::from_env(env, Default::default()).expect("Failed to create snapshot"), + ); + + let mut sandbox = UninitializedSandbox::from_snapshot( + snapshot, + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create sandbox from snapshot"); + + // Register a custom host function + sandbox + .register("CustomAdd", |a: i32, b: i32| Ok(a + b)) + .expect("Failed to register custom function"); + + let evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + + // Verify the host function was registered + let host_funcs = evolved + .host_funcs + .try_lock() + .expect("Failed to lock host funcs"); + + let result = host_funcs + .call_host_function( + "CustomAdd", + vec![ParameterValue::Int(10), ParameterValue::Int(20)], + ) + .expect("Failed to call CustomAdd"); + + assert_eq!(result, ReturnValue::Int(30)); + } + + // Test 8: Create snapshot with init data (guest blob) + { + let init_data = [0xCA, 0xFE, 0xBA, 0xBE]; + let guest_env = + GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), Some(&init_data)); + + let snapshot = Arc::new( + Snapshot::from_env(guest_env, Default::default()) + .expect("Failed to create snapshot with init data"), + ); + + let sandbox = UninitializedSandbox::from_snapshot( + snapshot, + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create sandbox from snapshot with init data"); + + let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); + } + } } diff --git a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs index 0697fc350..d23d5356c 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs @@ -25,7 +25,6 @@ use super::SandboxConfiguration; use super::uninitialized::SandboxRuntimeConfig; use crate::hypervisor::hyperlight_vm::HyperlightVm; use crate::mem::exe::LoadInfo; -use crate::mem::layout::SandboxMemoryLayout; use crate::mem::mgr::SandboxMemoryManager; use crate::mem::ptr::{GuestPtr, RawPtr}; use crate::mem::ptr_offset::Offset; @@ -36,7 +35,7 @@ use crate::sandbox::config::DebugInfo; use crate::sandbox::trace::MemTraceInfo; #[cfg(target_os = "linux")] use crate::signal_handlers::setup_signal_handlers; -use crate::{MultiUseSandbox, Result, UninitializedSandbox, log_then_return, new_error}; +use crate::{MultiUseSandbox, Result, UninitializedSandbox, new_error}; #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] pub(super) fn evolve_impl_multi_use(u_sbox: UninitializedSandbox) -> Result { @@ -103,40 +102,29 @@ pub(crate) fn set_up_hypervisor_partition( #[cfg(any(crashdump, gdb))] rt_cfg: &SandboxRuntimeConfig, _load_info: LoadInfo, ) -> Result { + let base_ptr = GuestPtr::try_from(Offset::from(0))?; #[cfg(feature = "init-paging")] let rsp_ptr = { - let mut regions = mgr.layout.get_memory_regions(&mgr.shared_mem)?; - let rsp_u64 = mgr.set_up_shared_memory(&mut regions)?; - let rsp_raw = RawPtr::from(rsp_u64); - GuestPtr::try_from(rsp_raw) - }?; + let rsp_offset_u64 = mgr.layout.get_rsp_offset() as u64; + base_ptr + Offset::from(rsp_offset_u64) + }; + #[cfg(not(feature = "init-paging"))] let rsp_ptr = GuestPtr::try_from(Offset::from(0))?; + let regions = mgr.layout.get_memory_regions(&mgr.shared_mem)?; - let base_ptr = GuestPtr::try_from(Offset::from(0))?; + let pml4_ptr = { - let pml4_offset_u64 = u64::try_from(SandboxMemoryLayout::PML4_OFFSET)?; + let pml4_offset_u64 = mgr.layout.get_pt_offset() as u64; base_ptr + Offset::from(pml4_offset_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 - ); - } + let entrypoint_ptr = mgr + .entrypoint_offset + .map(|x| { + let entrypoint_total_offset = mgr.load_addr.clone() + x; + GuestPtr::try_from(entrypoint_total_offset) + }) + .ok_or(new_error!("Failed to create entrypoint pointer"))??; // Create gdb thread if gdb is enabled and the configuration is provided #[cfg(gdb)] diff --git a/src/hyperlight_host/src/testing/mod.rs b/src/hyperlight_host/src/testing/mod.rs index 30338f3a8..26776b405 100644 --- a/src/hyperlight_host/src/testing/mod.rs +++ b/src/hyperlight_host/src/testing/mod.rs @@ -13,30 +13,4 @@ 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::{Result, new_error}; 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()) -} - -/// 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) -} From c3ddfb50fdd92cc75683c1ad7a8e8b54482d1dae Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Thu, 18 Dec 2025 22:22:11 +0000 Subject: [PATCH 2/2] Review updates Signed-off-by: Simon Davies --- Cargo.lock | 332 +++++++----------- src/hyperlight_host/src/mem/layout.rs | 9 +- src/hyperlight_host/src/mem/mgr.rs | 16 +- src/hyperlight_host/src/sandbox/snapshot.rs | 22 +- .../src/sandbox/uninitialized.rs | 4 +- .../src/sandbox/uninitialized_evolve.rs | 6 +- src/tests/rust_guests/dummyguest/Cargo.lock | 8 +- src/tests/rust_guests/simpleguest/Cargo.lock | 8 +- src/tests/rust_guests/witguest/Cargo.lock | 8 +- 9 files changed, 173 insertions(+), 240 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9da416300..62cea2e0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.8.30", + "zerocopy 0.8.31", ] [[package]] @@ -299,9 +299,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -317,9 +317,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cairo-rs" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe4354df4da648870e363387679081f8f9fc538ec8b55901e3740c6a0ef81b1" +checksum = "b01fe135c0bd16afe262b6dea349bd5ea30e6de50708cec639aae7c5c14cc7e4" dependencies = [ "bitflags 2.10.0", "cairo-sys-rs", @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "cairo-sys-rs" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d6c3300c7103eb8e4de07591003511aa25664438f8c6fc317a3a9902c103f8" +checksum = "06c28280c6b12055b5e39e4554271ae4e6630b27c0da9148c4cf6485fc6d245c" dependencies = [ "glib-sys", "libc", @@ -365,9 +365,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -386,9 +386,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726" +checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720" dependencies = [ "smallvec", "target-lexicon", @@ -416,7 +416,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1038,16 +1038,17 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" dependencies = [ "cc", "cfg-if", "libc", "log", "rustversion", - "windows 0.61.3", + "windows-link", + "windows-result", ] [[package]] @@ -1095,9 +1096,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daeff3dd716d1ba91850b976b76a1c2d28f99ef6c1602cd8fdaa8fab8017fd9c" +checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a" dependencies = [ "futures-channel", "futures-core", @@ -1112,9 +1113,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" +checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" dependencies = [ "glib-sys", "gobject-sys", @@ -1125,9 +1126,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" +checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" dependencies = [ "bitflags 2.10.0", "libc", @@ -1138,9 +1139,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2" +checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ "bitflags 2.10.0", "futures-channel", @@ -1159,9 +1160,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb" +checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" dependencies = [ "heck", "proc-macro-crate", @@ -1172,9 +1173,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" +checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" dependencies = [ "libc", "system-deps", @@ -1201,9 +1202,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" +checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" dependencies = [ "glib-sys", "libc", @@ -1248,7 +1249,7 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", - "zerocopy 0.8.30", + "zerocopy 0.8.31", ] [[package]] @@ -1358,9 +1359,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64", "bytes", @@ -1546,8 +1547,8 @@ dependencies = [ "tracing-tracy", "uuid", "vmm-sys-util", - "windows 0.62.2", - "windows-result 0.4.1", + "windows", + "windows-result", "windows-sys 0.61.2", "windows-version", ] @@ -1591,7 +1592,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -1651,9 +1652,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1665,9 +1666,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -1799,9 +1800,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -1863,9 +1864,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.18.2+1.9.1" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -1880,7 +1881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1896,9 +1897,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", @@ -1992,8 +1993,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb4bdc8b0ce69932332cf76d24af69c3a155242af95c226b2ab6c2e371ed1149" dependencies = [ "thiserror 2.0.17", - "zerocopy 0.8.30", - "zerocopy-derive 0.8.30", + "zerocopy 0.8.31", + "zerocopy-derive 0.8.31", ] [[package]] @@ -2094,9 +2095,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -2112,7 +2113,7 @@ dependencies = [ "libc", "num_enum", "vmm-sys-util", - "zerocopy 0.8.30", + "zerocopy 0.8.31", ] [[package]] @@ -2333,9 +2334,9 @@ dependencies = [ [[package]] name = "pango" -version = "0.21.3" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37b7a678e18c2e9f2485f7e39b7b2dac99590d5ddef08a7f56eae38a145402e" +checksum = "52d1d85e2078077a065bb7fc072783d5bcd4e51b379f22d67107d0a16937eb69" dependencies = [ "gio", "glib", @@ -2345,9 +2346,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f5daf21da43fba9f2a0092da0eebeb77637c23552bccaf58f791c518009c94" +checksum = "b4f06627d36ed5ff303d2df65211fc2e52ba5b17bf18dd80ff3d9628d6e06cfd" dependencies = [ "glib-sys", "gobject-sys", @@ -2357,9 +2358,9 @@ dependencies = [ [[package]] name = "pangocairo" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe686297711b9c0499d0e292e86d343d90b4832d19ebff3d50ce3f627b64e17" +checksum = "b36c5c84304072939d860595d9bda2a797d3bd6f7215e20b8ccd0e72d84da8c8" dependencies = [ "cairo-rs", "glib", @@ -2370,9 +2371,9 @@ dependencies = [ [[package]] name = "pangocairo-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6263d7d919c8f3ccd31874774b5f7924e88f5b82ddee3163b3ef49197786a00" +checksum = "eadbb01ad38be76e0d37e329d40ba0f3f9ef261d7b84b05201d7a0f14f819406" dependencies = [ "cairo-sys-rs", "glib-sys", @@ -2401,7 +2402,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2419,8 +2420,8 @@ dependencies = [ "arrayvec", "bitflags 2.10.0", "thiserror 2.0.17", - "zerocopy 0.8.30", - "zerocopy-derive 0.8.30", + "zerocopy 0.8.31", + "zerocopy-derive 0.8.31", ] [[package]] @@ -2603,9 +2604,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "portable-atomic-util" @@ -2631,7 +2632,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.30", + "zerocopy 0.8.31", ] [[package]] @@ -2891,9 +2892,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" dependencies = [ "base64", "bytes", @@ -3139,9 +3140,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -3242,9 +3243,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "sketches-ddsketch" @@ -3499,9 +3500,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", @@ -3514,18 +3515,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -3535,18 +3536,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" @@ -3606,9 +3607,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.10.0", "bytes", @@ -4006,9 +4007,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -4019,9 +4020,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -4032,9 +4033,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4042,9 +4043,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", @@ -4055,9 +4056,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] @@ -4077,9 +4078,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -4126,38 +4127,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", ] [[package]] @@ -4166,20 +4145,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -4190,20 +4156,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -4212,9 +4167,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-core", + "windows-link", + "windows-threading", ] [[package]] @@ -4239,45 +4194,20 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-core", + "windows-link", ] [[package]] @@ -4286,16 +4216,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -4304,7 +4225,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4322,7 +4243,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4347,7 +4268,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -4358,22 +4279,13 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4382,7 +4294,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4483,9 +4395,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -4552,11 +4464,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ - "zerocopy-derive 0.8.30", + "zerocopy-derive 0.8.31", ] [[package]] @@ -4572,9 +4484,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/src/hyperlight_host/src/mem/layout.rs b/src/hyperlight_host/src/mem/layout.rs index 00b1e58d3..13e78692e 100644 --- a/src/hyperlight_host/src/mem/layout.rs +++ b/src/hyperlight_host/src/mem/layout.rs @@ -225,7 +225,10 @@ impl SandboxMemoryLayout { const MAX_MEMORY_SIZE: usize = 0x40000000 - Self::BASE_ADDRESS; /// The base address of the sandbox's memory. + #[cfg(feature = "init-paging")] pub(crate) const BASE_ADDRESS: usize = 0x1000; + #[cfg(not(feature = "init-paging"))] + pub(crate) const BASE_ADDRESS: usize = 0x0; // the offset into a sandbox's input/output buffer where the stack starts const STACK_POINTER_SIZE_BYTES: u64 = 8; @@ -478,7 +481,7 @@ impl SandboxMemoryLayout { self.pt_offset } - /// Get the offset into the snapshot region of the page tables + /// Sets the size of the memory region used for page tables #[instrument(skip_all, parent = Span::current(), level= "Trace")] #[cfg(feature = "init-paging")] pub(crate) fn set_pt_size(&mut self, size: usize) { @@ -668,8 +671,8 @@ impl SandboxMemoryLayout { if after_init_offset != expected_pt_offset { return Err(new_error!( "Page table offset does not match expected: {}, actual: {}", - expected_init_data_offset, - init_data_offset + expected_pt_offset, + after_init_offset )); } diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index 7c9b96f83..6d1cea0f5 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -13,6 +13,8 @@ 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(feature = "init-paging")] +use std::cmp::Ordering; use flatbuffers::FlatBufferBuilder; use hyperlight_common::flatbuffer_wrappers::function_call::{ @@ -127,7 +129,7 @@ impl GuestPageTableBuffer { } pub(crate) fn size(&self) -> usize { - self.buffer.borrow().len() * PAGE_TABLE_SIZE + self.buffer.borrow().len() } pub(crate) fn into_bytes(self) -> Box<[u8]> { @@ -308,6 +310,16 @@ 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")] + #[cfg(feature = "init-paging")] + pub(crate) fn check_stack_guard(&self) -> Result { + let expected = self.stack_cookie; + let offset = self.layout.get_top_of_user_stack_offset(); + let actual: [u8; STACK_COOKIE_LEN] = self.shared_mem.read(offset)?; + let cmp_res = expected.iter().cmp(actual.iter()); + Ok(cmp_res == Ordering::Equal) + } + + #[cfg(not(feature = "init-paging"))] pub(crate) fn check_stack_guard(&self) -> Result { Ok(true) } @@ -409,8 +421,6 @@ impl SandboxMemoryManager { } } -// ...existing code... - #[cfg(test)] #[cfg(all(feature = "init-paging", target_arch = "x86_64"))] mod tests { diff --git a/src/hyperlight_host/src/sandbox/snapshot.rs b/src/hyperlight_host/src/sandbox/snapshot.rs index 936886d72..1033d639a 100644 --- a/src/hyperlight_host/src/sandbox/snapshot.rs +++ b/src/hyperlight_host/src/sandbox/snapshot.rs @@ -62,14 +62,19 @@ pub struct Snapshot { /// require constant-time equality checking hash: [u8; 32], - /// TODO: this should not necessarily be around in the long term... + /// Preinitialisation entry point for snapshots created directly from a + /// guest binary. /// - /// When creating a snapshot directly from a guest binary, this - /// tracks the address that we need to call into before actually - /// using a sandbox from this snapshot in order to do - /// preinitialisation. Ideally we would either not need to do this - /// at all, or do it as part of the snapshot creation process and - /// never need this. + /// When creating a snapshot directly from a guest binary, this tracks + /// the address that we need to call into before actually using a + /// sandbox from this snapshot in order to perform guest-side + /// preinitialisation. + /// + /// Long-term, the intention is to run this preinitialisation eagerly as + /// part of the snapshot creation process so that restored sandboxes can + /// begin executing from their normal entry point without requiring this + /// field. Until that refactoring happens, this remains part of the + /// snapshot format and must be preserved. preinitialise: Option, } @@ -105,7 +110,8 @@ fn hash(memory: &[u8], regions: &[MemoryRegion]) -> Result<[u8; 32]> { } impl Snapshot { - /// Create a new snapshot that runs the guest binary identified by env + /// Create a new snapshot from the guest binary identified by `env`. With the configuration + /// specified in `cfg`. pub(crate) fn from_env<'a, 'b>( env: impl Into>, cfg: SandboxConfiguration, diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index 9cd2b49c6..ea40a7f43 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -88,6 +88,8 @@ pub enum GuestBinary<'a> { impl<'a> GuestBinary<'a> { /// If the guest binary is identified by a file, canonicalise the path /// + /// For [`GuestBinary::FilePath`], this resolves the path to its canonical + /// form. For [`GuestBinary::Buffer`], this method is a no-op. /// TODO: Maybe we should make the GuestEnvironment or /// GuestBinary constructors crate-private and turn this /// into an invariant on one of those types. @@ -161,7 +163,7 @@ impl UninitializedSandbox { // that can be changed (from the original snapshot) is the configuration defines the behaviour of // `InterruptHandler` on Linux. // - // This is ok for now as this is not a public + // This is ok for now as this is not a public function fn from_snapshot( snapshot: Arc, cfg: Option, diff --git a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs index d23d5356c..c9f6bc220 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs @@ -120,11 +120,11 @@ pub(crate) fn set_up_hypervisor_partition( }; let entrypoint_ptr = mgr .entrypoint_offset - .map(|x| { + .ok_or_else(|| new_error!("Entrypoint offset is None")) + .and_then(|x| { let entrypoint_total_offset = mgr.load_addr.clone() + x; GuestPtr::try_from(entrypoint_total_offset) - }) - .ok_or(new_error!("Failed to create entrypoint pointer"))??; + })?; // Create gdb thread if gdb is enabled and the configuration is provided #[cfg(gdb)] diff --git a/src/tests/rust_guests/dummyguest/Cargo.lock b/src/tests/rust_guests/dummyguest/Cargo.lock index 51a6aed35..7c63e8c58 100644 --- a/src/tests/rust_guests/dummyguest/Cargo.lock +++ b/src/tests/rust_guests/dummyguest/Cargo.lock @@ -388,9 +388,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -410,9 +410,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" [[package]] name = "unicode-ident" diff --git a/src/tests/rust_guests/simpleguest/Cargo.lock b/src/tests/rust_guests/simpleguest/Cargo.lock index 032791dab..e74c91d02 100644 --- a/src/tests/rust_guests/simpleguest/Cargo.lock +++ b/src/tests/rust_guests/simpleguest/Cargo.lock @@ -392,9 +392,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -415,9 +415,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" [[package]] name = "unicode-ident" diff --git a/src/tests/rust_guests/witguest/Cargo.lock b/src/tests/rust_guests/witguest/Cargo.lock index 7933a0903..08d4f9a4b 100644 --- a/src/tests/rust_guests/witguest/Cargo.lock +++ b/src/tests/rust_guests/witguest/Cargo.lock @@ -618,9 +618,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -640,9 +640,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" [[package]] name = "unicode-ident"