From 7832401816c5902b013630b05de6e5638b463e68 Mon Sep 17 00:00:00 2001 From: Souradeep Chakrabarti Date: Tue, 31 Mar 2026 06:18:27 +0000 Subject: [PATCH] hypervisor: Add GHCB CPUID, MSR and TERM_REQ handlers When booting an SEV-SNP guest VM using IGVM with -pvalidate_opt 1 (lazy page acceptance), the guest kernel's #VC exception handler may issue VMGEXIT with SVM_EXIT_CPUID (0x72) or SVM_EXIT_MSR (0x7c) exit codes via the GHCB page protocol. The hypervisor had no handlers for these exit codes, causing the guest's #VC handler to fail and trigger sev_es_terminate(), which sends GHCB_MSR_TERM_REQ (0x100). The hypervisor then panicked on the unhandled 0x100 operation. Add the following handlers to the GHCB VMGEXIT processing: - SVM_EXIT_CPUID (0x72): Read function/index/xcr0/xss from the GHCB page and return CPUID results via get_cpuid_values(). - SVM_EXIT_MSR (0x7c): Handle MSR read (RDMSR) and write (WRMSR) requests from the guest via the GHCB page protocol. - GHCB_MSR_TERM_REQ (0x100): Decode reason_set and reason_val from the GHCB MSR and return an error instead of panicking, allowing graceful error propagation. Testing: Reproducer (on Azure DC16as_cc_v5, /dev/mshv): cloud-hypervisor --cpus boot=1,nested=off --memory size=512M \ --disk path=osdisk.img path=cloudinit \ --net "tap=,mac=12:34:56:78:90:06,ip=192.168.6.1,mask=255.255.255.128" \ --serial null --console pty \ --api-socket /tmp/ch.sock \ --igvm /igvm_files/linux-ttyS0.bin \ --host-data --platform sev_snp=on -v Before fix: thread 'vcpu0' panicked at hypervisor/src/mshv/mod.rs:1207:30: Unsupported VMGEXIT operation: 100 After fix: VM boots successfully to login prompt with no panics. All virtio devices (console, rng, disks) activate normally. No regression risk for non-SEV-SNP: all new code is within the HVMSG_X64_SEV_VMGEXIT_INTERCEPT handler which is only reached for SEV-SNP guests. Signed-off-by: Souradeep Chakrabarti --- hypervisor/src/mshv/mod.rs | 101 +++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 00cc4a684..8623531c5 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -13,6 +13,8 @@ use anyhow::anyhow; #[cfg(target_arch = "x86_64")] use arc_swap::ArcSwap; #[cfg(feature = "sev_snp")] +use log::error; +#[cfg(feature = "sev_snp")] use log::info; use log::{debug, warn}; use mshv_bindings::*; @@ -85,6 +87,12 @@ use crate::{CpuState, IoEventAddress, IrqRoutingEntry, MpState}; pub const PAGE_SHIFT: usize = 12; +// SVM exit codes not yet defined in mshv-bindings (AMD APM Vol 2, Table 15-7) +#[cfg(feature = "sev_snp")] +const SVM_EXITCODE_CPUID: u32 = 0x72; +#[cfg(feature = "sev_snp")] +const SVM_EXITCODE_MSR: u32 = 0x7c; + #[cfg(target_arch = "x86_64")] impl From for ClockData { fn from(d: MshvClockData) -> Self { @@ -1199,11 +1207,104 @@ impl cpu::Vcpu for MshvVcpu { // Clear the SW_EXIT_INFO1 register to indicate no error self.clear_swexit_info1()?; } + SVM_EXITCODE_CPUID => { + // SAFETY: Accessing fields from the mapped GHCB page + let cpuid_fn = unsafe { (*ghcb).rax } as u32; + // SAFETY: Accessing fields from the mapped GHCB page + let cpuid_idx = unsafe { (*ghcb).rcx } as u32; + // SAFETY: Accessing fields from the mapped GHCB page + let xcr0 = unsafe { (*ghcb).xfem }; + // SAFETY: Accessing fields from the mapped GHCB page + let xss = unsafe { (*ghcb).xss }; + debug!("GHCB CPUID: fn=0x{cpuid_fn:x} idx=0x{cpuid_idx:x}"); + + let cpuid_result = self + .fd + .get_cpuid_values(cpuid_fn, cpuid_idx, xcr0, xss) + .unwrap_or([0u32; 4]); + + set_svm_field_u64_ptr!(ghcb, rax, cpuid_result[0] as u64); + set_svm_field_u64_ptr!(ghcb, rbx, cpuid_result[1] as u64); + set_svm_field_u64_ptr!(ghcb, rcx, cpuid_result[2] as u64); + set_svm_field_u64_ptr!(ghcb, rdx, cpuid_result[3] as u64); + + self.clear_swexit_info1()?; + } + SVM_EXITCODE_MSR => { + let exit_info1 = + info.__bindgen_anon_2.__bindgen_anon_1.sw_exit_info1; + // SAFETY: Accessing fields from the mapped GHCB page + let msr_index = unsafe { (*ghcb).rcx } as u32; + let is_write = exit_info1 & 1 != 0; + + if is_write { + // SAFETY: Accessing fields from the mapped GHCB page + let msr_lo = unsafe { (*ghcb).rax } as u32; + // SAFETY: Accessing fields from the mapped GHCB page + let msr_hi = unsafe { (*ghcb).rdx } as u32; + let msr_val = ((msr_hi as u64) << 32) | (msr_lo as u64); + debug!( + "GHCB MSR WRITE: index=0x{msr_index:x} val=0x{msr_val:x}" + ); + let entry = msr_entry { + index: msr_index, + data: msr_val, + ..Default::default() + }; + let msr_entries = MsrEntries::from_entries(&[entry]) + .map_err(|e| { + cpu::HypervisorCpuError::RunVcpu(e.into()) + })?; + self.fd.set_msrs(&msr_entries).map_err(|e| { + cpu::HypervisorCpuError::RunVcpu(e.into()) + })?; + } else { + let entry = msr_entry { + index: msr_index, + ..Default::default() + }; + let mut msr_entries = MsrEntries::from_entries(&[entry]) + .map_err(|e| { + cpu::HypervisorCpuError::RunVcpu(e.into()) + })?; + self.fd.get_msrs(&mut msr_entries).map_err(|e| { + cpu::HypervisorCpuError::RunVcpu(e.into()) + })?; + let msr_slice = msr_entries.as_slice(); + if msr_slice.is_empty() { + return Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( + "get_msrs returned no entries for index 0x{msr_index:x}" + ))); + } + let msr_val = msr_slice[0].data; + debug!( + "GHCB MSR READ: index=0x{msr_index:x} val=0x{msr_val:x}" + ); + set_svm_field_u64_ptr!(ghcb, rax, msr_val & 0xFFFFFFFF); + set_svm_field_u64_ptr!(ghcb, rdx, msr_val >> 32); + } + + self.clear_swexit_info1()?; + } _ => { panic!("GHCB_INFO_NORMAL: Unhandled exit code: {exit_code:0x}") } } } + GHCB_INFO_SHUTDOWN_REQUEST => { + let ghcb_msr_val = { info.ghcb_msr }; + let reason_set = (ghcb_msr_val >> 12) & 0xf; + let reason_val = (ghcb_msr_val >> 16) & 0xff; + error!( + "GHCB_MSR_TERM_REQ: Guest terminated! \ + ghcb_msr=0x{ghcb_msr_val:x}, \ + reason_set={reason_set}, reason_val={reason_val}" + ); + return Err(cpu::HypervisorCpuError::RunVcpu(anyhow!( + "Guest requested termination via GHCB_MSR_TERM_REQ \ + (reason_set={reason_set}, reason_val={reason_val})" + ))); + } _ => panic!("Unsupported VMGEXIT operation: {ghcb_op:0x}"), }