From: David Woodhouse <[email protected]>

Reinstate support for caches whose pfn a vCPU uses directly from guest
mode (stuffed into hardware control structures such as vmcs02 fields),
addressing the reasons the original KVM_GUEST_USES_PFN was removed as
incomplete/broken¹²:

 - The kick now comes with a guarantee. The original
   KVM_REQ_OUTSIDE_GUEST_MODE kick did "absolutely nothing to
   guarantee KVM refreshes the cache before re-entering the guest"¹.
   Now, a vCPU pins with a cmpxchg which succeeds only while GPC_VALID
   holds; the invalidation walk reads the pin in the same atomic word
   it already reads to veto in-flight publishes, so no pin can slip
   between its check and the zap; and re-pinning requires GPC_VALID,
   which requires a refresh, which cannot complete until
   invalidate_range_end(). The vCPU is forced out (with WAIT) and
   physically cannot re-enter with the stale mapping.

 - The service request is per-cache, given at init³
   (kvm_gpc_init_for_vcpu()), so e.g. nVMX can request
   KVM_REQ_GET_NESTED_STATE_PAGES for vmcs12 pages rather than KVM
   guessing. The force-out (KVM_REQ_OUTSIDE_GUEST_MODE, which carries
   KVM_REQUEST_WAIT) is common; the per-cache request says what the
   vCPU must do before re-entering.

 - No special-casing of non-blockable invalidations. The original
   stripped KVM_REQUEST_WAIT for the OOM reaper on the assumption that
   a reaped task's vCPUs are already stopped; that assumption is false
   for process_mrelease(), which requires only SIGNAL_GROUP_EXIT, and
   the strip opened the very use-after-free window the kick exists to
   prevent⁴. The wait is unconditionally safe:
   kvm_make_vcpus_request_mask() runs with preemption disabled and
   spin-waits for IPI acks; it never sleeps.

 - Pin lifecycle has single-writer discipline. Only the pinning vCPU
   sets its pin (cmpxchg-while-VALID) and only that vCPU clears it (on
   unpin or request service, before re-pinning) — except that a
   mutator's xchg of the whole state word to zero may consume a live
   pin, in which case it inherits the kick-and-wait duty before
   mutating anything the guest may be using. A stale pin left by a
   kicked vCPU costs at most a spurious request bit: the force-out
   only IPIs and waits for targets actually in guest mode.

No users yet; the nVMX conversion⁵ builds on this.

¹ https://lore.kernel.org/all/[email protected]
² https://lore.kernel.org/all/[email protected]
³ https://lore.kernel.org/all/[email protected]
⁴ per Jann Horn's analysis in ²
⁵ https://lore.kernel.org/all/[email protected]

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: Claude:claude-mythos-5
---
 arch/x86/kvm/xen.c        |   4 +-
 include/linux/kvm_host.h  |  55 ++++++++++++-
 include/linux/kvm_types.h |  28 +++++--
 virt/kvm/pfncache.c       | 168 ++++++++++++++++++++++++++++++++++++--
 4 files changed, 235 insertions(+), 20 deletions(-)

diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 16b76379dcec..b6643b889320 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -2388,7 +2388,7 @@ void kvm_xen_init_vcpu(struct kvm_vcpu *vcpu)
 
        kvm_gpc_init(&vcpu->arch.xen.runstate_cache, vcpu->kvm);
        kvm_gpc_init(&vcpu->arch.xen.runstate2_cache, vcpu->kvm);
-       __kvm_gpc_init(&vcpu->arch.xen.vcpu_info_cache, vcpu->kvm, true);
+       __kvm_gpc_init(&vcpu->arch.xen.vcpu_info_cache, vcpu->kvm, true, NULL, 
0);
        kvm_gpc_init(&vcpu->arch.xen.vcpu_time_info_cache, vcpu->kvm);
 }
 
@@ -2409,7 +2409,7 @@ void kvm_xen_init_vm(struct kvm *kvm)
 {
        mutex_init(&kvm->arch.xen.xen_lock);
        xa_init(&kvm->arch.xen.evtchn_ports);
-       __kvm_gpc_init(&kvm->arch.xen.shinfo_cache, kvm, true);
+       __kvm_gpc_init(&kvm->arch.xen.shinfo_cache, kvm, true, NULL, 0);
 }
 
 void kvm_xen_destroy_vm(struct kvm *kvm)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 7b2dbbd6b104..0ee1479b135b 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1475,13 +1475,64 @@ int kvm_vcpu_write_guest(struct kvm_vcpu *vcpu, gpa_t 
gpa, const void *data,
  * the caller before init).
  */
 void __kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm,
