From: David Woodhouse <[email protected]>

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 <[email protected]>
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


Reply via email to