[1/2,v2] KVM: nVMX: Defer error from VM-entry MSR-load area to until after hardware verifies VMCS guest state-area
diff mbox series

Message ID 20191015000446.8099-2-krish.sadhukhan@oracle.com
State New
Headers show
Series
  • KVM: nVMX: Defer error from VM-entry MSR-load area to until after hardware verifies VMCS guest state-area
Related show

Commit Message

Krish Sadhukhan Oct. 15, 2019, 12:04 a.m. UTC
According to section “VM Entries” in Intel SDM vol 3C, VM-entry checks are
performed in a certain order. Checks on MSRs that are loaded on VM-entry
from VM-entry MSR-load area, should be done after verifying VMCS controls,
host-state area and guest-state area. As KVM relies on CPU hardware to
perform some of these checks, we need to defer VM-exit due to invalid
VM-entry MSR-load area to until after CPU hardware completes the earlier
checks and is ready to do VMLAUNCH/VMRESUME.

In order to defer errors arising from invalid VM-entry MSR-load area in
vmcs12, we set up a single invalid entry, which is illegal according to
section "Loading MSRs in Intel SDM vol 3C, in VM-entry MSR-load area of
vmcs02. This will cause the CPU hardware to VM-exit with "VM-entry failure
due to MSR loading" after it completes checks on VMCS controls, host-state
area and guest-state area. We reflect a synthesized Exit Qualification to
our guest.

Suggested-by: Jim Mattson <jmattson@google.com>
Signed-off-by: Krish Sadhukhan <krish.sadhukhan@oracle.com>
Reviewed-by: Mihai Carabas <mihai.carabas@oracle.com>
Reviewed-by: Liran Alon <liran.alon@oracle.com>
---
 arch/x86/kvm/vmx/nested.c | 34 +++++++++++++++++++++++++++++++---
 arch/x86/kvm/vmx/nested.h | 15 +++++++++++++--
 arch/x86/kvm/vmx/vmx.c    | 18 ++++++++++++++++++
 arch/x86/kvm/vmx/vmx.h    |  6 ++++++
 4 files changed, 68 insertions(+), 5 deletions(-)

Patch
diff mbox series

diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index e76eb4f07f6c..cebdcb105ea8 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -3029,6 +3029,8 @@  static u8 vmx_has_apicv_interrupt(struct kvm_vcpu *vcpu)
 static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
 				   struct vmcs12 *vmcs12);
 