-                   bool never_dirty);
+                   bool never_dirty, struct kvm_vcpu *vcpu, u32 vcpu_req);
 
 static inline void kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm)
 {
-       __kvm_gpc_init(gpc, kvm, false);
+       __kvm_gpc_init(gpc, kvm, false, NULL, 0);
 }
 
+/**
+ * kvm_gpc_init_for_vcpu - initialize a cache whose pfn a vCPU may pin for
+ *                         direct use from guest mode.
+ *
+ * @gpc:  struct gfn_to_pfn_cache object.
+ * @vcpu: the vCPU which will pin and directly access this cache.
+ * @req:  service request to post to @vcpu when the cache is invalidated
+ *        while pinned. The invalidation separately forces @vcpu out of
+ *        guest mode (KVM_REQ_OUTSIDE_GUEST_MODE, with WAIT) and does not
+ *        complete until it has left; @req says what the vCPU must then do
+ *        before re-entering. Its handler must re-establish the mapping
+ *        (via a refresh, which cannot complete until the invalidation
+ *        ends) and re-pin before the pfn may be used from guest mode
+ *        again, or exit to userspace.
+ */
+static inline void kvm_gpc_init_for_vcpu(struct gfn_to_pfn_cache *gpc,
+                                        struct kvm_vcpu *vcpu, u32 req)
+{
+       __kvm_gpc_init(gpc, vcpu->kvm, false, vcpu, req);
+}
+
+/**
+ * kvm_gpc_pin_for_guest - pin a valid cache's pfn for use from guest mode.
+ *
+ * @gpc: struct gfn_to_pfn_cache object (initialized with
+ *       kvm_gpc_init_for_vcpu()).
+ *
+ * Marks the cache as being used directly by the vCPU in guest mode, so
+ * that an invalidation will kick the vCPU (with the request given at init
+ * time) and wait for it to be out of guest mode before completing.
+ * Succeeds only while the cache is valid: on success the pfn is
+ * guaranteed to remain mapped and unreclaimed until the pin is released
+ * or the vCPU is kicked. Must be called by @gpc->vcpu itself.
+ *
+ * Returns false if the cache is not currently valid; the caller must
+ * refresh and retry, or bail.
+ */
+bool kvm_gpc_pin_for_guest(struct gfn_to_pfn_cache *gpc);
+
+/**
+ * kvm_gpc_unpin_for_guest - release a guest-mode pin.
+ *
+ * @gpc: struct gfn_to_pfn_cache object.
+ *
+ * Must be called by @gpc->vcpu itself, after it has left guest mode (or
+ * before entering it), when the pfn is no longer stuffed into any
+ * hardware control structure. Idempotent: a pin already consumed by a
+ * mutator's teardown is harmless to release again.
+ */
+void kvm_gpc_unpin_for_guest(struct gfn_to_pfn_cache *gpc);
+
 /**
  * kvm_gpc_activate - prepare a cached kernel mapping and HPA for a given guest
  *                    physical address.
diff --git a/include/linux/kvm_types.h b/include/linux/kvm_types.h
index 22393f97266a..bdee01f22a2d 100644
--- a/include/linux/kvm_types.h
+++ b/include/linux/kvm_types.h
@@ -87,21 +87,34 @@ struct gfn_to_pfn_cache {
        unsigned long uhva;
        struct kvm_memory_slot *memslot;
        struct kvm *kvm;
+       /*
+        * A cache initialized with kvm_gpc_init_for_vcpu() may be pinned for
+        * direct use from guest mode (its pfn stuffed into hardware control
+        * structures such as vmcs02 fields) by setting GPC_GUEST_USING while
+        * GPC_VALID holds. When such a cache is invalidated, @vcpu is kicked
+        * out of guest mode with @vcpu_req, whose handler must re-pin (via a
+        * refresh which cannot complete until the invalidation ends) or exit.
+        */
+       struct kvm_vcpu *vcpu;
+       u32 vcpu_req;
        struct list_head list;
        struct mutex refresh_lock;
        void *khva;
        kvm_pfn_t pfn;
        bool active;
        /*
-        * GPC_VALID, GPC_BECOMING_VALID and GPC_INVALIDATING live in a
-        * single atomic word so that a refresh can publish
-        * (BECOMING_VALID => VALID) with a single conditional RMW, an
+        * GPC_VALID, GPC_BECOMING_VALID, GPC_INVALIDATING and
+        * GPC_GUEST_USING live in a single atomic word so that a refresh can
+        * publish (BECOMING_VALID => VALID) with a single conditional RMW, an
         * invalidation can veto that publish by clearing both of those bits
         * (setting INVALIDATING in the same operation if VALID was set, to
-        * record that its grace period is still owed), and a mutator can
-        * consume the whole state with one xchg to learn whether it must
-        * wait for readers before touching anything they see. See the
-        * comments in pfncache.c.
+        * record that its grace period is still owed) and observe in the same
+        * read whether a vCPU has the pfn pinned for guest-mode use
+        * (GUEST_USING, set by the vCPU with a cmpxchg only while VALID
+        * holds, cleared only by that vCPU or by a mutator's xchg), and a
+        * mutator can consume the whole state with one xchg to learn whether
+        * it must wait for readers and/or kick the pinning vCPU before
+        * touching anything they see. See the comments in pfncache.c.
         *
         * 'active' deliberately stays separate: the invalidation and
         * mutation paths operate on the word without having to care about
@@ -129,6 +142,7 @@ struct gfn_to_pfn_cache {
  * VALID clear.
  */
 #define GPC_INVALIDATING       0x4
