Skip to main content

hyperlight_host/hypervisor/virtual_machine/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use std::fmt::Debug;
5use std::sync::OnceLock;
6
7use tracing::{Span, instrument};
8
9#[cfg(gdb)]
10use crate::hypervisor::gdb::DebugError;
11use crate::hypervisor::regs::{
12    CommonDebugRegs, CommonFpu, CommonRegisters, CommonSpecialRegisters,
13};
14#[cfg(all(target_arch = "x86_64", any(mshv3, target_os = "windows")))]
15use crate::hypervisor::regs::{
16    MSR_MTRR_CAP, filterless_core_reset_candidates, hyperv_mtrr_reset_indices,
17};
18#[cfg(target_arch = "x86_64")]
19use crate::hypervisor::regs::{MsrEntry, is_resettable_msr};
20use crate::mem::memory_region::MemoryRegion;
21#[cfg(feature = "trace_guest")]
22use crate::sandbox::trace::TraceContext as SandboxTraceContext;
23
24/// Hypervisor.framework functionality (MacOS)
25#[cfg(hvf)]
26pub(crate) mod hvf;
27/// KVM (Kernel-based Virtual Machine) functionality (linux)
28#[cfg(kvm)]
29pub(crate) mod kvm;
30/// MSHV (Microsoft Hypervisor) functionality (linux)
31#[cfg(mshv3)]
32pub(crate) mod mshv;
33/// WHP (Windows Hypervisor Platform) functionality (windows)
34#[cfg(target_os = "windows")]
35pub(crate) mod whp;
36
37/// Shared x86-64 helpers for hardware interrupt support (MSHV and WHP)
38#[cfg(feature = "hw-interrupts")]
39pub(crate) mod x86_64;
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 if #[cfg(hvf)] {
77                if hvf::is_hypervisor_present() {
78                    Some(HypervisorType::Hvf)
79                } else {
80                    None
81                }
82            } else {
83                None
84            }
85        }
86    })
87}
88
89/// Returns `true` if a suitable hypervisor is available.
90/// If this returns `false`, no hypervisor-backed sandboxes can be created.
91#[instrument(skip_all, parent = Span::current())]
92pub fn is_hypervisor_present() -> bool {
93    get_available_hypervisor().is_some()
94}
95
96/// The hypervisor types available for the current platform
97#[derive(PartialEq, Eq, Debug, Copy, Clone)]
98pub(crate) enum HypervisorType {
99    #[cfg(kvm)]
100    Kvm,
101
102    #[cfg(mshv3)]
103    Mshv,
104
105    #[cfg(target_os = "windows")]
106    Whp,
107
108    #[cfg(hvf)]
109    Hvf,
110}
111
112/// Minimum XSAVE buffer size: 512 bytes legacy region + 64 bytes header.
113/// Only used by MSHV and WHP which use compacted XSAVE format and need to
114/// validate buffer size before accessing XCOMP_BV.
115#[cfg(all(target_arch = "x86_64", any(mshv3, target_os = "windows")))]
116pub(crate) const XSAVE_MIN_SIZE: usize = 576;
117
118/// Standard XSAVE buffer size (4KB) used by KVM and MSHV.
119/// WHP queries the required size dynamically.
120#[cfg(all(any(kvm, mshv3), test, not(target_arch = "aarch64")))]
121pub(crate) const XSAVE_BUFFER_SIZE: usize = 4096;
122
123/// Architectural XCR0 reset value. Only x87 state is enabled.
124#[cfg(target_arch = "x86_64")]
125pub(crate) const XCR0_RESET: u64 = 1;
126
127// Compiler error if no hypervisor type is available (not applicable on aarch64 yet)
128#[cfg(not(any(kvm, mshv3, target_os = "windows", target_arch = "aarch64")))]
129compile_error!(
130    "No hypervisor type is available for the current platform. Please enable either the `kvm` or `mshv3` cargo feature."
131);
132
133/// The various reasons a VM's vCPU can exit
134#[cfg_attr(target_os = "macos", allow(unused))]
135pub(crate) enum VmExit {
136    /// The vCPU has exited due to a debug event (usually breakpoint)
137    #[cfg(gdb)]
138    Debug {
139        #[cfg(target_arch = "x86_64")]
140        dr6: u64,
141        #[cfg(target_arch = "x86_64")]
142        exception: u32,
143    },
144    /// The vCPU has halted
145    Halt(),
146    /// The vCPU has issued a write to the given port with the given value
147    IoOut(u16, Vec<u8>),
148    /// The vCPU tried to read from the given (unmapped) addr
149    MmioRead(u64),
150    /// The vCPU tried to write to the given (unmapped) addr
151    MmioWrite(u64),
152    /// The vCPU execution has been cancelled
153    Cancelled(),
154    /// The vCPU has exited for a reason that is not handled by Hyperlight
155    Unknown(String),
156    /// The operation should be retried, for example this can happen on Linux where a call to run the CPU can return EAGAIN
157    #[cfg_attr(
158        any(target_os = "windows", feature = "hw-interrupts"),
159        expect(
160            dead_code,
161            reason = "Retry() is never constructed on Windows or with hw-interrupts (EAGAIN causes continue instead)"
162        )
163    )]
164    Retry(),
165}
166
167/// VM error
168#[derive(Debug, thiserror::Error)]
169pub enum VmError {
170    #[error("Failed to create vm: {0}")]
171    CreateVm(#[from] CreateVmError),
172    #[cfg(gdb)]
173    #[error("Debug operation failed: {0}")]
174    Debug(#[from] DebugError),
175    #[error("Map memory operation failed: {0}")]
176    MapMemory(#[from] MapMemoryError),
177    #[error("Register operation failed: {0}")]
178    Register(#[from] RegisterError),
179    #[error("Failed to run vcpu: {0}")]
180    RunVcpu(#[from] RunVcpuError),
181    #[error("Unmap memory operation failed: {0}")]
182    UnmapMemory(#[from] UnmapMemoryError),
183}
184
185/// Create VM error
186#[derive(Debug, Clone, thiserror::Error)]
187pub enum CreateVmError {
188    #[error("VCPU creation failed: {0}")]
189    CreateVcpuFd(HypervisorError),
190    #[error("VM creation failed: {0}")]
191    CreateVmFd(HypervisorError),
192    #[error("Hypervisor is not available: {0}")]
193    HypervisorNotAvailable(HypervisorError),
194    #[error("Initialize VM failed: {0}")]
195    InitializeVm(HypervisorError),
196    #[cfg(all(kvm, target_arch = "x86_64"))]
197    #[error("KVM MSR filtering requires KVM_CAP_X86_MSR_FILTER")]
198    MsrFilterNotSupported,
199    #[cfg(target_arch = "x86_64")]
200    #[error("MSR {msr:#x} cannot be declared as a guest MSR: {reason}")]
201    MsrNotDeclarable { msr: u32, reason: String },
202    #[cfg(target_arch = "x86_64")]
203    #[error("Failed to read IA32_MTRRCAP: {0}")]
204    GetMtrrCap(RegisterError),
205    #[cfg(target_arch = "x86_64")]
206    #[error("Guest-visible MTRRs cannot be reset: {0}")]
207    RequiredMtrrsNotResettable(RegisterError),
208    #[cfg(all(target_arch = "x86_64", any(mshv3, target_os = "windows")))]
209    #[error("Core reset MSR {msr:#x} is readable but not writable on this host")]
210    MsrNotResettable { msr: u32 },
211    #[cfg(target_arch = "x86_64")]
212    #[error("Guest exposes {advertised} variable MTRR pairs, expected at most {maximum}")]
213    UnexpectedVariableMtrrCount { advertised: u8, maximum: u8 },
214    #[cfg(all(kvm, target_arch = "x86_64"))]
215    #[error("Too many guest MSR filter ranges: {0}. Maximum is 16")]
216    TooManyMsrRanges(usize),
217    #[cfg(target_os = "windows")]
218    #[error("Get Partition Property failed: {0}")]
219    GetPartitionProperty(HypervisorError),
220    #[cfg(target_os = "windows")]
221    #[error("WHP exposes {advertised} processor feature banks, expected {expected}")]
222    UnexpectedProcessorFeatureBankCount { advertised: u32, expected: u32 },
223    #[error("Set Partition Property failed: {0}")]
224    SetPartitionProperty(HypervisorError),
225    #[cfg(target_os = "windows")]
226    #[error("Surrogate process creation failed: {0}")]
227    SurrogateProcess(String),
228}
229
230/// RunVCPU error
231#[derive(Debug, thiserror::Error)]
232pub enum RunVcpuError {
233    #[error("Failed to decode message type: {0}")]
234    DecodeIOMessage(u32),
235    #[cfg(gdb)]
236    #[error("Failed to get DR6 debug register: {0}")]
237    GetDr6(HypervisorError),
238    #[error("Increment RIP failed: {0}")]
239    IncrementRip(HypervisorError),
240    #[error("Parse GPA access info failed")]
241    ParseGpaAccessInfo,
242    #[cfg(target_arch = "aarch64")]
243    #[error("Flush MMIO pending state failed: {0}")]
244    FlushMmioPending(String),
245    #[cfg(hvf)]
246    #[error("HVF sync error: {0}")]
247    HvfSync(HvfSyncError),
248    #[error("Unknown error: {0}")]
249    Unknown(HypervisorError),
250}
251
252/// Register error
253#[derive(Debug, Clone, thiserror::Error)]
254pub enum RegisterError {
255    #[error("Failed to get registers: {0}")]
256    GetRegs(HypervisorError),
257    #[error("Failed to set registers: {0}")]
258    SetRegs(HypervisorError),
259    #[error("Failed to get FPU registers: {0}")]
260    GetFpu(HypervisorError),
261    #[error("Failed to set FPU registers: {0}")]
262    SetFpu(HypervisorError),
263    #[error("Failed to get special registers: {0}")]
264    GetSregs(HypervisorError),
265    #[error("Failed to set special registers: {0}")]
266    SetSregs(HypervisorError),
267    #[cfg(target_arch = "x86_64")]
268    #[error("Snapshot APIC_BASE {value:#x} enables unsupported x2APIC mode")]
269    InvalidSnapshotApicBase {
270        /// APIC_BASE value supplied by the snapshot.
271        value: u64,
272    },
273    #[error("Failed to get debug registers: {0}")]
274    GetDebugRegs(HypervisorError),
275    #[error("Failed to set debug registers: {0}")]
276    SetDebugRegs(HypervisorError),
277    #[error("Failed to get xsave: {0}")]
278    GetXsave(HypervisorError),
279    #[error("Failed to set xsave: {0}")]
280    SetXsave(HypervisorError),
281    #[cfg(target_arch = "x86_64")]
282    #[error("Failed to get XCRs: {0}")]
283    GetXcrs(HypervisorError),
284    #[cfg(target_arch = "x86_64")]
285    #[error("Failed to set XCRs: {0}")]
286    SetXcrs(HypervisorError),
287    #[cfg(target_arch = "x86_64")]
288    #[error("Hypervisor did not return XCR0")]
289    MissingXcr0,
290    #[error("Xsave size mismatch: expected {expected} bytes, got {actual}")]
291    XsaveSizeMismatch {
292        /// Expected size in bytes
293        expected: u32,
294        /// Actual size in bytes
295        actual: u32,
296    },
297    #[error("Invalid xsave alignment")]
298    InvalidXsaveAlignment,
299    #[cfg(target_arch = "x86_64")]
300    #[error("MSR operation not supported on this hypervisor")]
301    MsrsUnsupported,
302    #[cfg(target_arch = "x86_64")]
303    #[error("Failed to build MSR list: {0}")]
304    MsrBuild(String),
305    #[cfg(target_arch = "x86_64")]
306    #[error("Failed to get MSRs: {0}")]
307    GetMsrs(HypervisorError),
308    #[cfg(target_arch = "x86_64")]
309    #[error("Failed to set MSRs: {0}")]
310    SetMsrs(HypervisorError),
311    #[cfg(target_arch = "x86_64")]
312    #[error("Failed to set batched registers: {0}")]
313    SetBatchedRegisters(HypervisorError),
314    #[cfg(target_arch = "x86_64")]
315    #[error("Batched register writes are not supported")]
316    BatchedSetRegistersUnsupported,
317    #[cfg(target_arch = "x86_64")]
318    #[error("Snapshot MSR index {index:#x} is not in this VM's reset set")]
319    InvalidSnapshotMsrIndex {
320        /// Architectural MSR index supplied by the snapshot.
321        index: u32,
322    },
323    #[cfg(all(kvm, target_arch = "x86_64"))]
324    #[error("MSR batch short count: expected {expected}, applied {actual}")]
325    MsrShortCount {
326        /// Number of MSRs requested
327        expected: usize,
328        /// Number of MSRs actually applied before KVM stopped
329        actual: usize,
330    },
331    #[cfg(target_os = "windows")]
332    #[error("Failed to get xsave size: {0}")]
333    GetXsaveSize(#[from] HypervisorError),
334    #[cfg(target_os = "windows")]
335    #[error("Failed to convert WHP registers: {0}")]
336    ConversionFailed(String),
337}
338
339#[derive(Debug, thiserror::Error)]
340pub enum ResetVcpuError {
341    #[error("Single-operation vcpu reset not supported on this hypervisor")]
342    NotSupported,
343    #[error("Hypervisor operation failed: {0}")]
344    Hypervisor(HypervisorError),
345    #[error("Register operation failed: {0}")]
346    Register(#[from] RegisterError),
347    #[error("Operation failed: {0}")]
348    Unknown(String),
349}
350
351/// Map memory error
352#[derive(Debug, thiserror::Error)]
353pub enum MapMemoryError {
354    #[cfg(target_os = "windows")]
355    #[error("Address conversion failed: {0}")]
356    AddressConversion(std::num::TryFromIntError),
357    #[error("Hypervisor error: {0}")]
358    Hypervisor(HypervisorError),
359    #[cfg(target_os = "windows")]
360    #[error("Invalid memory region flags: {0}")]
361    InvalidFlags(String),
362    #[cfg(target_os = "windows")]
363    #[error("Failed to load API '{api_name}': {source}")]
364    LoadApi {
365        api_name: &'static str,
366        source: windows_result::Error,
367    },
368    #[cfg(target_os = "windows")]
369    #[error("Operation not supported: {0}")]
370    NotSupported(String),
371    #[cfg(target_os = "windows")]
372    #[error("Surrogate process creation failed: {0}")]
373    SurrogateProcess(String),
374}
375
376/// Unmap memory error
377#[derive(Debug, thiserror::Error)]
378pub enum UnmapMemoryError {
379    #[error("Hypervisor error: {0}")]
380    Hypervisor(HypervisorError),
381}
382
383/// Implementation-specific Hypervisor error
384#[derive(Debug, Clone, thiserror::Error)]
385pub enum HypervisorError {
386    #[cfg(test)]
387    #[error("Injected hypervisor error")]
388    Injected,
389    #[cfg(kvm)]
390    #[error("KVM error: {0}")]
391    KvmError(#[from] kvm_ioctls::Error),
392    #[cfg(mshv3)]
393    #[error("MSHV error: {0}")]
394    MshvError(#[from] mshv_ioctls::MshvError),
395    #[cfg(target_os = "windows")]
396    #[error("Windows error: {0}")]
397    WindowsError(#[from] windows_result::Error),
398    #[cfg(hvf)]
399    #[error("HVF error: {0}")]
400    HvfError(hvf::bindings::hv_return_t),
401}
402
403/// HVF-specific error synchronising vcpu state
404#[cfg(hvf)]
405#[derive(Debug, thiserror::Error)]
406pub enum MemorySpaceInstallError {
407    #[error("Failed to update VM/VCPU state: {0}")]
408    Hypervisor(#[from] HypervisorError),
409    #[error("Unexpected VCPU exit: {0:?}")]
410    UnexpectedExit(hvf::bindings::hv_vcpu_exit_t),
411    #[error("Failed to allocate ReadonlySharedMemory: {0}")]
412    SharedMemoryCreation(#[from] crate::mem::shared_mem::SharedMemoryError),
413}
414#[cfg(hvf)]
415#[derive(Debug, thiserror::Error)]
416pub enum HvfSyncError {
417    #[error("Error creating VCPU: {0}")]
418    CreateVcpu(HypervisorError),
419    #[error("Error resetting VCPU: {0}")]
420    ResetVcpu(HypervisorError),
421    #[error("Error reading/writing registers: {0}")]
422    Register(#[from] RegisterError),
423    #[error("Error updating memory space: {0}")]
424    MemorySpace(#[from] MemorySpaceInstallError),
425    #[error("Invariant violation: vcpu in unexpected sync state: {0}")]
426    SyncInvariant(String),
427}
428
429/// Trait for single-vCPU VMs. Provides a common interface for basic VM operations.
430/// Abstracts over differences between KVM, MSHV and WHP implementations.
431pub(crate) trait VirtualMachine: Debug + Send {
432    /// Map memory region into this VM
433    ///
434    /// # Safety
435    /// The caller must ensure that the memory region is valid and points to valid memory,
436    /// and lives long enough for the VM to use it.
437    /// The caller must ensure that the given u32 is not already mapped, otherwise previously mapped
438    /// memory regions may be overwritten.
439    /// The memory region must not overlap with an existing region, and depending on platform, must be aligned to page boundaries.
440    unsafe fn map_memory(
441        &mut self,
442        region: (u32, &MemoryRegion),
443    ) -> std::result::Result<(), MapMemoryError>;
444
445    /// Unmap memory region from this VM that has previously been mapped using `map_memory`.
446    fn unmap_memory(
447        &mut self,
448        region: (u32, &MemoryRegion),
449    ) -> std::result::Result<(), UnmapMemoryError>;
450
451    /// Runs the vCPU until it exits.
452    /// Note: this function emits traces spans for guests
453    /// and the span setup is called right before the run virtual processor call of each hypervisor
454    fn run_vcpu(
455        &mut self,
456        #[cfg(feature = "trace_guest")] tc: &mut SandboxTraceContext,
457    ) -> std::result::Result<VmExit, RunVcpuError>;
458
459    /// Get regs
460    #[allow(dead_code)]
461    fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError>;
462    /// Set regs
463    fn set_regs(&mut self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError>;
464    /// Get fpu regs
465    #[allow(dead_code)]
466    fn fpu(&self) -> std::result::Result<CommonFpu, RegisterError>;
467    /// Set fpu regs
468    fn set_fpu(&mut self, fpu: &CommonFpu) -> std::result::Result<(), RegisterError>;
469    /// Get special regs
470    #[allow(dead_code)]
471    fn sregs(&self) -> std::result::Result<CommonSpecialRegisters, RegisterError>;
472    /// Set special regs
473    fn set_sregs(
474        &mut self,
475        sregs: &CommonSpecialRegisters,
476    ) -> std::result::Result<(), RegisterError>;
477    /// Get the debug registers of the vCPU
478    #[allow(dead_code)]
479    fn debug_regs(&self) -> std::result::Result<CommonDebugRegs, RegisterError>;
480    /// Set the debug registers of the vCPU
481    #[allow(dead_code)]
482    fn set_debug_regs(&self, drs: &CommonDebugRegs) -> std::result::Result<(), RegisterError>;
483
484    /// Reads the requested MSRs.
485    #[cfg(target_arch = "x86_64")]
486    fn msrs(&self, indices: &[u32]) -> std::result::Result<Vec<MsrEntry>, RegisterError>;
487    /// Writes the supplied MSRs.
488    #[cfg(target_arch = "x86_64")]
489    fn set_msrs(&self, msrs: &[MsrEntry]) -> std::result::Result<(), RegisterError>;
490    /// Returns the MSRs whose state this backend must reset.
491    #[cfg(target_arch = "x86_64")]
492    fn msr_reset_indices(&self, guest_msrs: &[u32])
493    -> std::result::Result<Vec<u32>, CreateVmError>;
494
495    /// Get xsave
496    #[allow(dead_code)]
497    #[cfg(not(target_arch = "aarch64"))]
498    fn xsave(&self) -> std::result::Result<Vec<u8>, RegisterError>;
499    /// Reset xsave to default state
500    #[cfg(not(target_arch = "aarch64"))]
501    fn reset_xsave(&self) -> std::result::Result<(), RegisterError>;
502    /// Set xsave - only used for tests
503    #[cfg(test)]
504    #[cfg(not(target_arch = "aarch64"))]
505    fn set_xsave(&self, xsave: &[u32]) -> std::result::Result<(), RegisterError>;
506
507    #[cfg(all(test, target_arch = "x86_64"))]
508    fn xcr0(&self) -> std::result::Result<u64, RegisterError>;
509    #[cfg(target_arch = "x86_64")]
510    fn set_xcr0(&self, value: u64) -> std::result::Result<(), RegisterError>;
511
512    #[cfg(target_arch = "x86_64")]
513    fn can_batch_registers(&self) -> bool {
514        false
515    }
516    #[cfg(target_arch = "x86_64")]
517    fn set_batched_registers(
518        &mut self,
519        _regs: &CommonRegisters,
520        _debug_regs: &CommonDebugRegs,
521        _sregs: &CommonSpecialRegisters,
522        _xcr0: u64,
523        _msrs: &[MsrEntry],
524    ) -> std::result::Result<(), RegisterError> {
525        Err(RegisterError::BatchedSetRegistersUnsupported)
526    }
527
528    /// Single-operation vCPU reset
529    #[cfg(target_arch = "aarch64")]
530    fn can_reset_vcpu(&self) -> bool {
531        false
532    }
533    #[cfg(target_arch = "aarch64")]
534    fn reset_vcpu(&mut self) -> std::result::Result<(), ResetVcpuError> {
535        Err(ResetVcpuError::NotSupported)
536    }
537    /// Get partition handle
538    #[cfg(target_os = "windows")]
539    fn partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
540}
541
542/// Why an MSR failed the read-then-write-back probe that restore relies on.
543#[cfg(target_arch = "x86_64")]
544enum MsrProbe {
545    /// The host cannot read the MSR.
546    Unreadable,
547    /// The host reads the MSR but rejects writing the value back.
548    Unwritable,
549}
550
551/// Reads `msr` and writes the captured value back, the round trip restore
552/// replays. Success means the host can reset the MSR.
553#[cfg(target_arch = "x86_64")]
554fn probe_resettable(vm: &dyn VirtualMachine, msr: u32) -> Result<(), MsrProbe> {
555    let captured = vm.msrs(&[msr]).map_err(|_| MsrProbe::Unreadable)?;
556    vm.set_msrs(&captured).map_err(|_| MsrProbe::Unwritable)
557}
558
559/// Validates that each declared guest MSR is restorable: reset replays a
560/// captured value, so the host must read and write it.
561/// Rejects e.g. a variable MTRR above the host VCNT.
562#[cfg(target_arch = "x86_64")]
563pub(crate) fn validate_guest_msrs(
564    vm: &dyn VirtualMachine,
565    guest_msrs: &[u32],
566) -> std::result::Result<(), CreateVmError> {
567    for &msr in guest_msrs {
568        if !is_resettable_msr(msr) {
569            return Err(CreateVmError::MsrNotDeclarable {
570                msr,
571                reason: "MSR is not a resettable MSR".to_string(),
572            });
573        }
574        // A declared MSR is user-named, so either failure is a config error.
575        probe_resettable(vm, msr).map_err(|probe| CreateVmError::MsrNotDeclarable {
576            msr,
577            reason: match probe {
578                MsrProbe::Unreadable => "MSR cannot be read on this host",
579                MsrProbe::Unwritable => "MSR cannot be written on this host",
580            }
581            .to_string(),
582        })?;
583    }
584    Ok(())
585}
586
587/// Returns every guest-visible MTRR a filterless (MSHV/WHP) host must reset.
588#[cfg(all(target_arch = "x86_64", any(mshv3, target_os = "windows")))]
589pub(crate) fn mtrr_reset_indices(
590    vm: &dyn VirtualMachine,
591) -> std::result::Result<Vec<u32>, CreateVmError> {
592    let mtrr_cap = vm
593        .msrs(&[MSR_MTRR_CAP])
594        .map_err(CreateVmError::GetMtrrCap)?[0]
595        .value;
596    let indices = hyperv_mtrr_reset_indices(mtrr_cap)?;
597    vm.msrs(&indices)
598        .map_err(CreateVmError::RequiredMtrrsNotResettable)?;
599    Ok(indices)
600}
601
602/// Builds the reset index set required by a filterless Hyper-V backend.
603#[cfg(all(target_arch = "x86_64", any(mshv3, target_os = "windows")))]
604pub(crate) fn hyperv_msr_reset_indices(
605    vm: &dyn VirtualMachine,
606    guest_msrs: &[u32],
607) -> std::result::Result<Vec<u32>, CreateVmError> {
608    // MSR probing is expensive. Cache the common case with no additional guest MSRs.
609    // Default VMs in a process use the same partition configuration and MSR set.
610    static DEFAULT: OnceLock<Vec<u32>> = OnceLock::new();
611
612    // Cache hit: no guest MSRs, so the reset set is always the same.
613    if guest_msrs.is_empty()
614        && let Some(indices) = DEFAULT.get()
615    {
616        return Ok(indices.clone());
617    }
618
619    validate_guest_msrs(vm, guest_msrs)?;
620    let mut indices = filterless_core_reset_candidates()
621        .filter_map(|index| match probe_resettable(vm, index) {
622            // Readable and writable, so it joins the reset set.
623            Ok(()) => Some(Ok(index)),
624            // A read failure means the feature is absent, so nothing is retained. This is fine.
625            Err(MsrProbe::Unreadable) => None,
626            // A readable candidate must be writable, or restore cannot scrub it.
627            Err(MsrProbe::Unwritable) => Some(Err(CreateVmError::MsrNotResettable { msr: index })),
628        })
629        .collect::<Result<Vec<u32>, _>>()?;
630    // Guest-visible MTRRs, sized from the host MTRRCAP. mtrr_reset_indices
631    // read-probes them. Hyper-V stores MTRRs unconditionally, so a readable
632    // MTRR is always writable and needs no write probe.
633    indices.extend(mtrr_reset_indices(vm)?);
634    // The declared guest MSRs, validated above.
635    indices.extend(guest_msrs.iter().copied());
636    indices.sort_unstable();
637    indices.dedup();
638    // Populate the cache for future default VMs.
639    if guest_msrs.is_empty() {
640        // An error means another thread populated the same cache (harmless).
641        let _ = DEFAULT.set(indices.clone());
642    }
643    Ok(indices)
644}
645
646#[cfg(test)]
647mod tests {
648
649    #[test]
650    // TODO: add support for testing on WHP
651    #[cfg(target_os = "linux")]
652    fn is_hypervisor_present() {
653        use std::path::Path;
654
655        cfg_if::cfg_if! {
656            if #[cfg(all(kvm, mshv3))] {
657                assert_eq!(Path::new("/dev/kvm").exists() || Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
658            } else if #[cfg(kvm)] {
659                assert_eq!(Path::new("/dev/kvm").exists(), super::is_hypervisor_present());
660            } else if #[cfg(mshv3)] {
661                assert_eq!(Path::new("/dev/mshv").exists(), super::is_hypervisor_present());
662            } else {
663                assert!(!super::is_hypervisor_present());
664            }
665        }
666    }
667}