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 <hash> --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 <schakrabarti@microsoft.com>
This commit is contained in:
Souradeep Chakrabarti
2026-03-31 06:18:27 +00:00
committed by Rob Bradford
parent ff20f18364
commit 7832401816

View File

@@ -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<MshvClockData> 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}"),
}