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
