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/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" 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/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/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()), + } + } } } 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)?; 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) { 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 {