+extern struct vmx_msr_entry *vmcs12_invalid_msr_load_area;
+
 /*
  * If from_vmentry is false, this is being called from state restore (either RSM
  * or KVM_SET_NESTED_STATE).  Otherwise it's called from vmlaunch/vmresume.
@@ -3100,12 +3102,38 @@  int nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu, bool from_vmentry)
 		goto vmentry_fail_vmexit_guest_mode;
 
 	if (from_vmentry) {
-		exit_reason = EXIT_REASON_MSR_LOAD_FAIL;
 		exit_qual = nested_vmx_load_msr(vcpu,
 						vmcs12->vm_entry_msr_load_addr,
 						vmcs12->vm_entry_msr_load_count);
-		if (exit_qual)
-			goto vmentry_fail_vmexit_guest_mode;
+		if (exit_qual) {
+			/*
+			 * According to section “VM Entries” in Intel SDM
+			 * vol 3C, VM-entry checks are performed in a certain
+			 * order. Checks on MSRs that are loaded on VM-entry
+			 * from VM-entry MSR-load area, should be done after
+			 * verifying VMCS controls, host-state area and
+			 * guest-state area. As KVM relies on CPU hardware to
+			 * perform some of these checks, we need to defer
+			 * VM-exit due to invalid VM-entry MSR-load area to
+			 * until after CPU hardware completes the earlier
+			 * checks and is ready to do VMLAUNCH/VMRESUME.
+			 *
+			 * In order to defer errors arising from invalid
+			 * VM-entry MSR-load area in vmcs12, we set up a
+			 * single invalid entry, which is illegal according
+			 * to section "Loading MSRs in Intel SDM vol 3C, in
+			 * VM-entry MSR-load area of vmcs02. This will cause
+			 * the CPU hardware to VM-exit with "VM-entry
+			 * failure due to MSR loading" after it completes
+			 * checks on VMCS controls, host-state area and
+			 * guest-state area.
+			 */
+			vmx->nested.invalid_msr_load_exit_qual = exit_qual;
+			vmcs_write32(VM_ENTRY_MSR_LOAD_COUNT, 1);
+			vmcs_write64(VM_ENTRY_MSR_LOAD_ADDR,
+			    __pa(vmcs12_invalid_msr_load_area));
+			vmx->nested.dirty_vmcs12 = true;
+		}
 	} else {
 		/*
 		 * The MMU is not initialized to point at the right entities yet and
diff --git a/arch/x86/kvm/vmx/nested.h b/arch/x86/kvm/vmx/nested.h
index 187d39bf0bf1..bb51ec8cf7da 100644
--- a/arch/x86/kvm/vmx/nested.h
+++ b/arch/x86/kvm/vmx/nested.h
@@ -64,7 +64,9 @@  static inline bool nested_ept_ad_enabled(struct kvm_vcpu *vcpu)
 static inline int nested_vmx_reflect_vmexit(struct kvm_vcpu *vcpu,
 					    u32 exit_reason)
 {
+	u32 exit_qual;
 	u32 exit_intr_info = vmcs_read32(VM_EXIT_INTR_INFO);
+	struct vmx_msr_entry *addr;
 
 	/*
 	 * At this point, the exit interruption info in exit_intr_info
@@ -81,8 +83,17 @@  static inline int nested_vmx_reflect_vmexit(struct kvm_vcpu *vcpu,
 			vmcs_read32(VM_EXIT_INTR_ERROR_CODE);
 	}
 
-	nested_vmx_vmexit(vcpu, exit_reason, exit_intr_info,
-			  vmcs_readl(EXIT_QUALIFICATION));
+	exit_qual = vmcs_readl(EXIT_QUALIFICATION);
+
+	addr = __va(vmcs_read64(VM_ENTRY_MSR_LOAD_ADDR));
+	if (addr && addr->index == MSR_FS_BASE &&
+	    (exit_reason == (VMX_EXIT_REASONS_FAILED_VMENTRY |
+			    EXIT_REASON_MSR_LOAD_FAIL))) {
+		exit_qual = (to_vmx(vcpu))->nested.invalid_msr_load_exit_qual;
+	}
+
+	nested_vmx_vmexit(vcpu, exit_reason, exit_intr_info, exit_qual);
+
 	return 1;
 }
 
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index e7970a2e8eae..7ece11322430 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -7914,6 +7914,13 @@  static void vmx_cleanup_l1d_flush(void)
 	l1tf_vmx_mitigation = VMENTER_L1D_FLUSH_AUTO;
 }
 
+/*
+ * This is used to set up an invalid VM-entry MSR-load area for vmcs02
+ * if an error is detected while processing the entries in VM-entry
+ * MSR-load area of vmcs12.
+ */
+struct vmx_msr_entry *vmcs12_invalid_msr_load_area = NULL;
+
 static void vmx_exit(void)
 {
 #ifdef CONFIG_KEXEC_CORE
@@ -7947,6 +7954,9 @@  static void vmx_exit(void)
 	}
 #endif
 	vmx_cleanup_l1d_flush();
+
+	if (vmcs12_invalid_msr_load_area)
+		kfree(vmcs12_invalid_msr_load_area);
 }
 module_exit(vmx_exit);
 
@@ -8012,6 +8022,14 @@  static int __init vmx_init(void)
 #endif
 	vmx_check_vmcs12_offsets();
 
+	vmcs12_invalid_msr_load_area =
+	kzalloc(sizeof(struct vmx_msr_entry), GFP_KERNEL_ACCOUNT);
+	if (!vmcs12_invalid_msr_load_area) {
+		vmx_exit();
+		return 15;
+	}
+	vmcs12_invalid_msr_load_area->index = MSR_FS_BASE;
+
 	return 0;
 }
 module_init(vmx_init);
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index bee16687dc0b..ee7f40abd199 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -183,6 +183,12 @@  struct nested_vmx {
 	gpa_t hv_evmcs_vmptr;
 	struct kvm_host_map hv_evmcs_map;
 	struct hv_enlightened_vmcs *hv_evmcs;
+
+	/*
+	 * This field is used for Exit Qualification when VM-entry fails
+	 * due to invalid VM-entry MSR-load area in vmcs12.
+	 */
+	u32 invalid_msr_load_exit_qual;
 };
 
 struct vcpu_vmx {