Skip to main content

nub_host_kvm/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
35/// Shared x86-64 helpers for hardware interrupt support (MSHV and WHP)
36#[cfg(feature = "hw-interrupts")]
37pub(crate) mod x86_64;
38
39static AVAILABLE_HYPERVISOR: OnceLock<Option<HypervisorType>> = OnceLock::new();
40
41/// Returns which type of hypervisor is available, if any
42pub fn get_available_hypervisor() -> &'static Option<HypervisorType> {
43    AVAILABLE_HYPERVISOR.get_or_init(|| {
44        #[cfg(kvm)]
45        {
46            if kvm::is_hypervisor_present() {
47                Some(HypervisorType::Kvm)
48            } else {
49                None
50            }
51        }
52        #[cfg(not(kvm))]
53        {
54            None
55        }
56    })
57}
58
59/// Returns `true` if a suitable hypervisor is available.
60/// If this returns `false`, no hypervisor-backed sandboxes can be created.
61#[instrument(skip_all, parent = Span::current())]
62pub fn is_hypervisor_present() -> bool {
63    get_available_hypervisor().is_some()
64}
65
66/// The hypervisor types available for the current platform
67#[derive(PartialEq, Eq, Debug, Copy, Clone)]
68pub(crate) enum HypervisorType {
69    #[cfg(kvm)]
70    Kvm,
71}
72
73// Compiler error if no hypervisor type is available (not applicable on aarch64 yet)
74#[cfg(not(any(kvm, target_arch = "aarch64")))]
75compile_error!(
76    "No hypervisor type is available for the current platform. Please enable the `kvm` cargo feature."
77);
78
79/// The various reasons a VM's vCPU can exit
80pub(crate) enum VmExit {
81    /// The vCPU has exited due to a debug event (usually breakpoint)
82    #[cfg(gdb)]
83    Debug {
84        #[cfg(target_arch = "x86_64")]
85        dr6: u64,
86        #[cfg(target_arch = "x86_64")]
87        exception: u32,
88    },
89    /// The vCPU has halted
90    Halt(),
91    /// The vCPU has issued a write to the given port with the given value
92    IoOut(u16, Vec<u8>),
93    /// The vCPU tried to read from the given (unmapped) addr
94    MmioRead(u64),
95    /// The vCPU tried to write to the given (unmapped) addr
96    MmioWrite(u64),
97    /// The vCPU execution has been cancelled
98    Cancelled(),
99    /// The vCPU has exited for a reason that is not handled by Hyperlight
100    Unknown(String),
101    /// The operation should be retried, for example this can happen on Linux where a call to run the CPU can return EAGAIN
102    #[cfg_attr(
103        any(target_os = "windows", feature = "hw-interrupts"),
104        expect(
105            dead_code,
106            reason = "Retry() is never constructed on Windows or with hw-interrupts (EAGAIN causes continue instead)"
107        )
108    )]
109    Retry(),
110}
111
112/// VM error
113#[derive(Debug, Clone, thiserror::Error)]
114pub enum VmError {
115    #[error("Failed to create vm: {0}")]
116    CreateVm(#[from] CreateVmError),
117    #[cfg(gdb)]
118    #[error("Debug operation failed: {0}")]
119    Debug(#[from] DebugError),
120    #[error("Map memory operation failed: {0}")]
121    MapMemory(#[from] MapMemoryError),
122    #[error("Register operation failed: {0}")]
123    Register(#[from] RegisterError),
124    #[error("Failed to run vcpu: {0}")]
125    RunVcpu(#[from] RunVcpuError),
126    #[error("Unmap memory operation failed: {0}")]
127    UnmapMemory(#[from] UnmapMemoryError),
128}
129
130/// Create VM error
131#[derive(Debug, Clone, thiserror::Error)]
132pub enum CreateVmError {
133    #[error("VCPU creation failed: {0}")]
134    CreateVcpuFd(HypervisorError),
135    #[error("VM creation failed: {0}")]
136    CreateVmFd(HypervisorError),
137    #[error("Hypervisor is not available: {0}")]
138    HypervisorNotAvailable(HypervisorError),
139    #[error("Initialize VM failed: {0}")]
140    InitializeVm(HypervisorError),
141    #[error("Set Partition Property failed: {0}")]
142    SetPartitionProperty(HypervisorError),
143    #[cfg(target_os = "windows")]
144    #[error("Surrogate process creation failed: {0}")]
145    SurrogateProcess(String),
146}
147
148/// RunVCPU error
149#[derive(Debug, Clone, thiserror::Error)]
150pub enum RunVcpuError {
151    #[error("Failed to decode message type: {0}")]
152    DecodeIOMessage(u32),
153    #[cfg(gdb)]
154    #[error("Failed to get DR6 debug register: {0}")]
155    GetDr6(HypervisorError),
156    #[error("Increment RIP failed: {0}")]
157    IncrementRip(HypervisorError),
158    #[error("Parse GPA access info failed")]
159    ParseGpaAccessInfo,
160    #[error("Unknown error: {0}")]
161    Unknown(HypervisorError),
162}
163
164/// Register error
165#[derive(Debug, Clone, thiserror::Error)]
166pub enum RegisterError {
167    #[error("Failed to get registers: {0}")]
168    GetRegs(HypervisorError),
169    #[error("Failed to set registers: {0}")]
170    SetRegs(HypervisorError),
171    #[error("Failed to get FPU registers: {0}")]
172    GetFpu(HypervisorError),
173    #[error("Failed to set FPU registers: {0}")]
174    SetFpu(HypervisorError),
175    #[error("Failed to get special registers: {0}")]
176    GetSregs(HypervisorError),
177    #[error("Failed to set special registers: {0}")]
178    SetSregs(HypervisorError),
179    #[error("Failed to get debug registers: {0}")]
180    GetDebugRegs(HypervisorError),
181    #[error("Failed to set debug registers: {0}")]
182    SetDebugRegs(HypervisorError),
183    #[error("Failed to get xsave: {0}")]
184    GetXsave(HypervisorError),
185    #[error("Failed to set xsave: {0}")]
186    SetXsave(HypervisorError),
187    #[error("Xsave size mismatch: expected {expected} bytes, got {actual}")]
188    XsaveSizeMismatch {
189        /// Expected size in bytes
190        expected: u32,
191        /// Actual size in bytes
192        actual: u32,
193    },
194    #[error("Invalid xsave alignment")]
195    InvalidXsaveAlignment,
196    #[cfg(target_os = "windows")]
197    #[error("Failed to get xsave size: {0}")]
198    GetXsaveSize(#[from] HypervisorError),
199    #[cfg(target_os = "windows")]
200    #[error("Failed to convert WHP registers: {0}")]
201    ConversionFailed(String),
202}
203
204/// Map memory error
205#[derive(Debug, Clone, thiserror::Error)]
206pub enum MapMemoryError {
207    #[cfg(target_os = "windows")]
208    #[error("Address conversion failed: {0}")]
209    AddressConversion(std::num::TryFromIntError),
210    #[error("Hypervisor error: {0}")]
211    Hypervisor(HypervisorError),
212    #[cfg(target_os = "windows")]
213    #[error("Invalid memory region flags: {0}")]
214    InvalidFlags(String),
215    #[cfg(target_os = "windows")]
216    #[error("Failed to load API '{api_name}': {source}")]
217    LoadApi {
218        api_name: &'static str,
219        source: windows_result::Error,
220    },
221    #[cfg(target_os = "windows")]
222    #[error("Operation not supported: {0}")]
223    NotSupported(String),
224    #[cfg(target_os = "windows")]
225    #[error("Surrogate process creation failed: {0}")]
226    SurrogateProcess(String),
227}
228
229/// Unmap memory error
230#[derive(Debug, Clone, thiserror::Error)]
231pub enum UnmapMemoryError {
232    #[error("Hypervisor error: {0}")]
233    Hypervisor(HypervisorError),
234}
235
236/// Implementation-specific Hypervisor error
237#[derive(Debug, Clone, thiserror::Error)]
238pub enum HypervisorError {
239    #[cfg(kvm)]
240    #[error("KVM error: {0}")]
241    KvmError(#[from] kvm_ioctls::Error),
242}
243
244/// Trait for single-vCPU VMs. Provides a common interface for basic VM operations.
245/// Abstracts over differences between KVM, MSHV and WHP implementations.
246pub(crate) trait VirtualMachine: Debug + Send {
247    /// Map memory region into this VM
248    ///
249    /// # Safety
250    /// The caller must ensure that the memory region is valid and points to valid memory,
251    /// and lives long enough for the VM to use it.
252    /// The caller must ensure that the given u32 is not already mapped, otherwise previously mapped
253    /// memory regions may be overwritten.
254    /// The memory region must not overlap with an existing region, and depending on platform, must be aligned to page boundaries.
255    unsafe fn map_memory(
256        &mut self,
257        region: (u32, &MemoryRegion),
258    ) -> std::result::Result<(), MapMemoryError>;
259
260    /// Runs the vCPU until it exits.
261    /// Note: this function emits traces spans for guests
262    /// and the span setup is called right before the run virtual processor call of each hypervisor
263    fn run_vcpu(
264        &mut self,
265        #[cfg(feature = "trace_guest")] tc: &mut SandboxTraceContext,
266    ) -> std::result::Result<VmExit, RunVcpuError>;
267
268    /// Get regs
269    #[allow(dead_code)]
270    fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError>;
271    /// Set regs
272    fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError>;
273    /// Get fpu regs
274    #[allow(dead_code)]
275    fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError>;
276    /// Set fpu regs
277    fn set_fpu(&self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError>;
278    /// Get special regs
279    #[allow(dead_code)]
280    fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
281    /// Set special regs
282    fn set_sregs(&self, sregs: &CommonSpecialRegisters) -> std::result::Result<(), RegisterError>;
283    /// Get the debug registers of the vCPU
284    #[allow(dead_code)]
285    fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
286
287    /// Get xsave
288    #[allow(dead_code)]
289    fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
290
291    /// Get partition handle
292    #[cfg(target_os = "windows")]
293    fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
294}
295
296#[cfg(test)]
297mod tests {
298    #[test]
299    #[cfg(kvm)]
300    fn is_hypervisor_present() {
301        use std::path::Path;
302        assert_eq!(
303            Path::new("/dev/kvm").exists(),
304            super::is_hypervisor_present()
305        );
306    }
307}