From: David Woodhouse <[email protected]>

When userspace sets a Hyper-V CPUID, kvm_set_cpuid() walks the vCPU's
planes to allocate the per-plane kvm_vcpu_hv, but passes the plane-0
vCPU to kvm_hv_vcpu_init() on every iteration. Non-zero plane vCPUs
are left without hv_vcpu, which trips the
WARN_ON_ONCE(vcpu->arch.hyperv_enabled) in kvm_hv_set_cpuid() and
leaves their vp_index and SynIC state uninitialised until a lazy init
path happens to run. Pass the plane vCPU being iterated.

A twin created *after* KVM_SET_CPUID never passes through
kvm_set_cpuid() at all, but shares a CPUID that already advertises
Hyper-V. Allocate its context in kvm_arch_vcpu_create(), so that
-ENOMEM fails KVM_CREATE_VCPU on the plane fd rather than leaving one
twin silently without Hyper-V.

That allocation runs before the twin's vcpu->vcpu_idx is assigned, so
kvm_hv_vcpu_init() takes the default VP index from the common, which
already holds it. With vcpu->vcpu_idx the twin would latch 0xFFFFFFFF,
and a host write of the real index to HV_X64_MSR_VP_INDEX would then
decrement num_mismatched_vp_indexes for a mismatch that was never
counted. (Assigning vcpu->vcpu_idx earlier instead would defeat the
"vcpu_idx < 0 means unreachable" test that to_hv_vcpu()'s lockdep
assertion falls back on during creation.)

Also admit KVM_ENABLE_CAP on plane vCPU fds, so userspace can enable
per-vCPU capabilities such as KVM_CAP_HYPERV_SYNIC2 on a plane vCPU
before it first runs.

Fixes the Hyper-V initialisation of plane vCPUs in "kvm: x86: Update
state for all plane VCPUs after CPUID update" and "KVM: Implement
KVM_CREATE_VCPU ioctl for planes".

Fixes: https://lore.kernel.org/all/[email protected]/
Fixes: https://lore.kernel.org/all/[email protected]/
Signed-off-by: David Woodhouse <[email protected]>
---
 arch/x86/kvm/cpuid.c  |  4 ++--
 arch/x86/kvm/cpuid.h  |  1 +
 arch/x86/kvm/hyperv.c |  7 ++++++-
 arch/x86/kvm/x86.c    | 20 +++++++++++++++++++-
 4 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index 7a32f658238a..54bcfedc5226 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -326,7 +326,7 @@ static void kvm_update_cpuid_runtime(struct kvm_vcpu *vcpu)
                                                 vcpu->arch.ia32_xss, true);
 }
 
-static bool kvm_cpuid_has_hyperv(struct kvm_vcpu *vcpu)
+bool kvm_cpuid_has_hyperv(struct kvm_vcpu *vcpu)
 {
 #ifdef CONFIG_KVM_HYPERV
        struct kvm_cpuid_entry2 *entry;
@@ -553,7 +553,7 @@ static int kvm_set_cpuid(struct kvm_vcpu *vcpu, struct 
kvm_cpuid_entry2 *e2,
 #ifdef CONFIG_KVM_HYPERV
        if (kvm_cpuid_has_hyperv(vcpu)) {
                vcpu_for_each_plane(common, i, v) {
-                       r = kvm_hv_vcpu_init(vcpu);
+                       r = kvm_hv_vcpu_init(v);
                        if (r)
                                goto err;
                }
diff --git a/arch/x86/kvm/cpuid.h b/arch/x86/kvm/cpuid.h
index 2abfab4b1c33..02dbd02c8f80 100644
--- a/arch/x86/kvm/cpuid.h
+++ b/arch/x86/kvm/cpuid.h
@@ -21,6 +21,7 @@ static inline void kvm_finalize_cpu_caps(void)
 }
 
 void kvm_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu);
+bool kvm_cpuid_has_hyperv(struct kvm_vcpu *vcpu);
 struct kvm_cpuid_entry2 *kvm_find_cpuid_entry2(struct kvm_cpuid_entry2 
*entries,
                                               int nent, u32 function, u64 
index);
 /*
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 1855b7e13e82..0b7440d09d49 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -997,7 +997,12 @@ int kvm_hv_vcpu_init(struct kvm_vcpu *vcpu)
        for (i = 0; i < ARRAY_SIZE(hv_vcpu->stimer); i++)
                stimer_init(&hv_vcpu->stimer[i], i);
 
-       hv_vcpu->vp_index = vcpu->vcpu_idx;
+       /*
+        * A plane twin is initialised here from kvm_arch_vcpu_create(),
+        * before vcpu->vcpu_idx is assigned; the index it is about to
+        * get is already in the shared common.
+        */
+       hv_vcpu->vp_index = vcpu->common->vcpu_idx;
 
        for (i = 0; i < HV_NR_TLB_FLUSH_FIFOS; i++) {
                INIT_KFIFO(hv_vcpu->tlb_flush_fifo[i].entries);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 2e38eaa0b7df..25f574bfbb32 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3467,6 +3467,7 @@ static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu 
*vcpu,
 bool kvm_arch_is_vcpu_plane_ioctl(unsigned ioctl)
 {
        switch (ioctl) {
+       case KVM_ENABLE_CAP:
        case KVM_GET_DEBUGREGS:
        case KVM_SET_DEBUGREGS:
        case KVM_GET_LAPIC:
@@ -9635,9 +9636,24 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
        vcpu->arch.hv_root_tdp = INVALID_PAGE;
 #endif
 
+       /*
+        * A plane twin created after KVM_SET_CPUID shares CPUID that may
+        * already advertise Hyper-V.  Allocate the Hyper-V context now so
+        * a failure fails the creation; left to kvm_vcpu_after_set_cpuid()
+        * below, an -ENOMEM silently downgrades just this twin to no
+        * Hyper-V (its MSRs fail) while the shared CPUID still advertises
+        * the Hyper-V signature, and nothing short of another full
+        * KVM_SET_CPUID repairs it.
+        */
+       if (kvm_cpuid_has_hyperv(vcpu)) {
+               r = kvm_hv_vcpu_init(vcpu);
+               if (r)
+                       goto free_guest_fpu;
+       }
+
        r = kvm_x86_call(vcpu_create)(vcpu);
        if (r)
-               goto free_guest_fpu;
+               goto free_hv;
 
        kvm_xen_init_vcpu(vcpu);
        vcpu_load(vcpu);
@@ -9648,6 +9664,8 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
        vcpu_put(vcpu);
        return 0;
 
+free_hv:
+       kvm_hv_vcpu_uninit(vcpu);
 free_guest_fpu:
        fpu_free_guest_fpstate(&vcpu->arch.guest_fpu);
 free_emulate_ctxt:
-- 
2.50.1


Reply via email to