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