vmm: add KVM SEV-SNP support to IGVM loader

Adapt the IGVM loader to work with both MSHV and KVM backends, which
differ in page type constants, CPUID page layout, and VMSA handling.

Abstract page types into a PageTypeConfig struct populated at runtime
from the detected hypervisor, replacing hardcoded mshv_bindings constants.

Apply the VMSA register state to each vCPU via setup_sev_snp_regs(),
translating SevSelector attributes to KVM segment format using a bitfield
decoder.

KVM's SNP launch path sanitizes certain CPUID bits that could lead to
an insecure guest. If the VMM sets these bits, KVM rejects the CPUID
page import on the first attempt, requiring a retry with the
firmware-corrected values.

Pre-clear the known problematic bits before import to avoid the
reject-and-retry cycle:

- Leaf 0x1, ECX bit 24: TSC_DEADLINE (filtered by KVM)
- Leaf 0x7, EBX bit 1: SGX (filtered by KVM)
- Leaf 0x7, EDX: clear entirely (contains speculative features)
- Leaf 0x80000008, EBX bit 25: filtered by KVM
- Leaf 0x80000021, ECX: clear entirely

This keeps the CPUID page stable across launch updates and avoids
noisy error logs from the retry path.

Co-authored-by: Keith Adler <kadler@cloudflare.com>
Signed-off-by: Keith Adler <kadler@cloudflare.com>
Co-authored-by: Alex Orozco <aorozco@google.com>
Signed-off-by: Alex Orozco <aorozco@google.com>
Co-authored-by: Dylan Reid <dgreid@fb.com>
Signed-off-by: Dylan Reid <dgreid@fb.com>
Signed-off-by: Ruben Hakobyan <hruben@meta.com>
This commit is contained in:
Ruben Hakobyan
2026-04-07 17:30:24 -07:00
committed by Rob Bradford
parent b5ddcdc74a
commit 75ed2c9f90
5 changed files with 435 additions and 60 deletions

View File

@@ -591,6 +591,10 @@ pub trait Vcpu: Send + Sync {
fn set_sev_control_register(&self, _vmsa_pfn: u64) -> Result<()> {
unimplemented!()
}
#[cfg(feature = "sev_snp")]
fn setup_sev_snp_regs(&self, _vmsa: igvm::snp_defs::SevVmsa) -> Result<()> {
unimplemented!()
}
///
/// Sets the value of GIC redistributor address

View File

@@ -145,10 +145,61 @@ ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a);
#[cfg(feature = "sev_snp")]
use igvm_defs::PAGE_SIZE_4K;
#[cfg(feature = "sev_snp")]
use kvm_bindings::{KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes};
use kvm_bindings::{
KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes, kvm_segment as Segment,
};
use vm_memory::GuestAddress;
#[cfg(feature = "sev_snp")]
use x86_64::sev;
// Hardcoded GPA of a bootloader and VMSA page for KVM
// TODO: Derive these from the IGVM file's PageData/SnpVpContext directives
// instead of using fixed constants, to support arbitrary bootloader layouts.
pub const BOOTLOADER_START: GuestAddress = GuestAddress(0xffc0_0000);
pub const BOOTLOADER_SIZE: usize = 0x40_0000; // 4 MiB
pub const KVM_VMSA_PAGE_ADDRESS: GuestAddress = GuestAddress(0xffff_ffff_f000);
pub const KVM_VMSA_PAGE_SIZE: usize = 0x1000; // 4 KiB
#[cfg(feature = "sev_snp")]
#[bitfield_struct::bitfield(u32)]
#[derive(PartialEq, Eq)]
/// AMD VMCB segment attributes
/// linux/arch/x86/include/asm/svm.h
pub struct SegAccess {
#[bits(4)]
pub seg_type: u8,
pub s_code_data: bool,
#[bits(2)]
pub priv_level: u8,
pub present: bool,
pub available: bool,
pub l_64bit: bool,
pub db_size_32: bool,
pub granularity: bool,
#[bits(20)]
_reserved: u32,
}
#[cfg(feature = "sev_snp")]
fn make_segment(sev_selector: igvm::snp_defs::SevSelector) -> Segment {
let flags = SegAccess::from_bits(sev_selector.attrib.into());
Segment {
base: sev_selector.base,
limit: sev_selector.limit,
selector: sev_selector.selector,
type_: flags.seg_type(),
s: flags.s_code_data() as u8,
dpl: flags.priv_level(),
present: flags.present() as u8,
avl: flags.available() as u8,
db: flags.db_size_32() as u8,
g: flags.granularity() as u8,
l: flags.l_64bit() as u8,
unusable: 0,
..Default::default()
}
}
#[cfg(feature = "tdx")]
const KVM_EXIT_TDX: u32 = 50;
#[cfg(feature = "tdx")]
@@ -3238,6 +3289,81 @@ impl cpu::Vcpu for KvmVcpu {
Ok(_) => Ok(()),
}
}
#[cfg(feature = "sev_snp")]
fn set_sev_control_register(&self, _vmsa_pfn: u64) -> cpu::Result<()> {
Ok(())
}
#[cfg(feature = "sev_snp")]
fn setup_sev_snp_regs(&self, vmsa: igvm::snp_defs::SevVmsa) -> cpu::Result<()> {
let mut sregs = self
.fd
.get_sregs()
.map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::GetSpecialRegs(e.into()))?;
sregs.cs = make_segment(vmsa.cs);
sregs.ds = make_segment(vmsa.ds);
sregs.es = make_segment(vmsa.es);
sregs.fs = make_segment(vmsa.fs);
sregs.gs = make_segment(vmsa.gs);
sregs.ss = make_segment(vmsa.ss);
sregs.tr = make_segment(vmsa.tr);
sregs.ldt = make_segment(vmsa.ldtr);
sregs.cr0 = vmsa.cr0;
sregs.cr4 = vmsa.cr4;
sregs.cr3 = vmsa.cr3;
sregs.efer = vmsa.efer;
sregs.idt.base = vmsa.idtr.base;
sregs.idt.limit = vmsa
.idtr
.limit
.try_into()
.map_err(|e: std::num::TryFromIntError| {
cpu::HypervisorCpuError::SetSpecialRegs(anyhow!(e))
})?;
sregs.gdt.base = vmsa.gdtr.base;
sregs.gdt.limit = vmsa
.gdtr
.limit
.try_into()
.map_err(|e: std::num::TryFromIntError| {
cpu::HypervisorCpuError::SetSpecialRegs(anyhow!(e))
})?;
self.fd
.set_sregs(&sregs)
.map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::SetSpecialRegs(e.into()))?;
let mut regs = self
.fd
.get_regs()
.map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::GetRegister(e.into()))?;
regs.rip = vmsa.rip;
regs.rdx = vmsa.rdx;
regs.rflags = vmsa.rflags;
regs.rsp = vmsa.rsp;
regs.rax = vmsa.rax;
regs.rbx = vmsa.rbx;
regs.rcx = vmsa.rcx;
regs.rbp = vmsa.rbp;
regs.rsi = vmsa.rsi;
regs.rdi = vmsa.rdi;
regs.r8 = vmsa.r8;
regs.r9 = vmsa.r9;
regs.r10 = vmsa.r10;
regs.r11 = vmsa.r11;
regs.r12 = vmsa.r12;
regs.r13 = vmsa.r13;
regs.r14 = vmsa.r14;
regs.r15 = vmsa.r15;
self.fd
.set_regs(&regs)
.map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::SetRegister(e.into()))?;
Ok(())
}
}
impl KvmVcpu {