Skip to content

Commit 9cc4093

Browse files
committed
KVM: nVMX: Inject #GP, not #UD, if "generic" VMXON CR0/CR4 check fails
Inject #GP for if VMXON is attempting with a CR0/CR4 that fails the generic "is CRx valid" check, but passes the CR4.VMXE check, and do the generic checks _after_ handling the post-VMXON VM-Fail. The CR4.VMXE check, and all other #UD cases, are special pre-conditions that are enforced prior to pivoting on the current VMX mode, i.e. occur before interception if VMXON is attempted in VMX non-root mode. All other CR0/CR4 checks generate #GP and effectively have lower priority than the post-VMXON check. Per the SDM: IF (register operand) or (CR0.PE = 0) or (CR4.VMXE = 0) or ... THEN #UD; ELSIF not in VMX operation THEN IF (CPL > 0) or (in A20M mode) or (the values of CR0 and CR4 are not supported in VMX operation) THEN #GP(0); ELSIF in VMX non-root operation THEN VMexit; ELSIF CPL > 0 THEN #GP(0); ELSE VMfail("VMXON executed in VMX root operation"); FI; which, if re-written without ELSIF, yields: IF (register operand) or (CR0.PE = 0) or (CR4.VMXE = 0) or ... THEN #UD IF in VMX non-root operation THEN VMexit; IF CPL > 0 THEN #GP(0) IF in VMX operation THEN VMfail("VMXON executed in VMX root operation"); IF (in A20M mode) or (the values of CR0 and CR4 are not supported in VMX operation) THEN #GP(0); Note, KVM unconditionally forwards VMXON VM-Exits that occur in L2 to L1, i.e. there is no need to check the vCPU is not in VMX non-root mode. Add a comment to explain why unconditionally forwarding such exits is functionally correct. Reported-by: Eric Li <ercli@ucdavis.edu> Fixes: c7d855c ("KVM: nVMX: Inject #UD if VMXON is attempted with incompatible CR0/CR4") Cc: stable@vger.kernel.org Signed-off-by: Sean Christopherson <seanjc@google.com> Link: https://lore.kernel.org/r/20221006001956.329314-1-seanjc@google.com
1 parent a8a12c0 commit 9cc4093

File tree

1 file changed

+33
-11
lines changed

1 file changed

+33
-11
lines changed

arch/x86/kvm/vmx/nested.c

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5131,24 +5131,35 @@ static int handle_vmxon(struct kvm_vcpu *vcpu)
51315131
| FEAT_CTL_VMX_ENABLED_OUTSIDE_SMX;
51325132

51335133
/*
5134-
* Note, KVM cannot rely on hardware to perform the CR0/CR4 #UD checks
5135-
* that have higher priority than VM-Exit (see Intel SDM's pseudocode
5136-
* for VMXON), as KVM must load valid CR0/CR4 values into hardware while
5137-
* running the guest, i.e. KVM needs to check the _guest_ values.
5134+
* Manually check CR4.VMXE checks, KVM must force CR4.VMXE=1 to enter
5135+
* the guest and so cannot rely on hardware to perform the check,
5136+
* which has higher priority than VM-Exit (see Intel SDM's pseudocode
5137+
* for VMXON).
51385138
*
5139-
* Rely on hardware for the other two pre-VM-Exit checks, !VM86 and
5140-
* !COMPATIBILITY modes. KVM may run the guest in VM86 to emulate Real
5141-
* Mode, but KVM will never take the guest out of those modes.
5139+
* Rely on hardware for the other pre-VM-Exit checks, CR0.PE=1, !VM86
5140+
* and !COMPATIBILITY modes. For an unrestricted guest, KVM doesn't
5141+
* force any of the relevant guest state. For a restricted guest, KVM
5142+
* does force CR0.PE=1, but only to also force VM86 in order to emulate
5143+
* Real Mode, and so there's no need to check CR0.PE manually.
51425144
*/
5143-
if (!nested_host_cr0_valid(vcpu, kvm_read_cr0(vcpu)) ||
5144-
!nested_host_cr4_valid(vcpu, kvm_read_cr4(vcpu))) {
5145+
if (!kvm_read_cr4_bits(vcpu, X86_CR4_VMXE)) {
51455146
kvm_queue_exception(vcpu, UD_VECTOR);
51465147
return 1;
51475148
}
51485149

51495150
/*
5150-
* CPL=0 and all other checks that are lower priority than VM-Exit must
5151-
* be checked manually.
5151+
* The CPL is checked for "not in VMX operation" and for "in VMX root",
5152+
* and has higher priority than the VM-Fail due to being post-VMXON,
5153+
* i.e. VMXON #GPs outside of VMX non-root if CPL!=0. In VMX non-root,
5154+
* VMXON causes VM-Exit and KVM unconditionally forwards VMXON VM-Exits
5155+
* from L2 to L1, i.e. there's no need to check for the vCPU being in
5156+
* VMX non-root.
5157+
*
5158+
* Forwarding the VM-Exit unconditionally, i.e. without performing the
5159+
* #UD checks (see above), is functionally ok because KVM doesn't allow
5160+
* L1 to run L2 without CR4.VMXE=0, and because KVM never modifies L2's
5161+
* CR0 or CR4, i.e. it's L2's responsibility to emulate #UDs that are
5162+
* missed by hardware due to shadowing CR0 and/or CR4.
51525163
*/
51535164
if (vmx_get_cpl(vcpu)) {
51545165
kvm_inject_gp(vcpu, 0);
@@ -5158,6 +5169,17 @@ static int handle_vmxon(struct kvm_vcpu *vcpu)
51585169
if (vmx->nested.vmxon)
51595170
return nested_vmx_fail(vcpu, VMXERR_VMXON_IN_VMX_ROOT_OPERATION);
51605171

5172+
/*
5173+
* Invalid CR0/CR4 generates #GP. These checks are performed if and
5174+
* only if the vCPU isn't already in VMX operation, i.e. effectively
5175+
* have lower priority than the VM-Fail above.
5176+
*/
5177+
if (!nested_host_cr0_valid(vcpu, kvm_read_cr0(vcpu)) ||
5178+
!nested_host_cr4_valid(vcpu, kvm_read_cr4(vcpu))) {
5179+
kvm_inject_gp(vcpu, 0);
5180+
return 1;
5181+
}
5182+
51615183
if ((vmx->msr_ia32_feature_control & VMXON_NEEDED_FEATURES)
51625184
!= VMXON_NEEDED_FEATURES) {
51635185
kvm_inject_gp(vcpu, 0);

0 commit comments

Comments
 (0)