KVM: VMX: Reset eVMCS controls in VP assist page during hardware disabling
authorSean Christopherson <seanjc@google.com>
Wed, 30 Nov 2022 23:08:54 +0000 (23:08 +0000)
committerPaolo Bonzini <pbonzini@redhat.com>
Thu, 29 Dec 2022 20:40:55 +0000 (15:40 -0500)
Reset the eVMCS controls in the per-CPU VP assist page during hardware
disabling instead of waiting until kvm-intel's module exit.  The controls
are activated if and only if KVM creates a VM, i.e. don't need to be
reset if hardware is never enabled.

Doing the reset during hardware disabling will naturally fix a potential
NULL pointer deref bug once KVM disables CPU hotplug while enabling and
disabling hardware (which is necessary to fix a variety of bugs).  If the
kernel is running as the root partition, the VP assist page is unmapped
during CPU hot unplug, and so KVM's clearing of the eVMCS controls needs
to occur with CPU hot(un)plug disabled, otherwise KVM could attempt to
write to a CPU's VP assist page after it's unmapped.

Reported-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
Reviewed-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Message-Id: <20221130230934.1014142-11-seanjc@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
arch/x86/kvm/vmx/vmx.c

index 0220e22b89ca8601cbd24bc9950db8d2bcb971b6..a68d1adfd66bd6ccf19b3ec2d73d9a9368d3e1fa 100644 (file)
@@ -551,6 +551,33 @@ static int hv_enable_l2_tlb_flush(struct kvm_vcpu *vcpu)
        return 0;
 }
 
+static void hv_reset_evmcs(void)
+{
+       struct hv_vp_assist_page *vp_ap;
+
+       if (!static_branch_unlikely(&enable_evmcs))
+               return;
+
+       /*
+        * KVM should enable eVMCS if and only if all CPUs have a VP assist
+        * page, and should reject CPU onlining if eVMCS is enabled the CPU
+        * doesn't have a VP assist page allocated.
+        */
+       vp_ap = hv_get_vp_assist_page(smp_processor_id());
+       if (WARN_ON_ONCE(!vp_ap))
+               return;
+
+       /*
+        * Reset everything to support using non-enlightened VMCS access later
+        * (e.g. when we reload the module with enlightened_vmcs=0)
+        */
+       vp_ap->nested_control.features.directhypercall = 0;
+       vp_ap->current_nested_vmcs = 0;
+       vp_ap->enlighten_vmentry = 0;
+}
+
+#else /* IS_ENABLED(CONFIG_HYPERV) */
+static void hv_reset_evmcs(void) {}
 #endif /* IS_ENABLED(CONFIG_HYPERV) */
 
 /*
@@ -2527,6 +2554,8 @@ static void vmx_hardware_disable(void)
        if (cpu_vmxoff())
                kvm_spurious_fault();
 
+       hv_reset_evmcs();
+
        intel_pt_handle_vmx(0);
 }
 
@@ -8505,27 +8534,8 @@ static void vmx_exit(void)
        kvm_exit();
 
 #if IS_ENABLED(CONFIG_HYPERV)
-       if (static_branch_unlikely(&enable_evmcs)) {
-               int cpu;
-               struct hv_vp_assist_page *vp_ap;
-               /*
-                * Reset everything to support using non-enlightened VMCS
-                * access later (e.g. when we reload the module with
-                * enlightened_vmcs=0)
-                */
-               for_each_online_cpu(cpu) {
-                       vp_ap = hv_get_vp_assist_page(cpu);
-
-                       if (!vp_ap)
-                               continue;
-
-                       vp_ap->nested_control.features.directhypercall = 0;
-                       vp_ap->current_nested_vmcs = 0;
-                       vp_ap->enlighten_vmentry = 0;
-               }
-
+       if (static_branch_unlikely(&enable_evmcs))
                static_branch_disable(&enable_evmcs);
-       }
 #endif
        vmx_cleanup_l1d_flush();