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