+#define GPC_GUEST_USING                0x8
 
 #ifdef KVM_ARCH_NR_OBJS_PER_MEMORY_CACHE
 /*
diff --git a/virt/kvm/pfncache.c b/virt/kvm/pfncache.c
index 2fe50dfa3fc1..e15d9a475fed 100644
--- a/virt/kvm/pfncache.c
+++ b/virt/kvm/pfncache.c
@@ -25,7 +25,9 @@
 void gfn_to_pfn_cache_invalidate_start(struct kvm *kvm, unsigned long start,
                                       unsigned long end)
 {
+       DECLARE_BITMAP(vcpu_bitmap, KVM_MAX_VCPUS);
        struct gfn_to_pfn_cache *gpc;
+       bool evict_vcpus = false;
        bool cleared = false;
 
        spin_lock(&kvm->gpc_lock);
@@ -91,10 +93,60 @@ void gfn_to_pfn_cache_invalidate_start(struct kvm *kvm, 
unsigned long start,
 
                        if (old & GPC_VALID)
                                cleared = true;
+
+                       /*
+                        * If a vCPU has the pfn pinned for direct use from
+                        * guest mode, it must be forced out of guest mode
+                        * before the invalidation may complete. The pin bit
+                        * was set with a cmpxchg only while GPC_VALID held,
+                        * so this same read of 'old' cannot miss it. It is
+                        * NOT cleared here: only the vCPU itself (on request
+                        * service, before re-pinning) or a mutator's xchg
+                        * (which inherits the kick duty) may clear it. A
+                        * stale pin costs at most a spurious request bit:
+                        * kvm_make_vcpus_request_mask() only IPIs and waits
+                        * for targets actually in guest mode.
+                        */
+                       if (old & GPC_GUEST_USING) {
+                               if (!evict_vcpus) {
+                                       evict_vcpus = true;
+                                       bitmap_zero(vcpu_bitmap, KVM_MAX_VCPUS);
+                               }
+                               __set_bit(gpc->vcpu->vcpu_idx, vcpu_bitmap);
+                               /*
+                                * Set this cache's own service request (what
+                                * the vCPU must do before re-entering); the
+                                * force-out below is common to all of them.
+                                */
+                               __kvm_make_request(gpc->vcpu_req, gpc->vcpu);
+                       }
                }
        }
        spin_unlock(&kvm->gpc_lock);
 
