[PATCH v11 18/46] KVM: guest_memfd: Handle lru_add fbatch refcounts during conversion safety check
From: Ackerley Tng
Date: Wed Aug 26 2026 - 05:27:39 EST
A guest_memfd folio has no outstanding references if guest_memfd holds the
only references on it. Any other references on the folio may indicate
another user, and guest_memfd cannot convert it to private if there may be
an existing host user.
A folio will have outstanding references if it is present in a per-CPU
lru_add fbatch. guest_memfd does not actually participate in LRU, but
freshly-allocated folios are still added to the lru_add fbatch for batch
LRU statistics processing.
A folio may also have extra refcounts if it is on the mlock fbatch.
These two known "usages" of the folio are handled by calling
lru_cache_drain_for_folio, which drains both the lru_add and mlock
fbatches. After draining, if the refcount is still elevated, then there are
truly outstanding references.
If the page may be dma pinned, DMA is using it and hence there are
outstanding references. folio_maybe_dma_pinned() can have false positives,
but that's only with a significant number of refcounts, at which point
draining LRU is not going to move the needle - it can still be concluded
that the folio has outstanding references.
If the page is still mapped after guest_memfd tried to unmap it earlier in
the conversion process, it also has outstanding references.
Return true and exit early to avoid unnecessary draining in these 2 cases.
Provide a drain status to only drain once ever while processing a batch of
folios.
Acked-by: Vlastimil Babka (SUSE) <vbabka@xxxxxxxxxx>
Suggested-by: David Hildenbrand <david@xxxxxxxxxx>
Reviewed-by: Fuad Tabba <fuad.tabba@xxxxxxxxx>
Reviewed-by: Binbin Wu <binbin.wu@xxxxxxxxxxxxxxx>
Signed-off-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
---
mm/swap.c | 2 ++
virt/kvm/guest_memfd.c | 30 ++++++++++++++++++++++--------
2 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/mm/swap.c b/mm/swap.c
index 8e965c8ce9aa9..9f511b97ab110 100644
--- a/mm/swap.c
+++ b/mm/swap.c
@@ -37,6 +37,7 @@
#include <linux/page_idle.h>
#include <linux/local_lock.h>
#include <linux/buffer_head.h>
+#include <linux/kvm_types.h>
#include "internal.h"
@@ -995,6 +996,7 @@ void lru_cache_drain_for_folio(const struct folio *folio,
*drained = LRU_CACHE_DRAINED_ALL;
}
}
+EXPORT_SYMBOL_FOR_KVM(lru_cache_drain_for_folio);
atomic_t lru_disable_count = ATOMIC_INIT(0);
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 6dc199be0eb87..4912f90567fe8 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -8,6 +8,7 @@
#include <linux/mempolicy.h>
#include <linux/pseudo_fs.h>
#include <linux/pagemap.h>
+#include <linux/swap.h>
#include "kvm_mm.h"
#include "guest_memfd.h"
@@ -556,10 +557,28 @@ static int kvm_gmem_mas_preallocate(struct ma_state *mas, u64 attributes,
return mas_preallocate(mas, xa_mk_value(attributes), GFP_KERNEL);
}
+static bool __folio_has_outstanding_references(struct folio *folio,
+ enum lru_cache_drained *drained)
+{
+ if (folio_maybe_dma_pinned(folio) || folio_mapped(folio))
+ return true;
+
+ /* 1 reference held by filemap_get_folios() in the folio batch. */
+ lru_cache_drain_for_folio(folio, 1, drained);
+
+ /*
+ * Outstanding references are anything other than those from the page
+ * cache, plus 1 temporary reference held by filemap_get_folios() in the
+ * folio batch.
+ */
+ return folio_ref_count(folio) != folio_nr_pages(folio) + 1;
+}
+
static bool kvm_gmem_has_outstanding_references(struct inode *inode,
pgoff_t start, size_t nr_pages,
pgoff_t *err_index)
{
+ enum lru_cache_drained drained = LRU_CACHE_NOT_DRAINED;
struct address_space *mapping = inode->i_mapping;
pgoff_t last = start + nr_pages - 1;
bool has_outstanding = false;
@@ -570,17 +589,12 @@ static bool kvm_gmem_has_outstanding_references(struct inode *inode,
folio_batch_init(&fbatch);
next = start;
- while (has_outstanding && filemap_get_folios(mapping, &next, last, &fbatch)) {
+ while (!has_outstanding && filemap_get_folios(mapping, &next, last, &fbatch)) {
for (i = 0; i < folio_batch_count(&fbatch); ++i) {
struct folio *folio = fbatch.folios[i];
- /*
- * Outstanding references are anything other than those
- * from the page cache, plus 1 temporary reference held
- * by filemap_get_folios() in the folio batch.
- */
- if (folio_ref_count(folio) != folio_nr_pages(folio) + 1) {
- has_outstanding = true;
+ has_outstanding = __folio_has_outstanding_references(folio, &drained);
+ if (has_outstanding) {
*err_index = max(start, folio->index);
break;
}
--
2.55.0.887.g758fc8c411-goog