From 49259bb84f524dead1dd6e6a3b77f837d22ef13c Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Thu, 7 May 2026 17:01:27 +0100 Subject: [PATCH] hypervisor: kvm: Punch holes in guest_memfd on shared transition When an SEV-SNP guest transitions pages from private to shared via KVM_HC_MAP_GPA_RANGE, punch holes in the corresponding guest_memfd backing it. Without this the balloon driver's `set_memory_decrypted()` path transitions the page attributes but the physical memory stays pinned in guest_memfd, making virtio-balloon ineffective for memory overcommit with confidential VMs. Even without ballooning these pages are unused by the guest so consume resources. This mirrors the hole punching that the balloon device does for releasing pages. The memory_slots Arc is cloned into each KvmVcpu at creation so the punch can happen in the vcpu thread. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Rob Bradford --- hypervisor/src/kvm/mod.rs | 70 ++++++++++++++++++++++++++++++++++---- vmm/src/seccomp_filters.rs | 9 +++++ 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index c0f0c6e4a..39f76dca3 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -560,6 +560,8 @@ struct KvmDirtyLogSlot { #[allow(dead_code)] struct KvmMemorySlot { guest_memfd: OwnedFd, + guest_phys_addr: u64, + memory_size: u64, } /// Wrapper over KVM VM ioctls. @@ -850,6 +852,8 @@ impl vm::Vm for KvmVm { xsave_size, #[cfg(feature = "sev_snp")] vm_fd: self.fd.clone(), + #[cfg(feature = "sev_snp")] + memory_slots: self.memory_slots.clone(), }; Ok(Box::new(vcpu)) } @@ -1036,10 +1040,14 @@ impl vm::Vm for KvmVm { ) }; let raw_fd = fd.as_raw_fd() as u32; - slots - .write() - .unwrap() - .insert(slot, KvmMemorySlot { guest_memfd: fd }); + slots.write().unwrap().insert( + slot, + KvmMemorySlot { + guest_memfd: fd, + guest_phys_addr, + memory_size: memory_size as u64, + }, + ); raw_fd } else { 0 @@ -1726,6 +1734,51 @@ pub struct KvmVcpu { xsave_size: i32, #[cfg(feature = "sev_snp")] vm_fd: Arc, + #[cfg(feature = "sev_snp")] + memory_slots: Option>>>, +} + +#[cfg(feature = "sev_snp")] +impl KvmVcpu { + fn punch_holes_in_guest_memfd( + memory_slots: &Option>>>, + gpa: u64, + size: u64, + ) { + let Some(slots) = memory_slots else { + return; + }; + let slots = slots.read().unwrap(); + let req_end = gpa.saturating_add(size); + + for slot in slots.values() { + let slot_end = slot.guest_phys_addr.saturating_add(slot.memory_size); + if gpa >= slot_end || req_end <= slot.guest_phys_addr { + continue; + } + + let overlap_start = gpa.max(slot.guest_phys_addr); + let overlap_end = req_end.min(slot_end); + let offset = overlap_start - slot.guest_phys_addr; + let len = overlap_end - overlap_start; + + // SAFETY: fd is valid, offset and len are within the slot's range. + let ret = unsafe { + libc::fallocate( + slot.guest_memfd.as_raw_fd(), + libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE, + offset as libc::off_t, + len as libc::off_t, + ) + }; + if ret != 0 { + error!( + "Error punching hole in the guest_memfd: gpa={gpa:#x} offset={offset:#x} len={len:#x}: {}", + std::io::Error::last_os_error() + ); + } + } + } } /// Implementation of Vcpu trait for KVM @@ -2430,8 +2483,13 @@ impl cpu::Vcpu for KvmVcpu { }; self.vm_fd .set_memory_attributes(mem_attributes) - .map(|_| cpu::VmExit::Ignore) - .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into())) + .map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?; + + if set_private_attr == 0 { + Self::punch_holes_in_guest_memfd(&self.memory_slots, address, size); + } + + Ok(cpu::VmExit::Ignore) } _ => Ok(cpu::VmExit::Ignore), } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 83e0683b9..fb5aaedf9 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -881,6 +881,15 @@ fn vcpu_thread_rules( (libc::SYS_dup, vec![]), (libc::SYS_exit, vec![]), (libc::SYS_epoll_ctl, vec![]), + ( + libc::SYS_fallocate, + or![and![Cond::new( + 1, + ArgLen::Dword, + Eq, + (libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE) as u64, + )?]], + ), (libc::SYS_fcntl, vec![]), (libc::SYS_fstat, vec![]), (libc::SYS_futex, vec![]),