From 0e8fbf1ca34ec8d284ab4afb1c71395a2066c236 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Wed, 11 Dec 2024 15:13:08 +0000 Subject: [PATCH 1/5] Remove unnecessary check for hypervisor present Check for hypervisor presence is done in the hypervisor_handler module before trying to create the driver Signed-off-by: Simon Davies --- src/hyperlight_host/src/hypervisor/hyperv_linux.rs | 3 --- src/hyperlight_host/src/hypervisor/hyperv_windows.rs | 11 +++-------- src/hyperlight_host/src/hypervisor/kvm.rs | 3 --- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs index b8a18303e..224428eb2 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_linux.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_linux.rs @@ -84,9 +84,6 @@ impl HypervLinuxDriver { rsp_ptr: GuestPtr, pml4_ptr: GuestPtr, ) -> Result { - if !is_hypervisor_present() { - log_then_return!("Hyper-V is not present on this system"); - } let mshv = Mshv::new()?; let pr = Default::default(); let vm_fd = mshv.create_vm_with_config(&pr)?; diff --git a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs index 5c5bbc604..5ce5e815d 100644 --- a/src/hyperlight_host/src/hypervisor/hyperv_windows.rs +++ b/src/hyperlight_host/src/hypervisor/hyperv_windows.rs @@ -35,16 +35,15 @@ use super::surrogate_process_manager::*; use super::windows_hypervisor_platform::{VMPartition, VMProcessor}; use super::wrappers::WHvFPURegisters; use super::{ - windows_hypervisor_platform as whp, 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, + 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, }; use crate::hypervisor::fpu::FP_CONTROL_WORD_DEFAULT; use crate::hypervisor::hypervisor_handler::HypervisorHandler; use crate::hypervisor::wrappers::WHvGeneralRegisters; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; use crate::mem::ptr::{GuestPtr, RawPtr}; -use crate::HyperlightError::{NoHypervisorFound, WindowsAPIError}; +use crate::HyperlightError::WindowsAPIError; use crate::{debug, log_then_return, new_error, Result}; /// A Hypervisor driver for HyperV-on-Windows. @@ -75,10 +74,6 @@ impl HypervWindowsDriver { entrypoint: u64, rsp: u64, ) -> Result { - if !whp::is_hypervisor_present() { - log_then_return!(NoHypervisorFound()); - } - // create and setup hypervisor partition let mut partition = VMPartition::new(1)?; diff --git a/src/hyperlight_host/src/hypervisor/kvm.rs b/src/hyperlight_host/src/hypervisor/kvm.rs index 991e1e24b..0d459827b 100644 --- a/src/hyperlight_host/src/hypervisor/kvm.rs +++ b/src/hyperlight_host/src/hypervisor/kvm.rs @@ -76,9 +76,6 @@ impl KVMDriver { entrypoint: u64, rsp: u64, ) -> Result { - if !is_hypervisor_present() { - log_then_return!("KVM is not present"); - }; let kvm = Kvm::new()?; let vm_fd = kvm.create_vm_with_type(0)?; From 63ceac01587e9c689addc4e2ba717bfebfb3afb0 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Thu, 12 Dec 2024 20:29:04 +0000 Subject: [PATCH 2/5] Check that Windows version is 11 or Server 2022 or later Adds a check when creating a new uninitialisedsandbox to ensure that we are running on at least Windows 11 or Windows Server 2022 Signed-off-by: Simon Davies --- Cargo.lock | 10 +++++++ README.md | 4 +-- src/hyperlight_host/Cargo.toml | 1 + .../src/sandbox/uninitialized.rs | 28 +++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9de89cd19..707a19636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1140,6 +1140,7 @@ dependencies = [ "windows", "windows-result", "windows-sys 0.59.0", + "windows-version", ] [[package]] @@ -3289,6 +3290,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-version" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" diff --git a/README.md b/README.md index 1e4562586..3b58a329d 100644 --- a/README.md +++ b/README.md @@ -174,8 +174,8 @@ the [./src/tests/rust_guests](./src/tests/rust_guests) directory for Rust guests You can run Hyperlight on: - [Linux with KVM][kvm]. -- [Windows with Windows Hypervisor Platform (WHP)][whp]. -- Windows Subsystem for Linux 2 ([WSL2][wsl2]) with KVM. +- [Windows with Windows Hypervisor Platform (WHP).][whp] - Note that you need Windows 11 / Windows Server 2022 or later to use hyperlight, if you are running on earlier versions of Windows then you should consider using our devcontainer on [GitHub codepsaces]((https://codespaces.new/hyperlight-dev/hyperlight)) or WSL2. +- Windows Subsystem for Linux 2 (see instructions [here](https://learn.microsoft.com/en-us/windows/wsl/install) for Windows client and [here](https://learn.microsoft.com/en-us/windows/wsl/install-on-server) for Windows Server) with KVM. - Azure Linux with mshv (note that you need mshv to be installed to use Hyperlight) After having an environment with a hypervisor setup, running the example has the following pre-requisites: diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index 4f17e0b90..d25062ff1 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -67,6 +67,7 @@ windows-sys = { version = "0.59", features = ["Win32"] } windows-result = "0.2" rust-embed = { version = "8.3.0", features = ["debug-embed", "include-exclude", "interpolate-folder-path"] } sha256 = "1.4.0" +windows-version = "0.1" [target.'cfg(unix)'.dependencies] seccompiler = { version = "0.4.0", optional = true } diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index 6ae2e1a0c..78997870e 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -142,6 +142,10 @@ impl UninitializedSandbox { ) -> Result { log_build_details(); + // hyperlight is only supported on Windows 11 and Windows Server 2022 and later + #[cfg(target_os = "windows")] + check_windows_version()?; + // If the guest binary is a file make sure it exists let guest_binary = match guest_binary { GuestBinary::FilePath(binary_path) => { @@ -303,6 +307,30 @@ impl UninitializedSandbox { } } } +// Check to see if the current version of Windows is supported +// Hyperlight is only supported on Windows 11 and Windows Server 2022 and later +#[cfg(target_os = "windows")] +fn check_windows_version() -> Result<()> { + use windows_version::{is_server, OsVersion}; + const WINDOWS_MAJOR: u32 = 10; + const WINDOWS_MINOR: u32 = 0; + const WINDOWS_PACK: u32 = 0; + + // Windows Server 2022 has version numbers 10.0.20348 or greater + if is_server() { + if OsVersion::current() < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 20348) + { + return Err(new_error!( + "Hyperlight Requires Windows Server 2022 or newer" + )); + } + } else if OsVersion::current() + < OsVersion::new(WINDOWS_MAJOR, WINDOWS_MINOR, WINDOWS_PACK, 22000) + { + return Err(new_error!("Hyperlight Requires Windows 11 or newer")); + } + Ok(()) +} #[cfg(test)] mod tests { From 983913540bcf8e87948962acc8221476ebed0cc8 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Thu, 12 Dec 2024 21:41:09 +0000 Subject: [PATCH 3/5] Dynamically resolve WHvMapGpaRange2 function Resolve the WHvMapGpaRange2 function dynamically so that we can load on earlier versions of windows that are not supported Signed-off-by: Simon Davies --- .../hypervisor/windows_hypervisor_platform.rs | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs b/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs index 505d8c00b..578687569 100644 --- a/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs +++ b/src/hyperlight_host/src/hypervisor/windows_hypervisor_platform.rs @@ -17,13 +17,16 @@ limitations under the License. use core::ffi::c_void; use tracing::{instrument, Span}; -use windows::Win32::Foundation::HANDLE; +use windows::core::s; +use windows::Win32::Foundation::{FreeLibrary, HANDLE}; use windows::Win32::System::Hypervisor::*; +use windows::Win32::System::LibraryLoader::*; +use windows_result::HRESULT; use super::wrappers::HandleWrapper; use crate::hypervisor::wrappers::{WHvFPURegisters, WHvGeneralRegisters, WHvSpecialRegisters}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; -use crate::Result; +use crate::{new_error, Result}; // We need to pass in a primitive array of register names/values // to WHvSetVirtualProcessorRegisters and rust needs to know array size @@ -90,8 +93,18 @@ impl VMPartition { process_handle: HandleWrapper, ) -> Result<()> { let process_handle: HANDLE = process_handle.into(); + // The function pointer to WHvMapGpaRange2 is resolved dynamically to allow us to detect + // when we are running on older versions of windows that do not support this API and + // return a more informative error message, rather than failing with an error about a missing entrypoint + let whvmapgparange2_func = unsafe { + match try_load_whv_map_gpa_range2() { + Ok(func) => func, + Err(e) => return Err(new_error!("Cant find API: {}", e)), + } + }; + regions.iter().try_for_each(|region| unsafe { - WHvMapGpaRange2( + let res = whvmapgparange2_func( self.0, process_handle, region.host_region.start as *const c_void, @@ -109,12 +122,59 @@ impl VMPartition { _ => panic!("Invalid flag"), }) .fold(WHvMapGpaRangeFlagNone, |acc, flag| acc | flag), // collect using bitwise OR, - ) + ); + if res.is_err() { + return Err(new_error!("Call to WHvMapGpaRange2 failed")); + } + Ok(()) })?; Ok(()) } } +// This function dynamically loads the WHvMapGpaRange2 function from the winhvplatform.dll +// WHvMapGpaRange2 only available on Windows 11 or Windows Server 2022 and later +// we do things this way to allow a user trying to load hyperlight on an older version of windows to +// get an error message saying that hyperlight requires a newer version of windows, rather than just failing +// with an error about a missing entrypoint +// This function should always succeed since before we get here we have already checked that the hypervisor is present and +// that we are on a supported version of windows. +type WHvMapGpaRange2Func = unsafe extern "cdecl" fn( + WHV_PARTITION_HANDLE, + HANDLE, + *const c_void, + u64, + u64, + WHV_MAP_GPA_RANGE_FLAGS, +) -> HRESULT; + +pub unsafe fn try_load_whv_map_gpa_range2() -> Result { + let library = unsafe { + LoadLibraryExA( + s!("winhvplatform.dll"), + None, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, + ) + }; + + if let Err(e) = library { + return Err(new_error!("{}", e)); + } + + let library = library.unwrap(); + + let address = unsafe { GetProcAddress(library, s!("WHvMapGpaRange2")) }; + + if address.is_none() { + unsafe { FreeLibrary(library)? }; + return Err(new_error!( + "Failed to find WHvMapGpaRange2 in winhvplatform.dll" + )); + } + + unsafe { Ok(std::mem::transmute_copy(&address)) } +} + impl Drop for VMPartition { #[instrument(skip_all, parent = Span::current(), level= "Trace")] fn drop(&mut self) { From 72ddd4d8cb81725e8617bf96cd1ede9a05c283c2 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Thu, 12 Dec 2024 21:43:24 +0000 Subject: [PATCH 4/5] Fixes an issue where errors do not get propogated correctly when the hypervisor handler thread returns an error. The hypervisor handler thread can return an error rather than send a message, in this case the error returned would be a timeout rather than the real error, this change ensures that we check for an error in the hypervisor handler thread when we time out and return that if it exists Signed-off-by: Simon Davies --- .../src/hypervisor/hypervisor_handler.rs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs b/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs index 8e1645cb5..96b6e8994 100644 --- a/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs +++ b/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs @@ -592,7 +592,29 @@ impl HypervisorHandler { HandlerMsg::Error(e) => Err(e), HandlerMsg::FinishedHypervisorHandlerAction => Ok(()), }, - Err(_) => Err(HyperlightError::HypervisorHandlerMessageReceiveTimedout()), + Err(_) => { + // If we have timed out it may be that the handler thread returned an error before it sent a message, so rather than just timeout here + // we will try and get the join handle for the thread and if it has finished check to see if it returned an error + // if it did then we will return that error, otherwise we will return the timeout error + // we need to take ownership of the handle to join it + match self + .execution_variables + .join_handle + .try_lock() + .map_err(|_| HyperlightError::HypervisorHandlerMessageReceiveTimedout())? + .take_if(|handle| handle.is_finished()) + { + Some(handle) => { + // If the thread has finished, we try to join it and return the error if it has one + let res = handle.join(); + if res.as_ref().is_ok_and(|inner_res| inner_res.is_err()) { + return Err(res.unwrap().unwrap_err()); + } + Err(HyperlightError::HypervisorHandlerMessageReceiveTimedout()) + } + None => Err(HyperlightError::HypervisorHandlerMessageReceiveTimedout()), + } + } } } From c3121aea93cab217f1a77b4c814464cc2a405728 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Tue, 17 Dec 2024 12:03:42 +0000 Subject: [PATCH 5/5] Increase MSRV to 1.80 This change uses take_if method on Option which is stable since 1.80 Signed-off-by: Simon Davies --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b73e6aff4..9e3d0b3ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ exclude = [ [workspace.package] version = "0.1.0" edition = "2021" -rust-version = "1.79.0" +rust-version = "1.80.0" license = "Apache-2.0" homepage = "https://github.com/hyperlight-dev/hyperlight" repository = "https://github.com/hyperlight-dev/hyperlight"