[PATCH v11 11/46] KVM: guest_memfd: Invalidate both SHARED and PRIVATE mappings for in-place conversions
From: Ackerley Tng
Date: Wed Aug 26 2026 - 05:23:11 EST
From: Sean Christopherson <seanjc@xxxxxxxxxx>
When removing one or more folios from a guest_memfd instance, invalidate
both SHARED and PRIVATE mappings if in-place conversion is enabled, because
stating the obvious, KVM needs to ensure that all mappings to the folio(s)
are dropped.
Opportunistically rename the helper to capture that it returns a filter for
all gfns in anticipation of zapping only the previous mapping types on
conversion. I.e. when doing in-place conversion to PRIVATE, only SHARED
mappings need to be zapped (ignoring that KVM would ideally not invalidate
ranges whose attributes aren't changing in the first place).
Note, precisely zapping only the possible mapping types when in-place
conversion is disabled is important for functional correctness, not just
for performance. Specifically, if KVM zaps both when SHARED vs. PRIVATE is
tracked per-VM, then a PUNCH_HOLE operation on a PRIVATE guest_memfd will
incorrectly zap SHARED mappings that have nothing to do with that gmem
instance (because they're mapped via a VMA, not a gmem fd). And vice versa,
a PUNCH_HOLE on a SHARED gmem (if userspace is using an INIT_SHARED gmem
for the shared branch of a memslot) could invalidate the PRIVATE mappings
of a different gmem instance. The latter case in particular would be a
functional bug, as spuriously zapping PRIVATE SPTEs is fatal to TDX, as
doing so destroys the contents of the memory.
Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
Signed-off-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
---
virt/kvm/guest_memfd.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 5399ae4aae0e7..d4a9877b29ab0 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -139,8 +139,11 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
return folio;
}
-static enum kvm_gfn_range_filter kvm_gmem_get_invalidate_filter(struct inode *inode)
+static enum kvm_gfn_range_filter kvm_gmem_get_all_gfns_filter(struct inode *inode)
{
+ if (gmem_in_place_conversion)
+ return KVM_FILTER_SHARED | KVM_FILTER_PRIVATE;
+
if (GMEM_I(inode)->flags & GUEST_MEMFD_FLAG_INIT_SHARED)
return KVM_FILTER_SHARED;
@@ -191,11 +194,9 @@ static void __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
static void kvm_gmem_invalidate_start(struct inode *inode, pgoff_t start,
pgoff_t end)
{
- enum kvm_gfn_range_filter attr_filter;
+ enum kvm_gfn_range_filter attr_filter = kvm_gmem_get_all_gfns_filter(inode);
struct gmem_file *f;
- attr_filter = kvm_gmem_get_invalidate_filter(inode);
-
kvm_gmem_for_each_file(f, inode)
__kvm_gmem_invalidate_start(f, start, end, attr_filter);
}
@@ -347,7 +348,7 @@ static int kvm_gmem_release(struct inode *inode, struct file *file)
* memory, as its lifetime is associated with the inode, not the file.
*/
__kvm_gmem_invalidate_start(f, 0, -1ul,
- kvm_gmem_get_invalidate_filter(inode));
+ kvm_gmem_get_all_gfns_filter(inode));
__kvm_gmem_invalidate_end(f, 0, -1ul);
list_del(&f->entry);
--
2.55.0.887.g758fc8c411-goog