[PATCH 08/17] KVM: pfncache: Return -EAGAIN for a lookup which hits an invalid memslot
From: David Woodhouse
Date: Sun Sep 20 2026 - 17:44:48 EST
From: David Woodhouse <dwmw@xxxxxxxxxxxx>
A memslot being deleted or moved is present in the active array but
flagged KVM_MEMSLOT_INVALID — a transient state which exists only
within a single KVM_SET_USER_MEMORY_REGION call, resolved (or
reverted) before that ioctl returns and announced by a further
memslot generation bump. gfn_to_hva_memslot() collapses that case
and a genuinely unbacked gfn into one error, and the pfncache
refresh reported both as -EFAULT.
Distinguish them: return -EAGAIN when the slot exists but is
invalid, so that callers may retry — mirroring what RET_PF_RETRY
does for ordinary guest faults on an invalid slot — rather than
treat the gfn as unbacked.
For most consumers no change is needed: check-at-use sites (vcpu_info
event injection, steal_time, pvclock) skip the update and self-heal
on their next invocation, and treating -EAGAIN as any other failure
is no worse than before. Event channel delivery is the exception:
abandoning it on a transient refresh failure drops the event. All
three producers — userspace ioctl, guest evtchn_send hypercall, and
eventfd/irqfd (whose inatomic fast path already punts to a workqueue
on -EWOULDBLOCK) — converge on the kvm_xen_set_evtchn() slow path, so
teach its retry loop to keep going on -EAGAIN. A guest send racing a
memslot update now waits out the update (with cond_resched()) instead
of bouncing the hypercall out to userspace.
Where a userspace ioctl performs the resolution (e.g. setting a
vcpu_info address), a concurrent memslot update in the VMM's own
other thread now yields -EAGAIN to userspace instead of -EFAULT:
truthful, actionable, and only visible to a VMM racing against
itself.
Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
Assisted-by: Claude:claude-mythos-5
---
arch/x86/kvm/xen.c | 12 +++++++++++-
virt/kvm/pfncache.c | 17 ++++++++++++++++-
2 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index b6643b889320..0b49eb67d06c 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -2026,7 +2026,17 @@ static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *xe, struct kvm *kvm)
idx = srcu_read_lock(&kvm->srcu);
rc = kvm_gpc_refresh(gpc, PAGE_SIZE);
srcu_read_unlock(&kvm->srcu, idx);
- } while(!rc);
+
+ /*
+ * A refresh which raced with a concurrent memslot update
+ * (-EAGAIN: the slot exists but is mid-DELETE/MOVE) will
+ * succeed once the update completes or reverts; giving up
+ * instead would drop the event. Only a genuinely unbacked
+ * gfn (-EFAULT etc.) is fatal.
+ */
+ if (rc == -EAGAIN)
+ cond_resched();
+ } while (!rc || rc == -EAGAIN);
if (mm_borrowed)
kthread_unuse_mm(kvm->mm);
diff --git a/virt/kvm/pfncache.c b/virt/kvm/pfncache.c
index e15d9a475fed..29397ac02668 100644
--- a/virt/kvm/pfncache.c
+++ b/virt/kvm/pfncache.c
@@ -681,7 +681,22 @@ static int __kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, gpa_t gpa, unsigned l
WRITE_ONCE(gpc->uhva, gfn_to_hva_memslot(gpc->memslot, gfn));
if (kvm_is_error_hva(gpc->uhva)) {
- ret = -EFAULT;
+ /*
+ * A slot being deleted or moved is present
+ * but flagged invalid; the lookup failure is
+ * transient, resolved when the update
+ * completes (or reverts) and announced by a
+ * new memslot generation. Distinguish that
+ * from a genuinely unbacked gfn so that the
+ * caller may retry rather than report an
+ * error, as RET_PF_RETRY does for ordinary
+ * guest faults.
+ */
+ if (gpc->memslot &&
+ (gpc->memslot->flags & KVM_MEMSLOT_INVALID))
+ ret = -EAGAIN;
+ else
+ ret = -EFAULT;
goto out;
}
--
2.55.0