[RFC PATCH 06/14] KVM: guest_memfd: Report the size of holes in export lookups
From: Fred Griffoul
Date: Tue Oct 27 2026 - 12:04:28 EST
From: Fred Griffoul <fgriffo@xxxxxxxxxxxx>
A lookup reports a hole as a single page, so a consumer walks a large
private or unbacked range one page at a time, and cannot map a large
hole with one entry, e.g. to a scratch page.
Report the largest aligned hole around the page, up to the order the
caller asks for. The search checks one half-block at a time, so it
costs about one lookup per page of the hole it finds.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@xxxxxxxxxxxx>
---
virt/kvm/guest_memfd.c | 48 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 46 insertions(+), 2 deletions(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 015f6657c386..1c5826c5384e 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -1417,6 +1417,48 @@ static int __kvm_gmem_export_lookup(struct inode *inode, pgoff_t index,
return 0;
}
+/* Does no page of [start, start + nr) have a frame to map? */
+static bool kvm_gmem_export_is_hole(struct inode *inode, pgoff_t start,
+ pgoff_t nr)
+{
+ unsigned long pfn;
+ pgoff_t index;
+ u32 attrs;
+ int order;
+
+ for (index = start; index < start + nr; index++) {
+ order = 0;
+ if (__kvm_gmem_export_lookup(inode, index, &pfn, &order,
+ &attrs) != -ENOENT)
+ return false;
+ cond_resched();
+ }
+ return true;
+}
+
+/*
+ * The order, up to @max_order, of the largest naturally aligned block around
+ * @index that lies inside the file and has no frame to map. Each step checks
+ * only the half of the larger block that does not hold @index.
+ */
+static int kvm_gmem_export_hole_order(struct inode *inode, pgoff_t index,
+ int max_order)
+{
+ pgoff_t npages = i_size_read(inode) >> PAGE_SHIFT;
+ int order;
+
+ for (order = 0; order < max_order; order++) {
+ pgoff_t n = 1UL << order;
+ pgoff_t first = round_down(index, 2 * n);
+
+ if (first + 2 * n > npages ||
+ !kvm_gmem_export_is_hole(inode, index & n ? first : first + n,
+ n))
+ break;
+ }
+ return order;
+}
+
/**
* kvm_gmem_export_lookup_pfn - The frame behind a shared page.
* @e: The export.
@@ -1424,7 +1466,8 @@ static int __kvm_gmem_export_lookup(struct inode *inode, pgoff_t index,
* @pfn: The frame.
* @max_order: In: the largest order the caller can map. Out: the order of
* the naturally aligned block around @index that is contiguous,
- * shared and of the same @attrs; 0 for -ENOENT.
+ * shared and of the same @attrs or, for -ENOENT, that has no
+ * frame to map at all.
* @attrs: The GUEST_MEMFD_PFN_* attributes of the block.
*
* Takes no reference and allocates nothing, and may be called from the
@@ -1438,6 +1481,7 @@ int kvm_gmem_export_lookup_pfn(struct guest_memfd_export *e, pgoff_t index,
unsigned long *pfn, int *max_order, u32 *attrs)
{
struct inode *inode = file_inode(e->file);
+ int order = *max_order;
int r;
if (index >= i_size_read(inode) >> PAGE_SHIFT)
@@ -1445,7 +1489,7 @@ int kvm_gmem_export_lookup_pfn(struct guest_memfd_export *e, pgoff_t index,
r = __kvm_gmem_export_lookup(inode, index, pfn, max_order, attrs);
if (r == -ENOENT)
- *max_order = 0;
+ *max_order = kvm_gmem_export_hole_order(inode, index, order);
return r;
}
EXPORT_SYMBOL_GPL(kvm_gmem_export_lookup_pfn);
--
2.47.3