From b50ffaf334ffc4aa32ae3491c33478613c8e1bd0 Mon Sep 17 00:00:00 2001 From: tonic Date: Wed, 17 Jun 2026 12:12:25 +0800 Subject: [PATCH] x86_64: lock IA32_FEATURE_CONTROL with VMXON enabled at boot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guest hypervisors (Windows Hyper-V, WSL2) check IA32_FEATURE_CONTROL at boot and refuse VMXON unless firmware has set the lock bit with VMXON-outside-SMX enabled — otherwise they report "virtualization not enabled in firmware". rust-hypervisor-firmware never touched this MSR, so a Windows guest saw VMX in CPUID (VMMonitorModeExtensions=True) but VirtualizationFirmwareEnabled=False, and Hyper-V launch failed with Event 41 "Either VMX not present or not enabled in BIOS". Nested virtualization was therefore unusable. Set the MSR the way real firmware does: when CPUID reports VMX, lock IA32_FEATURE_CONTROL with VMXON-outside-SMX enabled, before handing control to the OS. Skipped when the lock bit is already set (idempotent) and when VMX is absent (AMD / nested off), so AMD and non-nested guests are unaffected. Verified on a Cloud Hypervisor Windows 11 guest: VirtualizationFirmwareEnabled flips False to True and the guest boots normally. --- src/arch/x86_64/mod.rs | 1 + src/arch/x86_64/vmx.rs | 22 ++++++++++++++++++++++ src/main.rs | 1 + 3 files changed, 24 insertions(+) create mode 100644 src/arch/x86_64/vmx.rs diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 0d2d5788..8a227d78 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -7,3 +7,4 @@ pub mod gdt; pub mod layout; pub mod paging; pub mod sse; +pub mod vmx; diff --git a/src/arch/x86_64/vmx.rs b/src/arch/x86_64/vmx.rs new file mode 100644 index 00000000..56ff9b9b --- /dev/null +++ b/src/arch/x86_64/vmx.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 + +use core::arch::x86_64::__cpuid; +use x86_64::registers::model_specific::Msr; + +const IA32_FEATURE_CONTROL: u32 = 0x3a; +const FEATURE_CONTROL_LOCKED: u64 = 1 << 0; +const FEATURE_CONTROL_VMXON_OUTSIDE_SMX: u64 = 1 << 2; +const CPUID_1_ECX_VMX: u32 = 1 << 5; + +// A guest hypervisor (Hyper-V / WSL2) refuses VMXON unless firmware locked IA32_FEATURE_CONTROL with VMXON enabled; real BIOS does this at boot, so we must too or nested virt is unusable despite VMX in CPUID. +pub fn enable_feature_control() { + let has_vmx = unsafe { __cpuid(1) }.ecx & CPUID_1_ECX_VMX != 0; + if !has_vmx { + return; + } + let mut msr = Msr::new(IA32_FEATURE_CONTROL); + let current = unsafe { msr.read() }; + if current & FEATURE_CONTROL_LOCKED == 0 { + unsafe { msr.write(current | FEATURE_CONTROL_VMXON_OUTSIDE_SMX | FEATURE_CONTROL_LOCKED) }; + } +} diff --git a/src/main.rs b/src/main.rs index 5928863d..8a6ee198 100644 --- a/src/main.rs +++ b/src/main.rs @@ -185,6 +185,7 @@ pub extern "C" fn rust64_start(#[cfg(not(feature = "coreboot"))] pvh_info: &pvh: arch::x86_64::sse::enable_sse(); arch::x86_64::paging::setup(); + arch::x86_64::vmx::enable_feature_control(); #[cfg(feature = "coreboot")] let info = &coreboot::StartInfo::default();