Skip to main content

hyperlight_host/hypervisor/virtual_machine/
mod.rs

1/*
2Copyright 2025 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::fmt::Debug;
18use std::sync::OnceLock;
19
20use tracing::{Span, instrument};
21
22#[cfg(gdb)]
23use crate::hypervisor::gdb::DebugError;
24use crate::hypervisor::regs::{
25    CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
26};
27use crate::mem::memory_region::MemoryRegion;
28#[cfg(feature = "trace_guest")]
29use crate::sandbox::trace::TraceContext as SandboxTraceContext;
30
31/// KVM (Kernel-based Virtual Machine) functionality (linux)
32#[cfg(kvm)]
33pub(crate) mod kvm;
34/// MSHV (Microsoft Hypervisor) functionality (linux)
35#[cfg(mshv3)]
36pub(crate) mod mshv;
37/// WHP (Windows Hypervisor Platform) functionality (windows)
38#[cfg(target_os = "windows")]
39pub(crate) mod whp;
40
41/// Shared x86-64 helpers for hardware interrupt support (MSHV and WHP)
42#[cfg(feature = "hw-interrupts")]
43pub(crate) mod x86_64;
44
45static AVAILABLE_HYPERVISOR: OnceLock<Option<HypervisorType>> = OnceLock::new();
46
47/// Returns which type of hypervisor is available, if any
48pub fn get_available_hypervisor() -> &'static Option<HypervisorType> {
49    AVAILABLE_HYPERVISOR.get_or_init(|| {
50        cfg_if::cfg_if! {
51            if #[cfg(all(kvm, mshv3))] {
52                // If both features are enabled, we need to determine hypervisor at runtime.
53                // Currently /dev/kvm and /dev/mshv cannot exist on the same machine, so the first one
54                // that works is guaranteed to be correct.
55                if mshv::is_hypervisor_present() {
56                    Some(HypervisorType::Mshv)
57                } else if kvm::is_hypervisor_present() {
58                    Some(HypervisorType::Kvm)
59                } else {
60                    None
61                }
62            } else if #[cfg(kvm)] {
63                if kvm::is_hypervisor_present() {
64                    Some(HypervisorType::Kvm)
65                } else {
66                    None
67                }
68            } else if #[cfg(mshv3)] {
69                if mshv::is_hypervisor_present() {
70                    Some(HypervisorType::Mshv)
71                } else {
72                    None
73                }
74            } else if #[cfg(target_os = "windows")] {
75                if whp::is_hypervisor_present() {
76                    Some(HypervisorType::Whp)
77                } else {
78                    None
79                }
80            } else {
81                None
82            }
83        }
84    })
85}
86
87/// Returns `true` if a suitable hypervisor is available.
88/// If this returns `false`, no hypervisor-backed sandboxes can be created.
89#[instrument(skip_all, parent = Span::current())]
90pub fn is_hypervisor_present() -> bool {
91    get_available_hypervisor().is_some()
92}
93
94/// The hypervisor types available for the current platform
95#[derive(PartialEq, Eq, Debug, Copy, Clone)]
96pub(crate) enum HypervisorType {
97    #[cfg(kvm)]
98    Kvm,
99
100    #[cfg(mshv3)]
101    Mshv,
102
103    #[cfg(target_os = "windows")]
104    Whp,
105}
106
107/// Minimum XSAVE buffer size: 512 bytes legacy region + 64 bytes header.
108/// Only used by MSHV and WHP which use compacted XSAVE format and need to
109/// validate buffer size before accessing XCOMP_BV.
110#[cfg(any(mshv3, target_os = "windows"))]
111pub(crate) const XSAVE_MIN_SIZE: usize = 576;
112
113/// Standard XSAVE buffer size (4KB) used by KVM and MSHV.
114/// WHP queries the required size dynamically.
115#[cfg(all(any(kvm, mshv3), test, not(feature = "nanvix-unstable")))]
116pub(crate) const XSAVE_BUFFER_SIZE: usize = 4096;
117
118// Compiler error if no hypervisor type is available (not applicable on aarch64 yet)
119#[cfg(not(any(kvm, mshv3, target_os = "windows", target_arch = "aarch64")))]
120compile_error!(
121    "No hypervisor type is available for the current platform. Please enable either the `kvm` or `mshv3` cargo feature."
122);
123
124/// The various reasons a VM's vCPU can exit
125pub(crate) enum VmExit {
126    /// The vCPU has exited due to a debug event (usually breakpoint)
127    #[cfg(gdb)]
128    Debug {
129        #[cfg(target_arch = "x86_64")]
130        dr6: u64,
131        #[cfg(target_arch = "x86_64")]
132        exception: u32,
133    },
134    /// The vCPU has halted
135    Halt(),
136    /// The vCPU has issued a write to the given port with the given value
137    IoOut(u16, Vec<u8>),
138    /// The vCPU tried to read from the given (unmapped) addr
139    MmioRead(u64),
140    /// The vCPU tried to write to the given (unmapped) addr
141    MmioWrite(u64),
142    /// The vCPU execution has been cancelled
143    Cancelled(),
144    /// The vCPU has exited for a reason that is not handled by Hyperlight
145    Unknown(String),
146    /// The operation should be retried, for example this can happen on Linux where a call to run the CPU can return EAGAIN
147    #[cfg_attr(
148        any(target_os = "windows", feature = "hw-interrupts"),
149        expect(
150            dead_code,
151            reason = "Retry() is never constructed on Windows or with hw-interrupts (EAGAIN causes continue instead)"
152        )
153    )]
154    Retry(),
155}
156
157/// VM error
158#[derive(Debug, Clone, thiserror::Error)]
159pub enum VmError {
160    #[error("Failed to create vm: {0}")]
161    CreateVm(#[from] CreateVmError),
162    #[cfg(gdb)]
163    #[error("Debug operation failed: {0}")]
164    Debug(#[from] DebugError),
165    #[error("Map memory operation failed: {0}")]
166    MapMemory(#[from] MapMemoryError),
167    #[error("Register operation failed: {0}")]
168    Register(#[from] RegisterError),
169    #[error("Failed to run vcpu: {0}")]
170    RunVcpu(#[from] RunVcpuError),
171    #[error("Unmap memory operation failed: {0}")]
172    UnmapMemory(#[from] UnmapMemoryError),
173}
174
175/// Create VM error
176#[derive(Debug, Clone, thiserror::Error)]
177pub enum CreateVmError {
178    #[error("VCPU creation failed: {0}")]
179    CreateVcpuFd(HypervisorError),
180    #[error("VM creation failed: {0}")]
181    CreateVmFd(HypervisorError),
182    #[error("Hypervisor is not available: {0}")]
183    HypervisorNotAvailable(HypervisorError),
184    #[error("Initialize VM failed: {0}")]
185    InitializeVm(HypervisorError),
186    #[error("Set Partition Property failed: {0}")]
187    SetPartitionProperty(HypervisorError),
188    #[cfg(target_os = "windows")]
189    #[error("Surrogate process creation failed: {0}")]
190    SurrogateProcess(String),
191}
192
193/// RunVCPU error
194#[derive(Debug, Clone, thiserror::Error)]
195pub enum RunVcpuError {
196    #[error("Failed to decode message type: {0}")]
197    DecodeIOMessage(u32),
198    #[cfg(gdb)]
199    #[error("Failed to get DR6 debug register: {0}")]
200    GetDr6(HypervisorError),
201    #[error("Increment RIP failed: {0}")]
202    IncrementRip(HypervisorError),
203    #[error("Parse GPA access info failed")]
204    ParseGpaAccessInfo,
205    #[error("Unknown error: {0}")]
206    Unknown(HypervisorError),
207}
208
209/// Register error
210#[derive(Debug, Clone, thiserror::Error)]
211pub enum RegisterError {
212    #[error("Failed to get registers: {0}")]
213    GetRegs(HypervisorError),
214    #[error("Failed to set registers: {0}")]
215    SetRegs(HypervisorError),
216    #[error("Failed to get FPU registers: {0}")]
217    GetFpu(HypervisorError),
218    #[error("Failed to set FPU registers: {0}")]
219    SetFpu(HypervisorError),
220    #[error("Failed to get special registers: {0}")]
221    GetSregs(HypervisorError),
222    #[error("Failed to set special registers: {0}")]
223    SetSregs(HypervisorError),
224    #[error("Failed to get debug registers: {0}")]
225    GetDebugRegs(HypervisorError),
226    #[error("Failed to set debug registers: {0}")]
227    SetDebugRegs(HypervisorError),
228    #[error("Failed to get xsave: {0}")]
229    GetXsave(HypervisorError),
230    #[error("Failed to set xsave: {0}")]
231    SetXsave(HypervisorError),
232    #[error("Xsave size mismatch: expected {expected} bytes, got {actual}")]
233    XsaveSizeMismatch {
234        /// Expected size in bytes
235        expected: u32,
236        /// Actual size in bytes
237        actual: u32,
238    },
239    #[error("Invalid xsave alignment")]
240    InvalidXsaveAlignment,
241    #[cfg(target_os = "windows")]
242    #[error("Failed to get xsave size: {0}")]
243    GetXsaveSize(#[from] HypervisorError),
244    #[cfg(target_os = "windows")]
245    #[error("Failed to convert WHP registers: {0}")]
246    ConversionFailed(String),
247}
248
249/// Map memory error
250#[derive(Debug, Clone, thiserror::Error)]
251pub enum MapMemoryError {
252    #[cfg(target_os = "windows")]
253    #[error("Address conversion failed: {0}")]
254    AddressConversion(std::num::TryFromIntError),
255    #[error("Hypervisor error: {0}")]
256    Hypervisor(HypervisorError),
257    #[cfg(target_os = "windows")]
258    #[error("Invalid memory region flags: {0}")]
259    InvalidFlags(String),
260    #[cfg(target_os = "windows")]
261    #[error("Failed to load API '{api_name}': {source}")]
262    LoadApi {
263        api_name: &'static str,
264        source: windows_result::Error,
265    },
266    #[cfg(target_os = "windows")]
267    #[error("Operation not supported: {0}")]
268    NotSupported(String),
269    #[cfg(target_os = "windows")]
270    #[error("Surrogate process creation failed: {0}")]
271    SurrogateProcess(String),
272}
273
274/// Unmap memory error
275#[derive(Debug, Clone, thiserror::Error)]
276pub enum UnmapMemoryError {
277    #[error("Hypervisor error: {0}")]
278    Hypervisor(HypervisorError),
279}
280
281/// Implementation-specific Hypervisor error
282#[derive(Debug, Clone, thiserror::Error)]
283pub enum HypervisorError {
284    #[cfg(kvm)]
285    #[error("KVM error: {0}")]
286    KvmError(#[from] kvm_ioctls::Error),
287    #[cfg(mshv3)]
288    #[error("MSHV error: {0}")]
289    MshvError(#[from] mshv_ioctls::MshvError),
290    #[cfg(target_os = "windows")]
291    #[error("Windows error: {0}")]
292    WindowsError(#[from] windows_result::Error),
293}
294
295/// Trait for single-vCPU VMs. Provides a common interface for basic VM operations.
296/// Abstracts over differences between KVM, MSHV and WHP implementations.
297pub(crate) trait VirtualMachine: Debug + Send {
298    /// Map memory region into this VM
299    ///
300    /// # Safety
301    /// The caller must ensure that the memory region is valid and points to valid memory,
302    /// and lives long enough for the VM to use it.
303    /// The caller must ensure that the given u32 is not already mapped, otherwise previously mapped
304    /// memory regions may be overwritten.
305    /// The memory region must not overlap with an existing region, and depending on platform, must be aligned to page boundaries.
306    unsafe fn map_memory(
307        &mut self,
308        region: (u32, &MemoryRegion),
309    ) -> std::result::Result<(), MapMemoryError>;
310
311    /// Unmap memory region from this VM that has previously been mapped using `map_memory`.
312    fn unmap_memory(
313        &mut self,
314        region: (u32, &MemoryRegion),
315    ) -> std::result::Result<(), UnmapMemoryError>;
316
317    /// Runs the vCPU until it exits.
318    /// Note: this function emits traces spans for guests
319    /// and the span setup is called right before the run virtual processor call of each hypervisor
320    fn run_vcpu(
321        &mut self,
322        #[cfg(feature = "trace_guest")] tc: &mut SandboxTraceContext,
323    ) -> std::result::Result<VmExit, RunVcpuError>;
324
325    /// Get regs
326    #[allow(dead_code)]
327    fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError>;
328    /// Set regs
329    fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError>;
330    /// Get fpu regs
331    #[allow(dead_code)]
332    fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError>;
333    /// Set fpu regs
334    fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError>;
335    /// Get special regs
336    #[allow(dead_code)]
337    fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
338    /// Set special regs
339    fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError>;
340    /// Get the debug registers of the vCPU
341    #[allow(dead_code)]
342    fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
343    /// Set the debug registers of the vCPU
344    fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
345
346    /// Get xsave
347    #[allow(dead_code)]
348    fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
349    /// Reset xsave to default state
350    fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
351    /// Set xsave - only used for tests
352    #[cfg(test)]
353    #[cfg(not(feature = "nanvix-unstable"))]
354    fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
355
356    /// Get partition handle
357    #[cfg(target_os = "windows")]
358    fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
359}
360
361#[cfg(test)]
362mod tests {
363
364    #[test]
365    // TODO: add support for testing on WHP
366    #[cfg(target_os = "linux")]
367    fn is_hypervisor_present() {
368        use std::path::Path;
369
370        cfg_if::cfg_if! {
371            if #[cfg(all(kvm, mshv3))] {
372                assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
373            } else if #[cfg(kvm)] {
374                assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present());
375            } else if #[cfg(mshv3)] {
376                assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
377            } else {
378                assert!(!super::is_hypervisor_present());
379            }
380        }
381    }
382}