diff --git a/docs/assets/hyperlight_arch.excalidraw b/docs/assets/hyperlight_arch.excalidraw index bfaeca907..3d18006bb 100644 --- a/docs/assets/hyperlight_arch.excalidraw +++ b/docs/assets/hyperlight_arch.excalidraw @@ -544,10 +544,10 @@ { "id": "Ph9DIcEYybit7fiUewmDA", "type": "arrow", - "x": 1111.926599382088, - "y": 426, - "width": 3.1214874998370306, - "height": 123, + "x": 1107.926599382088, + "y": 416, + "width": 0.8785125001629694, + "height": 133, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -562,11 +562,11 @@ "type": 2 }, "seed": 230755504, - "version": 528, - "versionNonce": 1642140732, + "version": 549, + "versionNonce": 2008038525, "isDeleted": false, "boundElements": [], - "updated": 1730814289308, + "updated": 1737045415864, "link": null, "locked": false, "points": [ @@ -575,20 +575,22 @@ 0 ], [ - -3.1214874998370306, - 123 + 0.8785125001629694, + 133 ] ], "lastCommittedPoint": null, "startBinding": { "elementId": "NJEIgp1M_osOAid-pqylt", - "gap": 10.5, - "focus": -0.19866512165760108 + "focus": -0.14562925044842748, + "gap": 1, + "fixedPoint": null }, "endBinding": { "elementId": "7nba6ESM8WtJu9ALZO5xR", + "focus": -0.05182243444132844, "gap": 1, - "focus": 0.03885529393375819 + "fixedPoint": null }, "startArrowhead": null, "endArrowhead": "arrow", @@ -596,8 +598,8 @@ }, { "type": "arrow", - "version": 520, - "versionNonce": 967519420, + "version": 548, + "versionNonce": 562391101, "isDeleted": false, "id": "p7oFf4tdH2F-Rs6h74mXc", "fillStyle": "hachure", @@ -606,12 +608,12 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 1033.262520530746, - "y": 423.6756983844284, + "x": 1036.262520530746, + "y": 413.6756983844284, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", - "width": 69.44279429559049, - "height": 168, + "width": 74.44279429559049, + "height": 186, "seed": 584371376, "groupIds": [], "frameId": null, @@ -619,18 +621,20 @@ "type": 2 }, "boundElements": [], - "updated": 1730814289308, + "updated": 1737045406282, "link": null, "locked": false, "startBinding": { "elementId": "NJEIgp1M_osOAid-pqylt", - "gap": 8.175698384428415, - "focus": 0.34981241764755727 + "focus": 0.365217665128887, + "gap": 1, + "fixedPoint": null }, "endBinding": { "elementId": "3kyoI793GcGF7EDeHkGwn", - "gap": 8.324301615571528, - "focus": -0.8818303206989911 + "focus": -0.9247001447347454, + "gap": 1, + "fixedPoint": null }, "lastCommittedPoint": null, "startArrowhead": null, @@ -641,16 +645,16 @@ 0 ], [ - -69.44279429559049, - 168 + -74.44279429559049, + 186 ] ], "index": "aN" }, { "type": "arrow", - "version": 656, - "versionNonce": 832451900, + "version": 688, + "versionNonce": 1717589939, "isDeleted": false, "id": "Kb3EyDIdXPG3ZT2r9mpHi", "fillStyle": "hachure", @@ -659,12 +663,12 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 992.0058069311134, - "y": 427.6685868091555, + "x": 1000.0058069311134, + "y": 413.6685868091555, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", - "width": 98.36782637495253, - "height": 225.0000000000001, + "width": 105.36782637495253, + "height": 246.0000000000001, "seed": 752920240, "groupIds": [], "frameId": null, @@ -672,18 +676,20 @@ "type": 2 }, "boundElements": [], - "updated": 1730814289308, + "updated": 1737045396963, "link": null, "locked": false, "startBinding": { "elementId": "NJEIgp1M_osOAid-pqylt", - "gap": 12.1685868091555, - "focus": 0.6750814542078091 + "focus": 0.6636815928403077, + "gap": 1, + "fixedPoint": null }, "endBinding": { "elementId": "4UEm1MHIYvpNpekBnVfxq", - "gap": 7.331413190844387, - "focus": -0.7616555031432064 + "focus": -0.7623126270323419, + "gap": 1, + "fixedPoint": null }, "lastCommittedPoint": null, "startArrowhead": null, @@ -694,8 +700,8 @@ 0 ], [ - -98.36782637495253, - 225.0000000000001 + -105.36782637495253, + 246.0000000000001 ] ], "index": "aO" @@ -779,8 +785,8 @@ }, { "type": "rectangle", - "version": 183, - "versionNonce": 1452679684, + "version": 237, + "versionNonce": 1173479901, "isDeleted": false, "id": "b7Bij3NIQzuDHvVD1Sq9F", "fillStyle": "hachure", @@ -793,8 +799,8 @@ "y": 622, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", - "width": 253.00000000000006, - "height": 43, + "width": 351, + "height": 60, "seed": 1087376048, "groupIds": [], "frameId": null, @@ -811,15 +817,15 @@ "type": "arrow" } ], - "updated": 1730814221461, + "updated": 1737045370952, "link": null, "locked": false, "index": "aR" }, { "type": "text", - "version": 263, - "versionNonce": 517468220, + "version": 384, + "versionNonce": 879992381, "isDeleted": false, "id": "kasuMAdu3diZs4y3X1s-9", "fillStyle": "hachure", @@ -828,27 +834,27 @@ "roughness": 1, "opacity": 100, "angle": 0, - "x": 437.0599899291992, - "y": 631, + "x": 441.3483333309491, + "y": 627, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", - "width": 225.88002014160156, - "height": 25, + "width": 315.3033333381017, + "height": 50, "seed": 2044409008, "groupIds": [], "frameId": null, "roundness": null, "boundElements": [], - "updated": 1730814221461, + "updated": 1737045370952, "link": null, "locked": false, "fontSize": 20, "fontFamily": 1, - "text": "VirtualAlloc/VirtualFree", + "text": "CreateFileMapping/MapViewOfFile\nUnmapViewOfFile/CloseHandle", "textAlign": "center", "verticalAlign": "middle", "containerId": "b7Bij3NIQzuDHvVD1Sq9F", - "originalText": "VirtualAlloc/VirtualFree", + "originalText": "CreateFileMapping/MapViewOfFile\nUnmapViewOfFile/CloseHandle", "lineHeight": 1.25, "baseline": 18, "index": "aS", @@ -857,10 +863,10 @@ { "id": "blyLXaPSOXpb1fkRZ017C", "type": "arrow", - "x": 636.6588056574739, - "y": 415, - "width": 12.830409331983105, - "height": 136, + "x": 635.6588056574739, + "y": 414, + "width": 11.830409331983105, + "height": 148, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -875,11 +881,11 @@ "type": 2 }, "seed": 58666576, - "version": 221, - "versionNonce": 1326137732, + "version": 250, + "versionNonce": 960823891, "isDeleted": false, "boundElements": [], - "updated": 1730814221461, + "updated": 1737045430579, "link": null, "locked": false, "points": [ @@ -888,20 +894,22 @@ 0 ], [ - -12.830409331983105, - 136 + -11.830409331983105, + 148 ] ], "lastCommittedPoint": null, "startBinding": { "elementId": "-vXJ-MosW68OujoNRBhQj", - "focus": -0.3141025641025642, - "gap": 2 + "focus": -0.30148431419804506, + "gap": 1, + "fixedPoint": null }, "endBinding": { "elementId": "c0XS-Vryc8iymtiVZQj9a", - "focus": 0.2554561155597424, - "gap": 10 + "focus": 0.27404122631940364, + "gap": 1, + "fixedPoint": null }, "startArrowhead": null, "endArrowhead": "arrow", @@ -910,10 +918,10 @@ { "id": "vtiLTp9RPUEAHDDwc5n_T", "type": "arrow", - "x": 564.3431442239641, - "y": 426, - "width": 73.80817085522006, - "height": 195, + "x": 561.3431442239641, + "y": 415, + "width": 82.16943731060223, + "height": 211.5, "angle": 0, "strokeColor": "#1e1e1e", "backgroundColor": "transparent", @@ -928,11 +936,11 @@ "type": 2 }, "seed": 835222704, - "version": 238, - "versionNonce": 1801881788, + "version": 349, + "versionNonce": 1154055165, "isDeleted": false, "boundElements": [], - "updated": 1730814221461, + "updated": 1737045424265, "link": null, "locked": false, "points": [ @@ -941,20 +949,22 @@ 0 ], [ - -73.80817085522006, - 195 + -82.16943731060223, + 211.5 ] ], "lastCommittedPoint": null, "startBinding": { "elementId": "-vXJ-MosW68OujoNRBhQj", - "focus": 0.14180380423167346, - "gap": 13 + "focus": 0.19263691070624067, + "gap": 2, + "fixedPoint": null }, "endBinding": { "elementId": "b7Bij3NIQzuDHvVD1Sq9F", - "focus": -0.5049201779638837, - "gap": 1 + "focus": -0.6931852241077019, + "gap": 1, + "fixedPoint": null }, "startArrowhead": null, "endArrowhead": "arrow", @@ -981,11 +991,20 @@ "type": 3 }, "seed": 500723376, - "version": 122, - "versionNonce": 1157127556, + "version": 124, + "versionNonce": 251249459, "isDeleted": false, - "boundElements": [], - "updated": 1730814244437, + "boundElements": [ + { + "id": "vtiLTp9RPUEAHDDwc5n_T", + "type": "arrow" + }, + { + "id": "p7oFf4tdH2F-Rs6h74mXc", + "type": "arrow" + } + ], + "updated": 1737045400331, "link": null, "locked": false, "index": "aV" diff --git a/docs/assets/hyperlight_arch.png b/docs/assets/hyperlight_arch.png index 93294c5e2..5a97f16fd 100644 Binary files a/docs/assets/hyperlight_arch.png and b/docs/assets/hyperlight_arch.png differ diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index 9cf699cf7..89a2ae72a 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -62,6 +62,7 @@ windows = { version = "0.59", features = [ "Win32_System_Memory", "Win32_System_Threading", "Win32_System_JobObjects", + "Win32_System_SystemServices", ] } windows-sys = { version = "0.59", features = ["Win32"] } windows-result = "0.3" diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index 5ce5e815d..067d0cb1a 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -21,7 +21,6 @@ use std::string::String; use hyperlight_common::mem::PAGE_SIZE_USIZE; use tracing::{instrument, Span}; -use windows::Win32::Foundation::HANDLE; use windows::Win32::System::Hypervisor::{ WHvX64RegisterCr0, WHvX64RegisterCr3, WHvX64RegisterCr4, WHvX64RegisterCs, WHvX64RegisterEfer, WHV_MEMORY_ACCESS_TYPE, WHV_PARTITION_HANDLE, WHV_REGISTER_VALUE, WHV_RUN_VP_EXIT_CONTEXT, @@ -33,7 +32,7 @@ use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper}; use super::surrogate_process::SurrogateProcess; use super::surrogate_process_manager::*; use super::windows_hypervisor_platform::{VMPartition, VMProcessor}; -use super::wrappers::WHvFPURegisters; +use super::wrappers::{HandleWrapper, WHvFPURegisters}; use super::{ HyperlightExit, Hypervisor, VirtualCPU, CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP, CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX, EFER_SCE, @@ -43,15 +42,14 @@ use crate::hypervisor::hypervisor_handler::HypervisorHandler; use crate::hypervisor::wrappers::WHvGeneralRegisters; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::ptr::{GuestPtr, RawPtr}; -use crate::HyperlightError::WindowsAPIError; -use crate::{debug, log_then_return, new_error, Result}; +use crate::{debug, new_error, Result}; /// A Hypervisor driver for HyperV-on-Windows. pub(crate) struct HypervWindowsDriver { size: usize, // this is the size of the memory region, excluding the 2 surrounding guard pages processor: VMProcessor, - surrogate_process: SurrogateProcess, - source_address: *mut c_void, // this points into the first guard page + _surrogate_process: SurrogateProcess, // we need to keep a reference to the SurrogateProcess for the duration of the driver since otherwise it will dropped and the memory mapping will be unmapped and the surrogate process will be returned to the pool + source_address: *mut c_void, // this points into the first guard page entrypoint: u64, orig_rsp: GuestPtr, mem_regions: Vec, @@ -73,6 +71,7 @@ impl HypervWindowsDriver { pml4_address: u64, entrypoint: u64, rsp: u64, + mmap_file_handle: HandleWrapper, ) -> Result { // create and setup hypervisor partition let mut partition = VMPartition::new(1)?; @@ -81,7 +80,7 @@ impl HypervWindowsDriver { // with guard pages setup let surrogate_process = { let mgr = get_surrogate_process_manager()?; - mgr.get_surrogate_process(raw_size, raw_source_address) + mgr.get_surrogate_process(raw_size, raw_source_address, mmap_file_handle) }?; partition.map_gpa_range(&mem_regions, surrogate_process.process_handle)?; @@ -95,7 +94,7 @@ impl HypervWindowsDriver { Ok(Self { size: mem_size, processor: proc, - surrogate_process, + _surrogate_process: surrogate_process, source_address: raw_source_address, entrypoint, orig_rsp: GuestPtr::try_from(RawPtr::from(rsp))?, @@ -402,54 +401,8 @@ impl Hypervisor for HypervWindowsDriver { #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")] fn run(&mut self) -> Result { - let bytes_written: Option<*mut usize> = None; - let bytes_read: Option<*mut usize> = None; - let handle: HANDLE = self.surrogate_process.process_handle.into(); - - // TODO optimise this - // the following write to and read from process memory is required as we need to use - // surrogate processes to allow more than one WHP Partition per process - // see HyperVSurrogateProcessManager - // this needs updating so that - // 1. it only writes to memory that changes between usage - // 2. memory is allocated in the process once and then only freed and reallocated if the - // memory needs to grow. - - // - copy stuff to surrogate process - - if let Err(e) = unsafe { - windows::Win32::System::Diagnostics::Debug::WriteProcessMemory( - handle, - self.surrogate_process - .allocated_address - .add(PAGE_SIZE_USIZE), - self.source_address.add(PAGE_SIZE_USIZE), - self.size, - bytes_written, - ) - } { - log_then_return!(WindowsAPIError(e.clone())); - } - - // - call WHvRunVirtualProcessor let exit_context: WHV_RUN_VP_EXIT_CONTEXT = self.processor.run()?; - // - call read-process memory - - if let Err(e) = unsafe { - windows::Win32::System::Diagnostics::Debug::ReadProcessMemory( - handle, - self.surrogate_process - .allocated_address - .add(PAGE_SIZE_USIZE), - self.source_address.add(PAGE_SIZE_USIZE), - self.size, - bytes_read, - ) - } { - log_then_return!(WindowsAPIError(e.clone())); - } - let result = match exit_context.ExitReason { // WHvRunVpExitReasonX64IoPortAccess WHV_RUN_VP_EXIT_REASON(2i32) => { diff --git a/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs b/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs index 3975d5344..c78624c0f 100644 --- a/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs +++ b/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs @@ -38,6 +38,8 @@ use windows::Win32::System::Hypervisor::{WHvCancelRunVirtualProcessor, WHV_PARTI #[cfg(feature = "function_call_metrics")] use crate::histogram_vec_observe; use crate::hypervisor::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper}; +#[cfg(target_os = "windows")] +use crate::hypervisor::wrappers::HandleWrapper; use crate::hypervisor::Hypervisor; use crate::mem::layout::SandboxMemoryLayout; use crate::mem::mgr::SandboxMemoryManager; @@ -905,6 +907,9 @@ fn set_up_hypervisor_partition( #[cfg(target_os = "windows")] Some(HypervisorType::Whp) => { + let mmap_file_handle = mgr + .shared_mem + .with_exclusivity(|e| e.get_mmap_file_handle())?; let hv = crate::hypervisor::hyperv_windows::HypervWindowsDriver::new( regions, mgr.shared_mem.raw_mem_size(), // we use raw_* here because windows driver requires 64K aligned addresses, @@ -912,6 +917,7 @@ fn set_up_hypervisor_partition( pml4_ptr.absolute()?, entrypoint_ptr.absolute()?, rsp_ptr.absolute()?, + HandleWrapper::from(mmap_file_handle), )?; Ok(Box::new(hv)) } diff --git a/src/hyperlight_host/src/hypervisor/surrogate_process.rs b/src/hyperlight_host/src/hypervisor/surrogate_process.rs index ad1f4ad51..4ebbaec94 100644 --- a/src/hyperlight_host/src/hypervisor/surrogate_process.rs +++ b/src/hyperlight_host/src/hypervisor/surrogate_process.rs @@ -18,7 +18,9 @@ use core::ffi::c_void; use tracing::{instrument, Span}; use windows::Win32::Foundation::HANDLE; -use windows::Win32::System::Memory::{VirtualFreeEx, MEM_RELEASE}; +use windows::Win32::System::Memory::{ + UnmapViewOfFile2, MEMORY_MAPPED_VIEW_ADDRESS, UNMAP_VIEW_OF_FILE_FLAGS, +}; use super::surrogate_process_manager::get_surrogate_process_manager; use super::wrappers::HandleWrapper; @@ -54,10 +56,16 @@ impl Default for SurrogateProcess { impl Drop for SurrogateProcess { #[instrument(skip_all, parent = Span::current(), level= "Trace")] fn drop(&mut self) { - let handle: HANDLE = self.process_handle.into(); - if let Err(e) = unsafe { VirtualFreeEx(handle, self.allocated_address, 0, MEM_RELEASE) } { + let process_handle: HANDLE = self.process_handle.into(); + let memory_mapped_view_address = MEMORY_MAPPED_VIEW_ADDRESS { + Value: self.allocated_address, + }; + let flags = UNMAP_VIEW_OF_FILE_FLAGS(0); + if let Err(e) = + unsafe { UnmapViewOfFile2(process_handle, memory_mapped_view_address, flags) } + { tracing::error!( - "Failed to free surrogate process resources (VirtualFreeEx failed): {:?}", + "Failed to free surrogate process resources (UnmapViewOfFile2 failed): {:?}", e ); } @@ -66,9 +74,21 @@ impl Drop for SurrogateProcess { // of the SurrogateProcess being dropped. this is ok to // do because we are in the process of dropping ourselves // anyway. - get_surrogate_process_manager() - .unwrap() - .return_surrogate_process(self.process_handle) - .unwrap(); + match get_surrogate_process_manager() { + Ok(manager) => match manager.return_surrogate_process(self.process_handle) { + Ok(_) => (), + Err(e) => { + tracing::error!("Failed to return surrogate process to surrogate process manager when dropping : {:?}", e); + return; + } + }, + Err(e) => { + tracing::error!( + "Failed to get surrogate process manager when dropping SurrogateProcess: {:?}", + e + ); + return; + } + } } } diff --git a/src/hyperlight_host/src/hypervisor/surrogate_process_manager.rs b/src/hyperlight_host/src/hypervisor/surrogate_process_manager.rs index 4cec0f459..cbb81e1fb 100644 --- a/src/hyperlight_host/src/hypervisor/surrogate_process_manager.rs +++ b/src/hyperlight_host/src/hypervisor/surrogate_process_manager.rs @@ -33,9 +33,9 @@ use windows::Win32::System::JobObjects::{ JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, }; use windows::Win32::System::Memory::{ - VirtualAllocEx, VirtualProtectEx, MEM_COMMIT, MEM_RESERVE, PAGE_NOACCESS, - PAGE_PROTECTION_FLAGS, PAGE_READWRITE, + MapViewOfFileNuma2, VirtualProtectEx, PAGE_NOACCESS, PAGE_PROTECTION_FLAGS, PAGE_READWRITE, }; +use windows::Win32::System::SystemServices::NUMA_NO_PREFERRED_NODE; use windows::Win32::System::Threading::{ CreateProcessA, CREATE_SUSPENDED, PROCESS_INFORMATION, STARTUPINFOA, }; @@ -79,7 +79,7 @@ const NUMBER_OF_SURROGATE_PROCESSES: usize = 512; /// There is, however, another API (WHvMapGpaRange2) that has a second /// parameter which is a handle to a process. This process merely has to exist, /// the memory being mapped from the host to the virtual machine is -/// allocated/freed in this process using VirtualAllocEx/VirtualFreeEx. +/// allocated/freed in this process using CreateFileMapping/MapViewOfFile. /// Memory for the HyperVisor partition is copied to and from the host process /// from/into the surrogate process in Sandbox before and after the VCPU is run. /// @@ -145,27 +145,47 @@ impl SurrogateProcessManager { &self, raw_size: usize, raw_source_address: *const c_void, + mmap_file_handle: HandleWrapper, ) -> Result { - let process_handle: HANDLE = self.process_receiver.recv()?.into(); + let surrogate_process_handle: HANDLE = self.process_receiver.recv()?.into(); + let mapping_file_handle: HANDLE = mmap_file_handle.into(); - // allocate memory + // Allocate the memory by creating a view over the memory mapped file + + // Use MapViewOfFile2 to map memoy into the surrogate process, the MapViewOfFile2 API is implemented in as an inline function in a windows header file + // (see https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile2#remarks) so we use the same API it uses in the header file here instead of + // MapViewOfFile2 which does not exist in the rust crate (see https://github.com/microsoft/windows-rs/issues/2595) let allocated_address = unsafe { - VirtualAllocEx( - process_handle, + MapViewOfFileNuma2( + mapping_file_handle, + surrogate_process_handle, + 0, Some(raw_source_address), raw_size, - MEM_COMMIT | MEM_RESERVE, - PAGE_READWRITE, + 0, + PAGE_READWRITE.0, + NUMA_NO_PREFERRED_NODE, ) }; - if allocated_address.is_null() { + + if allocated_address.Value.is_null() { log_then_return!( - "VirtualAllocEx failed for mem address {:?}", + "MapViewOfFileNuma2 failed for mem address {:?}", raw_source_address ); } - // set up guard page + if allocated_address.Value as *const c_void != raw_source_address { + log_then_return!( + "Address Mismatch Allocated: {:?} Requested: {:?}", + allocated_address.Value, + raw_source_address + ); + } + + // set up guard pages + + // If the following calls to VirtualProtectEx are changed make sure to update the calls to VirtualProtect in shared_mem.rs let mut unused_out_old_prot_flags = PAGE_PROTECTION_FLAGS(0); @@ -173,7 +193,7 @@ impl SurrogateProcessManager { let first_guard_page_start = raw_source_address; if let Err(e) = unsafe { VirtualProtectEx( - process_handle, + surrogate_process_handle, first_guard_page_start, PAGE_SIZE_USIZE, PAGE_NOACCESS, @@ -187,7 +207,7 @@ impl SurrogateProcessManager { let last_guard_page_start = unsafe { raw_source_address.add(raw_size - PAGE_SIZE_USIZE) }; if let Err(e) = unsafe { VirtualProtectEx( - process_handle, + surrogate_process_handle, last_guard_page_start, PAGE_SIZE_USIZE, PAGE_NOACCESS, @@ -197,7 +217,10 @@ impl SurrogateProcessManager { log_then_return!(WindowsAPIError(e.clone())); } - Ok(SurrogateProcess::new(allocated_address, process_handle)) + Ok(SurrogateProcess::new( + allocated_address.Value, + surrogate_process_handle, + )) } /// Returns a surrogate process to the pool of surrogate processes. @@ -391,15 +414,17 @@ mod tests { use std::thread; use std::time::{Duration, Instant}; + use hyperlight_common::mem::PAGE_SIZE_USIZE; use rand::{thread_rng, Rng}; use serial_test::serial; - use windows::Win32::Foundation::BOOL; + use windows::Win32::Foundation::{CloseHandle, BOOL, HANDLE, INVALID_HANDLE_VALUE}; use windows::Win32::System::Diagnostics::ToolHelp::{ CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS, }; use windows::Win32::System::JobObjects::IsProcessInJob; use windows::Win32::System::Memory::{ - VirtualAlloc, VirtualFree, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, + CreateFileMappingA, MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, PAGE_READWRITE, + SEC_COMMIT, }; use super::*; @@ -423,13 +448,29 @@ mod tests { // surrogate process, make sure we actually got one, // then put it back for p in 0..NUMBER_OF_SURROGATE_PROCESSES { - let allocated_address = unsafe { - VirtualAlloc(None, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE) + let dwmaximumsizehigh = 0; + let dwmaximumsizelow = (size & 0xFFFFFFFF) as u32; + let handle = unsafe { + CreateFileMappingA( + INVALID_HANDLE_VALUE, // Causes the page file to be used as the backing store + None, + PAGE_READWRITE | SEC_COMMIT, + dwmaximumsizehigh, + dwmaximumsizelow, + PCSTR::null(), + ) + .unwrap() }; + + let addr = unsafe { MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, 0) }; + let timer = Instant::now(); let surrogate_process = { - let res = surrogate_process_manager - .get_surrogate_process(size, allocated_address)?; + let res = surrogate_process_manager.get_surrogate_process( + size, + addr.Value, + HandleWrapper::from(handle), + )?; let elapsed = timer.elapsed(); // Print out the time it took to get the process if its greater than 150ms (this is just to allow us to see that threads are blocking on the process queue) if (elapsed.as_millis() as u64) > 150 { @@ -454,10 +495,11 @@ mod tests { // dropping the surrogate process, as we do in the line // below, will return it to the surrogate process manager drop(surrogate_process); - unsafe { - let res = VirtualFree(allocated_address, 0, MEM_RELEASE); - assert!(res.is_ok()) - } + let res = unsafe { UnmapViewOfFile(addr) }; + assert!(res.is_ok(), "Failed to UnmapViewOfFile: {:?}", res.err()); + + let res = unsafe { CloseHandle(handle) }; + assert!(res.is_ok(), "Failed to CloseHandle: {:?}", res.err()); } Ok(()) }); @@ -516,7 +558,11 @@ mod tests { let mem = ExclusiveSharedMemory::new(SIZE).unwrap(); let process = mgr - .get_surrogate_process(mem.raw_mem_size(), mem.raw_ptr() as *mut c_void) + .get_surrogate_process( + mem.raw_mem_size(), + mem.raw_ptr() as *mut c_void, + HandleWrapper::from(mem.get_mmap_file_handle()), + ) .unwrap(); let buffer = vec![0u8; SIZE]; diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index dd5749cf9..993f68679 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -809,7 +809,7 @@ impl SandboxMemoryManager { mod tests { use hyperlight_testing::rust_guest_as_pathbuf; use serde_json::to_string; - #[cfg(target_os = "windows")] + #[cfg(all(target_os = "windows", inprocess))] use serial_test::serial; use super::SandboxMemoryManager; @@ -848,7 +848,7 @@ mod tests { } } - #[cfg(target_os = "windows")] + #[cfg(all(target_os = "windows", inprocess))] #[test] #[serial] fn load_guest_binary_using_load_library() { diff --git a/src/hyperlight_host/src/mem/shared_mem.rs b/src/hyperlight_host/src/mem/shared_mem.rs index 559c99f43..2bc4be31d 100644 --- a/src/hyperlight_host/src/mem/shared_mem.rs +++ b/src/hyperlight_host/src/mem/shared_mem.rs @@ -17,13 +17,21 @@ limitations under the License. use std::any::type_name; use std::ffi::c_void; use std::io::Error; +#[cfg(target_os = "linux")] use std::ptr::null_mut; use std::sync::{Arc, RwLock}; use hyperlight_common::mem::PAGE_SIZE_USIZE; use tracing::{instrument, Span}; #[cfg(target_os = "windows")] -use windows::Win32::System::Memory::{VirtualAlloc, MEM_COMMIT, PAGE_EXECUTE_READWRITE}; +use windows::core::PCSTR; +#[cfg(target_os = "windows")] +use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}; +#[cfg(target_os = "windows")] +use windows::Win32::System::Memory::{ + CreateFileMappingA, MapViewOfFile, UnmapViewOfFile, VirtualProtect, FILE_MAP_ALL_ACCESS, + MEMORY_MAPPED_VIEW_ADDRESS, PAGE_EXECUTE_READWRITE, PAGE_PROTECTION_FLAGS, PAGE_READWRITE, +}; #[cfg(target_os = "windows")] use crate::HyperlightError::{MemoryRequestTooBig, WindowsAPIError}; @@ -82,6 +90,8 @@ macro_rules! generate_writer { pub struct HostMapping { ptr: *mut u8, size: usize, + #[cfg(target_os = "windows")] + handle: HANDLE, } impl Drop for HostMapping { @@ -95,10 +105,19 @@ impl Drop for HostMapping { } #[cfg(target_os = "windows")] fn drop(&mut self) { - use windows::Win32::System::Memory::{VirtualFree, MEM_DECOMMIT}; + let mem_mapped_address = MEMORY_MAPPED_VIEW_ADDRESS { + Value: self.ptr as *mut c_void, + }; + if let Err(e) = unsafe { UnmapViewOfFile(mem_mapped_address) } { + tracing::error!( + "Failed to drop HostMapping (UnmapViewOfFile failed): {:?}", + e + ); + } - if let Err(e) = unsafe { VirtualFree(self.ptr as *mut c_void, self.size, MEM_DECOMMIT) } { - tracing::error!("Failed to free shared memory (VirtualFree failed): {:?}", e); + let file_handle: HANDLE = self.handle; + if let Err(e) = unsafe { CloseHandle(file_handle) } { + tracing::error!("Failed to drop HostMapping (CloseHandle failed): {:?}", e); } } } @@ -369,7 +388,9 @@ impl ExclusiveSharedMemory { #[cfg(target_os = "windows")] #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub fn new(min_size_bytes: usize) -> Result { - use windows::Win32::System::Memory::PAGE_READWRITE; + #[cfg(inprocess)] + use windows::Win32::System::Memory::FILE_MAP_EXECUTE; + use windows::Win32::System::Memory::{PAGE_NOACCESS, PAGE_PROTECTION_FLAGS}; use crate::HyperlightError::MemoryAllocationFailed; @@ -394,16 +415,67 @@ impl ExclusiveSharedMemory { return Err(MemoryRequestTooBig(total_size, isize::MAX as usize)); } - // allocate the memory + let mut dwmaximumsizehigh = 0; + let mut dwmaximumsizelow = 0; + + if std::mem::size_of::() == 8 { + dwmaximumsizehigh = (total_size >> 32) as u32; + dwmaximumsizelow = (total_size & 0xFFFFFFFF) as u32; + } + + // Allocate the memory use CreateFileMapping instead of VirtualAlloc + // This allows us to map the memory into the surrogate process using MapViewOfFile2 + let handle = unsafe { + CreateFileMappingA( + INVALID_HANDLE_VALUE, + None, + PAGE_READWRITE, + dwmaximumsizehigh, + dwmaximumsizelow, + PCSTR::null(), + )? + }; + + #[cfg(inprocess)] let addr = - unsafe { VirtualAlloc(Some(null_mut()), total_size, MEM_COMMIT, PAGE_READWRITE) }; - if addr.is_null() { + unsafe { MapViewOfFile(handle, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE, 0, 0, 0) }; + #[cfg(not(inprocess))] + let addr = unsafe { MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, 0) }; + if addr.Value.is_null() { log_then_return!(MemoryAllocationFailed( Error::last_os_error().raw_os_error() )); } - // TODO protect the guard pages + // Set the first and last pages to be guard pages + + let mut unused_out_old_prot_flags = PAGE_PROTECTION_FLAGS(0); + + // If the following calls to VirtualProtect are changed make sure to update the calls to VirtualProtectEx in surrogate_process_manager.rs + + let first_guard_page_start = addr.Value; + if let Err(e) = unsafe { + VirtualProtect( + first_guard_page_start, + PAGE_SIZE_USIZE, + PAGE_NOACCESS, + &mut unused_out_old_prot_flags, + ) + } { + log_then_return!(WindowsAPIError(e.clone())); + } + + let last_guard_page_start = unsafe { addr.Value.add(total_size - PAGE_SIZE_USIZE) }; + if let Err(e) = unsafe { + VirtualProtect( + last_guard_page_start, + PAGE_SIZE_USIZE, + PAGE_NOACCESS, + &mut unused_out_old_prot_flags, + ) + } { + log_then_return!(WindowsAPIError(e.clone())); + } Ok(Self { // HostMapping is only non-Send/Sync because raw pointers @@ -416,8 +488,9 @@ impl ExclusiveSharedMemory { // is not pointless as the lint suggests. #[allow(clippy::arc_with_non_send_sync)] region: Arc::new(HostMapping { - ptr: addr as *mut u8, + ptr: addr.Value as *mut u8, size: total_size, + handle, }), }) } @@ -425,8 +498,6 @@ impl ExclusiveSharedMemory { pub(super) fn make_memory_executable(&self) -> Result<()> { #[cfg(target_os = "windows")] { - use windows::Win32::System::Memory::{VirtualProtect, PAGE_PROTECTION_FLAGS}; - let mut _old_flags = PAGE_PROTECTION_FLAGS::default(); if let Err(e) = unsafe { VirtualProtect( @@ -472,7 +543,7 @@ impl ExclusiveSharedMemory { /// must be properly aligned. /// /// The rules on validity are still somewhat unspecified, but we - /// assume that the result of our calls to mmap/VirtualAlloc may + /// assume that the result of our calls to mmap/CreateFileMappings may /// be considered a single "allocated object". The use of /// non-atomic accesses is alright from a Safe Rust standpoint, /// because SharedMemoryBuilder is not Sync. @@ -482,7 +553,7 @@ impl ExclusiveSharedMemory { /// Again, the exact provenance restrictions on what is /// considered to be initialized values are unclear, but we make /// sure to use mmap(MAP_ANONYMOUS) and - /// VirtualAlloc(MEM_COMMIT), so the pages in question are + /// CreateFileMapping(SEC_COMMIT), so the pages in question are /// zero-initialized, which we hope counts for u8. /// - The memory referenced by the returned slice must not be /// accessed through any other pointer (not derived from the @@ -585,6 +656,12 @@ impl ExclusiveSharedMemory { }, ) } + + /// Gets the file handle of the shared memory region for this Sandbox + #[cfg(target_os = "windows")] + pub fn get_mmap_file_handle(&self) -> HANDLE { + self.region.handle + } } /// A trait that abstracts over the particular kind of SharedMemory, diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index bf462a701..4ca951d57 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -364,7 +364,28 @@ mod tests { None, ); - // in process should only be enabled with the inprocess feature and on debug builds, and requires windows + // debug mode should fail with an elf executable + assert!(sbox.is_err()); + + let simple_guest_path = simple_guest_exe_as_string().unwrap(); + let sbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_path.clone()), + None, + Some(SandboxRunOptions::RunInProcess(false)), + None, + ); + + // in process should only be enabled with the inprocess feature and on debug builds + assert_eq!(sbox.is_ok(), cfg!(all(inprocess))); + + let sbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_path.clone()), + None, + Some(SandboxRunOptions::RunInProcess(true)), + None, + ); + + // debug mode should succeed with a PE executable on windows with inprocess enabled assert_eq!(sbox.is_ok(), cfg!(all(inprocess, target_os = "windows"))); } @@ -592,7 +613,14 @@ mod tests { } #[cfg(target_os = "windows")] { - let _ = mgr_res.unwrap(); + #[cfg(inprocess)] + { + assert!(mgr_res.is_ok()) + } + #[cfg(not(inprocess))] + { + assert!(mgr_res.is_err()) + } } } diff --git a/src/tests/rust_guests/callbackguest/Cargo.lock b/src/tests/rust_guests/callbackguest/Cargo.lock index b2d029e52..b07307997 100644 --- a/src/tests/rust_guests/callbackguest/Cargo.lock +++ b/src/tests/rust_guests/callbackguest/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.5" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" dependencies = [ "shlex", ]