From: David Woodhouse <[email protected]>

Plane twins are published with a plain store to common->vcpus[] plus an
smp_wmb() that pairs with online_vcpus, not with twin initialisation,
while the lockless readers grown across the series (the twin-kick
paths, the Hyper-V TLB-flush and stimer twin lookups, the SEND_IPI
target lookup, vcpu_for_each_plane) read the slots with a mixture of
READ_ONCE and bare loads.  x86-TSO hides the lot, but the protocol is
unannotated generic code: KCSAN reports it, and nothing stops a
reordered publication from exposing a half-initialised twin to a kick.

Publish the slot with smp_store_release(), make every lockless reader
use READ_ONCE (vcpu_for_each_plane included), and document the pairing
at the iterator.  Mutex-serialised lookups (the in-kernel VTL switch
runs under the shared vCPU mutex that twin creation also takes) stay
plain.  Also document KVM_REQUEST_PER_PLANE's two scopes at its
definition: the fan-out to twins happens in kvm_make_vcpu_request(),
while a direct kvm_make_request() marks only the twin it was given - a
distinction a future direct-raise caller would otherwise trip over.

No functional change on x86.

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: LLM
---
 arch/x86/kvm/hyperv.c    |  6 +++---
 include/linux/kvm_host.h | 13 ++++++++++++-
 virt/kvm/kvm_main.c      |  8 +++++++-
 3 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index dbe1b83c1f28..8abc61f46608 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -612,7 +612,7 @@ static void stimer_mark_pending(struct kvm_vcpu_hv_stimer 
*stimer,
                int p;
 
                for (p = 0; p < vcpu->plane_level; p++) {
-                       twin = vcpu->common->vcpus[p];
+                       twin = READ_ONCE(vcpu->common->vcpus[p]);
                        if (twin) {
                                kvm_make_request(KVM_REQ_UNBLOCK, twin);
                                kvm_vcpu_kick(twin);
@@ -2370,7 +2370,7 @@ static void hv_tlb_flush_enqueue_all_planes(struct 
kvm_vcpu *v,
        int p;
 
        for (p = 0; p < KVM_MAX_PLANES; p++) {
-               twin = v->common->vcpus[p];
+               twin = READ_ONCE(v->common->vcpus[p]);
                if (!twin || !to_hv_vcpu(twin))
                        continue;
                fifo = kvm_hv_get_tlb_flush_fifo(twin, false);
@@ -2601,7 +2601,7 @@ static void kvm_hv_send_ipi_to_many(struct kvm *kvm, u32 
vector,
                 * VTL): deliver into that plane's APIC.
                 */
                if (target_plane) {
-                       target = vcpu->common->vcpus[target_plane];
+                       target = READ_ONCE(vcpu->common->vcpus[target_plane]);
                        if (!target)
                                continue;
                }
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 6a1ac96d64ba..4d98de4c7523 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -159,6 +159,12 @@ static inline bool kvm_is_error_gpa(gpa_t gpa)
 #define KVM_REQUEST_NO_WAKEUP      BIT(8)
 #define KVM_REQUEST_WAIT           BIT(9)
 #define KVM_REQUEST_NO_ACTION      BIT(10)
+/*
+ * A KVM_REQUEST_PER_PLANE request has two scopes: raised through
+ * kvm_make_vcpu_request() (kicks, mass requests) it fans out to every
+ * plane twin of the vCPU, while a direct kvm_make_request() marks only
+ * the twin it was given.  A direct caller must pick the twin it means.
+ */
 #define KVM_REQUEST_PER_PLANE      BIT(11)
 /*
  * Architecture-independent vcpu->requests bit members
@@ -400,9 +406,14 @@ struct kvm_vcpu_common {
        struct kvm_vcpu_arch_common arch;
 };
 
+/*
+ * The slot read pairs with the smp_store_release() publication in
+ * kvm_vcpu_finish_common() for iterators running outside the locks that
+ * serialise twin creation.
+ */
 #define vcpu_for_each_plane(common, i, v)                      \
        for ((i) = 0; (i) < KVM_MAX_PLANES; ++(i))              \
-               if (((v) = common->vcpus[(i)]) != NULL)
+               if (((v) = READ_ONCE(common->vcpus[(i)])) != NULL)
 
 /* Tracked per plane-VCPU - used for deciding which plane-vcpu to run */
 enum kvm_vcpu_state {
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index f0b474b0547d..6031ba9a6667 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -563,7 +563,13 @@ static int kvm_vcpu_init_common(struct kvm_vcpu *vcpu, 
struct kvm *kvm, unsigned
 static void kvm_vcpu_finish_common(struct kvm_vcpu *vcpu)
 {
        WARN_ON(vcpu->common->vcpus[vcpu->plane_level] != NULL);
-       vcpu->common->vcpus[vcpu->plane_level] = vcpu;
+       /*
+        * Publish with release semantics so the lockless twin readers
+        * (the kick paths, vcpu_for_each_plane, the hyperv twin lookups)
+        * observe a fully initialised vCPU through their READ_ONCE of the
+        * slot; the plain store relied on x86-TSO.
+        */
+       smp_store_release(&vcpu->common->vcpus[vcpu->plane_level], vcpu);
        smp_wmb();
        if (vcpu->plane_level == 0) {
                /*
-- 
2.50.1


Reply via email to