From 24db5e1efd4c5db19bb5e9e86352622c01b59bb0 Mon Sep 17 00:00:00 2001 From: Ruben Hakobyan Date: Tue, 7 Apr 2026 16:43:32 -0700 Subject: [PATCH] hypervisor, vmm: Add support for KVM_SEV_SNP_LAUNCH_UPDATE Implement the KVM_SEV_SNP_LAUNCH_UPDATE ioctl. Extend Vm::import_isolated_pages() with a uaddrs parameter carrying host virtual addresses, which KVM needs, unlike MSHV. Compute uaddrs from guest memory mappings in the IGVM loader. Add KVM_SEV_SNP_LAUNCH_UPDATE to the seccomp allowlist. Co-authored-by: Keith Adler Signed-off-by: Keith Adler Co-authored-by: Alex Orozco Signed-off-by: Alex Orozco Signed-off-by: Ruben Hakobyan --- hypervisor/src/kvm/mod.rs | 40 +++++++++++++++++++++++++++ hypervisor/src/kvm/x86_64/sev.rs | 47 ++++++++++++++++++++++++++++++++ hypervisor/src/mshv/mod.rs | 1 + hypervisor/src/vm.rs | 1 + vmm/src/igvm/igvm_loader.rs | 13 +++++++++ vmm/src/seccomp_filters.rs | 2 ++ 6 files changed, 104 insertions(+) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index a697d5df7..db18c6416 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -142,6 +142,8 @@ ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a); #[cfg(feature = "sev_snp")] use kvm_bindings::{KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes}; +#[cfg(feature = "sev_snp")] +use x86_64::sev; #[cfg(feature = "tdx")] const KVM_EXIT_TDX: u32 = 50; @@ -635,6 +637,44 @@ impl vm::Vm for KvmVm { .map_err(|e| vm::HypervisorVmError::InitializeSevSnp(e.into())) } + #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] + fn import_isolated_pages( + &self, + page_type: u32, + page_size: u32, + // host page frame numbers + pfns: &[u64], + uaddrs: &[u64], + ) -> vm::Result<()> { + if pfns.is_empty() { + return Ok(()); + } + assert_eq!(pfns.len(), uaddrs.len()); + // VMSA pages are not supported by launch_update + // https://elixir.bootlin.com/linux/v6.11/source/arch/x86/kvm/svm/sev.c#L2377 + if page_type == sev::SNP_PAGE_TYPE_VMSA { + return Ok(()); + } + for i in 0..pfns.len() { + self.fd + .set_memory_attributes(kvm_memory_attributes { + address: pfns[i] << sev::GPA_METADATA_SHIFT_OFFSET, + size: page_size as u64, + attributes: kvm_bindings::KVM_MEMORY_ATTRIBUTE_PRIVATE as u64, + // Flags must be zero o/w error (flags aren't being used here yet) + flags: 0, + }) + .map_err(|e| vm::HypervisorVmError::ImportIsolatedPages(e.into()))?; + self.sev_fd + .as_ref() + .unwrap() + .launch_update(&self.fd, uaddrs[i], page_size as u64, pfns[i], page_type) + .map_err(|e| vm::HypervisorVmError::ImportIsolatedPages(e.into()))?; + } + + Ok(()) + } + #[cfg(target_arch = "x86_64")] /// /// Sets the address of the one-page region in the VM's address space. diff --git a/hypervisor/src/kvm/x86_64/sev.rs b/hypervisor/src/kvm/x86_64/sev.rs index d3497bed3..a2aebdd19 100644 --- a/hypervisor/src/kvm/x86_64/sev.rs +++ b/hypervisor/src/kvm/x86_64/sev.rs @@ -19,6 +19,14 @@ pub(crate) type Result = std::result::Result; // KVM SEV command IDs — linux/include/uapi/linux/kvm.h const KVM_SEV_INIT2: u32 = 22; const KVM_SEV_SNP_LAUNCH_START: u32 = 100; +const KVM_SEV_SNP_LAUNCH_UPDATE: u32 = 101; +// SNP_LAUNCH_UPDATE page types — linux/arch/x86/include/uapi/asm/sev-guest.h +pub const SNP_PAGE_TYPE_VMSA: u32 = 2; + +// See AMD Spec Section 8.17 — SNP_LAUNCH_UPDATE +// The last 12 bits are metadata about the guest context +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +pub const GPA_METADATA_SHIFT_OFFSET: u32 = 12; // SNP in VMSA - linux/arch/x86/include/asm/svm.h const SVM_SEV_FEAT_SNP_ACTIVE: u64 = 1 << 0; @@ -59,6 +67,19 @@ pub(crate) struct KvmSevSnpLaunchStart { pub pad1: [u64; 4], } +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct KvmSevSnpLaunchUpdate { + pub gfn_start: u64, + pub uaddr: u64, + pub len: u64, + pub type_: u8, + pub pad0: u8, + pub flags: u16, + pub pad1: u32, + pub pad2: [u64; 4], +} + impl SevFd { pub(crate) fn new(sev_path: impl AsRef) -> Result { let file = OpenOptions::new() @@ -110,4 +131,30 @@ impl SevFd { }; sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_START") } + + pub(crate) fn launch_update( + &self, + vm: &VmFd, + // host virtual address + hva: u64, + size: u64, + // guest frame number + gfn_start: u64, + page_type: u32, + ) -> Result<()> { + let mut update = KvmSevSnpLaunchUpdate { + gfn_start, + uaddr: hva, + len: size, + type_: page_type as u8, + ..Default::default() + }; + let mut sev_cmd = kvm_sev_cmd { + id: KVM_SEV_SNP_LAUNCH_UPDATE, + data: &mut update as *mut KvmSevSnpLaunchUpdate as _, + sev_fd: self.fd.as_raw_fd() as _, + ..Default::default() + }; + sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_UPDATE") + } } diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 111969127..a61f2e44e 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -2272,6 +2272,7 @@ impl vm::Vm for MshvVm { page_type: u32, page_size: u32, pages: &[u64], + _uaddrs: &[u64], ) -> vm::Result<()> { debug_assert!(page_size == hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB); if pages.is_empty() { diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 36aae27b0..6d3a4a4ae 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -429,6 +429,7 @@ pub trait Vm: Send + Sync + Any { _page_type: u32, _page_size: u32, _pages: &[u64], + _uaddrs: &[u64], ) -> Result<()> { unimplemented!() } diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index d7a8f0cae..e22841c88 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -19,6 +19,8 @@ use log::debug; use log::info; use mshv_bindings::*; use thiserror::Error; +#[cfg(feature = "sev_snp")] +use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemory}; use zerocopy::IntoBytes; #[cfg(feature = "sev_snp")] @@ -471,6 +473,16 @@ pub fn load_igvm( .iter() .map(|gpa| gpa.gpa >> HV_HYP_PAGE_SHIFT) .collect(); + let guest_memory = memory_manager.lock().unwrap().guest_memory().memory(); + let uaddrs: Vec<_> = group + .iter() + .map(|gpa| { + let guest_region_mmap = guest_memory.to_region_addr(GuestAddress(gpa.gpa)); + let uaddr_base = guest_region_mmap.unwrap().0.as_ptr() as u64; + let uaddr_offset: u64 = guest_region_mmap.unwrap().1.0; + uaddr_base + uaddr_offset + }) + .collect(); memory_manager .lock() .unwrap() @@ -479,6 +491,7 @@ pub fn load_igvm( group[0].page_type, hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB, &pfns, + &uaddrs, ) .map_err(Error::ImportIsolatedPages)?; } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 125895943..748e3b3b6 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -112,6 +112,7 @@ mod kvm { pub const KVM_GET_NESTED_STATE: u64 = 3229658814; pub const KVM_SET_NESTED_STATE: u64 = 1082175167; pub const KVM_SEV_SNP_LAUNCH_START: u64 = 0x4018_aeb4; + pub const KVM_SEV_SNP_LAUNCH_UPDATE: u64 = 0x8018_aeb5; } mod iommufd { @@ -269,6 +270,7 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result, Backen and![Cond::new(1, ArgLen::Dword, Eq, KVM_GET_NESTED_STATE)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_NESTED_STATE)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SEV_SNP_LAUNCH_START)?], + and![Cond::new(1, ArgLen::Dword, Eq, KVM_SEV_SNP_LAUNCH_UPDATE)?], ]) }