From: David Woodhouse <[email protected]>

The remote kick paths (kvm_make_vcpus_request_mask(),
kvm_make_all_cpus_request(), __kvm_vcpu_kick()) read
READ_ONCE(vcpu->cpu) of the plane-0 vCPU. But vcpu->cpu is per plane
vCPU — kvm_arch_vcpu_load() refreshes only the loaded twin's copy —
while vcpu->mode is shared in kvm_vcpu_common. When the vCPU thread
has migrated pCPUs while a non-zero plane was loaded, the shared mode
reads IN_GUEST_MODE but the IPI goes to plane 0's stale pCPU: a flush
hypercall returns HV_STATUS_SUCCESS while the twin keeps running on
stale translations, breaking the TLFS guarantee that the flush covers
the whole VP.

Read the pCPU from common->current_vcpu, whose cpu field every
kvm_arch_vcpu_load() refreshes, in both kick paths. Also read the twin
array with READ_ONCE in the per-plane request fan-out and correct the
comment there: mode and wait are shared with the common, the pCPU is
not.

Found by Sashiko review.

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: LLM
---
 virt/kvm/kvm_main.c | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index ae998cef3c36..fa639a659138 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -225,16 +225,16 @@ static void kvm_make_vcpu_request(struct kvm_vcpu *vcpu, 
unsigned int req,
                 * A per-plane request raised on "the vCPU" (TLB flush after
                 * a host-side zap, obsolete-roots reload, ...) concerns
                 * every plane: fan it out to the twins, or the plane that
-                * runs next consumes a bit the others still need.  Mode,
-                * pCPU and wait are shared with the common, so the kick
-                * logic below covers the twins too.
+                * runs next consumes a bit the others still need.  Mode and
+                * wait live in the common and the kick below targets the
+                * running twin's pCPU, so it covers the twins too.
                 */
                if (req & KVM_REQUEST_PER_PLANE) {
                        struct kvm_vcpu *twin;
                        int p;
 
                        for (p = 0; p < KVM_MAX_PLANES; p++) {
-                               twin = vcpu->common->vcpus[p];
+                               twin = READ_ONCE(vcpu->common->vcpus[p]);
                                if (twin && twin != vcpu)
                                        __kvm_make_request(req, twin);
                        }
@@ -255,7 +255,14 @@ static void kvm_make_vcpu_request(struct kvm_vcpu *vcpu, 
unsigned int req,
         * finalized. See kvm_vcpu_kick() for more details on handling requests.
         */
        if (kvm_request_needs_ipi(vcpu, req)) {
-               cpu = READ_ONCE(vcpu->cpu);
+               /*
+                * vcpu->cpu is per plane vCPU and kvm_arch_vcpu_load()
+                * refreshes only the loaded twin's, so this plane's copy
+                * can point at a pCPU the thread has long left while a
+                * twin runs elsewhere.  The mode just checked is shared;
+                * the IPI must go where the running twin actually is.
+                */
+               cpu = READ_ONCE(READ_ONCE(vcpu->common->current_vcpu)->cpu);
                if (cpu != -1 && cpu != current_cpu)
                        __cpumask_set_cpu(cpu, tmp);
        }
@@ -4334,7 +4341,13 @@ void __kvm_vcpu_kick(struct kvm_vcpu *vcpu, bool wait)
         * vCPU also requires it to leave IN_GUEST_MODE.
         */
        if (kvm_arch_vcpu_should_kick(vcpu)) {
-               cpu = READ_ONCE(vcpu->cpu);
+               /*
+                * Target the running twin's pCPU: this plane's vcpu->cpu
+                * goes stale whenever the thread migrates while another
+                * plane is loaded, and the IN_GUEST_MODE just observed is
+                * the (shared) mode of whichever twin runs.
+                */
+               cpu = READ_ONCE(READ_ONCE(vcpu->common->current_vcpu)->cpu);
                if (cpu != me && (unsigned int)cpu < nr_cpu_ids && 
cpu_online(cpu)) {
                        /*
                         * Use a reschedule IPI to kick the vCPU if the caller
-- 
2.50.1


Reply via email to