+       /*
+        * Force pinned vCPUs out of guest mode before waiting for the
+        * kernel readers below and before the caller zaps the page tables.
+        * The request includes KVM_REQUEST_WAIT (enforced at init), so this
+        * does not return until the vCPUs have left guest mode; they cannot
+        * re-enter with the stale mapping, because re-pinning requires
+        * GPC_VALID, which requires a refresh, which cannot complete until
+        * invalidate_range_end().
+        *
+        * This is safe on unblockable (OOM reaper) ranges too, with no
+        * special-casing: kvm_make_vcpus_request_mask() runs with
+        * preemption disabled and spin-waits for IPI acks; it never sleeps.
+        * Do not be tempted to skip the wait for !blockable ranges on the
+        * assumption that a reaped task's vCPUs are already stopped: that
+        * is false for process_mrelease(), which only requires
+        * SIGNAL_GROUP_EXIT, and a vCPU still in guest mode after this
+        * walk returns is exactly the use-after-free this kick exists to
+        * prevent.
+        */
+       if (evict_vcpus)
+               kvm_make_vcpus_request_mask(kvm, KVM_REQ_OUTSIDE_GUEST_MODE,
+                                           vcpu_bitmap);
+
        /*
         * Readers may still be using the old mapping, having sampled
         * GPC_VALID before it was cleared. Wait for them all to drain
@@ -122,7 +174,7 @@ void gfn_to_pfn_cache_invalidate_start(struct kvm *kvm, 
unsigned long start,
         * so the grace period never blocks in reclaim either.)
         */
        if (cleared)
-               synchronize_srcu_expedited(&kvm->gpc_srcu);
+               synchronize_srcu_atomic(&kvm->gpc_srcu);
 
        /*
         * Note the GPC_INVALIDATING markers set above are deliberately NOT
@@ -190,6 +242,100 @@ bool kvm_gpc_check(struct gfn_to_pfn_cache *gpc, unsigned 
long len)
        return true;
 }
 
+/*
+ * A mutator's xchg of the state word to zero may have consumed a live
+ * GPC_GUEST_USING pin. Clearing that bit obliges the clearer to do what
+ * the invalidation walk would have done: post the cache's service
+ * request and force the vCPU out of guest mode, waiting until it has
+ * left, before mutating or tearing down what the guest may be using.
+ *
+ * The service request is posted unconditionally: consuming a pin means
+ * whatever physical address the vCPU had latched (e.g. into a hardware
+ * control structure) is stale, and only the request handler makes that
+ * good. That holds even when the pinning vCPU is the caller itself —
+ * a vCPU refreshing its own pinned cache may re-map at a different pfn,
+ * and without the request nothing would rewrite the stale address
+ * before the next guest entry. Only the waited force-out is skipped in
+ * that case: a vCPU running a refresh is by definition not in guest
+ * mode. (The vCPU's own unpin is the one silent path, in
+ * kvm_gpc_unpin_for_guest(), because unpinning asserts the address is
+ * no longer latched anywhere.)
+ */
+static void gpc_kick_pinned_vcpu(struct gfn_to_pfn_cache *gpc, int old_state)
+{
+       DECLARE_BITMAP(vcpu_bitmap, KVM_MAX_VCPUS);
+
+       if (!(old_state & GPC_GUEST_USING))
+               return;
+       if (WARN_ON_ONCE(!gpc->vcpu))
+               return;
+
+       __kvm_make_request(gpc->vcpu_req, gpc->vcpu);
+
+       if (kvm_get_running_vcpu() == gpc->vcpu)
+               return;
+
+       /*
+        * As in the invalidation walk: force the vCPU out of guest mode and
+        * wait until it has left (KVM_REQ_OUTSIDE_GUEST_MODE carries
+        * KVM_REQUEST_WAIT). A plain kvm_vcpu_kick() would not wait.
+        */
+       bitmap_zero(vcpu_bitmap, KVM_MAX_VCPUS);
+       __set_bit(gpc->vcpu->vcpu_idx, vcpu_bitmap);
+       kvm_make_vcpus_request_mask(gpc->kvm, KVM_REQ_OUTSIDE_GUEST_MODE,
+                                   vcpu_bitmap);
+}
+
+bool kvm_gpc_pin_for_guest(struct gfn_to_pfn_cache *gpc)
+{
+       int old, new;
+
+       WARN_ON_ONCE(!gpc->vcpu);
+       WARN_ON_ONCE(gpc->vcpu && kvm_get_running_vcpu() != gpc->vcpu);
+
+       /*
+        * Set GPC_GUEST_USING only while GPC_VALID holds: the pin then
+        * cannot race with an invalidation, because the invalidation walk
+        * reads the whole state word in one atomic operation — either it
+        * sees VALID (and this pin, if set) and kicks; or the publish which
+        * set VALID has not happened and the walk's veto of
+        * GPC_BECOMING_VALID prevents it. The acquire pairs with the
+        * release-publish so the pinned pfn/khva are the published ones.
+        */
+       old = atomic_read_acquire(&gpc->state);
+       do {
+               if (!(old & GPC_VALID))
+                       return false;
+               new = old | GPC_GUEST_USING;
+       } while (!atomic_try_cmpxchg(&gpc->state, &old, new));
+
+       return true;
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_gpc_pin_for_guest);
+
+void kvm_gpc_unpin_for_guest(struct gfn_to_pfn_cache *gpc)
+{
+       /*
+        * Only the pinning vCPU itself may unpin — except on teardown
+        * paths (vCPU destruction), which run with no running-vCPU
+        * context but with the vCPU necessarily out of guest mode.
+        */
+       WARN_ON_ONCE(gpc->vcpu && kvm_get_running_vcpu() &&
+                    kvm_get_running_vcpu() != gpc->vcpu);
+
+       /*
+        * The vCPU's last access to the pfn must be visible before the pin
+        * is seen clear by an invalidator deciding it need not kick
+        * (atomic_andnot() alone is unordered). In practice the VM-exit
+        * which preceded this call already serialized the guest's accesses,
+        * but the API should not depend on its callers' exit paths. A pin
+        * already consumed by a mutator's xchg makes this a harmless no-op.
+        */
+       smp_mb__before_atomic();
+       atomic_andnot(GPC_GUEST_USING, &gpc->state);
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_gpc_unpin_for_guest);
+
 static void *gpc_map(kvm_pfn_t pfn)
 {
        if (pfn_valid(pfn))
@@ -405,7 +551,7 @@ static int __kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, 
gpa_t gpa, unsigned l
        bool unmap_old = false;
        unsigned long old_uhva;
        kvm_pfn_t old_pfn;
-       bool must_drain;
+       int old_state;
        void *old_khva;
        int ret;
 
@@ -498,10 +644,10 @@ static int __kvm_gpc_refresh(struct gfn_to_pfn_cache 
*gpc, gpa_t gpa, unsigned l
         * reader-visible fields (uhva, gpa, memslot, and later pfn/khva) be
         * mutated or the old mapping torn down.
         */
-       must_drain = atomic_xchg(&gpc->state, 0) &
-                    (GPC_VALID | GPC_INVALIDATING);
-       if (must_drain)
-               synchronize_srcu_expedited(&gpc->kvm->gpc_srcu);
+       old_state = atomic_xchg(&gpc->state, 0);
+       gpc_kick_pinned_vcpu(gpc, old_state);
+       if (old_state & (GPC_VALID | GPC_INVALIDATING))
+               synchronize_srcu_atomic(&gpc->kvm->gpc_srcu);
 
        old_pfn = gpc->pfn;
        old_khva = (void *)PAGE_ALIGN_DOWN((uintptr_t)gpc->khva);
@@ -599,7 +745,7 @@ int kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, unsigned 
long len)
 }
 
 void __kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct kvm *kvm,
-                   bool never_dirty)
+                   bool never_dirty, struct kvm_vcpu *vcpu, u32 vcpu_req)
 {
        mutex_init(&gpc->refresh_lock);
 
@@ -610,6 +756,8 @@ void __kvm_gpc_init(struct gfn_to_pfn_cache *gpc, struct 
kvm *kvm,
        gpc->active = false;
        atomic_set(&gpc->state, 0);
        gpc->never_dirty = never_dirty;
+       gpc->vcpu = vcpu;
+       gpc->vcpu_req = vcpu_req;
 }
 
 static int __kvm_gpc_activate(struct gfn_to_pfn_cache *gpc, gpa_t gpa, 
unsigned long uhva,
@@ -667,6 +815,7 @@ void kvm_gpc_deactivate(struct gfn_to_pfn_cache *gpc)
        struct kvm *kvm = gpc->kvm;
        kvm_pfn_t old_pfn;
        void *old_khva;
+       int old_state;
 
        guard(mutex)(&gpc->refresh_lock);
 
@@ -687,8 +836,9 @@ void kvm_gpc_deactivate(struct gfn_to_pfn_cache *gpc)
                 * readers outstanding — it left GPC_INVALIDATING set to say
                 * so — and the unmap below must not run under those readers.
                 */
-               if (atomic_xchg(&gpc->state, 0) &
-                   (GPC_VALID | GPC_INVALIDATING))
+               old_state = atomic_xchg(&gpc->state, 0);
+               gpc_kick_pinned_vcpu(gpc, old_state);
+               if (old_state & (GPC_VALID | GPC_INVALIDATING))
                        synchronize_srcu_atomic(&kvm->gpc_srcu);
 
                /*
-- 
2.55.0


Reply via email to