]> git.baikalelectronics.ru Git - kernel.git/commitdiff
KVM: selftests: nSVM: Add svm_nested_soft_inject_test
authorMaciej S. Szmigiero <maciej.szmigiero@oracle.com>
Sun, 1 May 2022 22:07:35 +0000 (00:07 +0200)
committerPaolo Bonzini <pbonzini@redhat.com>
Wed, 8 Jun 2022 08:47:06 +0000 (04:47 -0400)
Add a KVM self-test that checks whether a nSVM L1 is able to successfully
inject a software interrupt, a soft exception and a NMI into its L2 guest.

In practice, this tests both the next_rip field consistency and
L1-injected event with intervening L0 VMEXIT during its delivery:
the first nested VMRUN (that's also trying to inject a software interrupt)
will immediately trigger a L0 NPF.
This L0 NPF will have zero in its CPU-returned next_rip field, which if
incorrectly reused by KVM will trigger a #PF when trying to return to
such address 0 from the interrupt handler.

For NMI injection this tests whether the L1 NMI state isn't getting
incorrectly mixed with the L2 NMI state if a L1 -> L2 NMI needs to be
re-injected.

Reviewed-by: Maxim Levitsky <mlevitsk@redhat.com>
[sean: check exact L2 RIP on first soft interrupt]
Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com>
Message-Id: <d5f3d56528558ad8e28a9f1e1e4187f5a1e6770a.1651440202.git.maciej.szmigiero@oracle.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
tools/testing/selftests/kvm/.gitignore
tools/testing/selftests/kvm/Makefile
tools/testing/selftests/kvm/include/x86_64/processor.h
tools/testing/selftests/kvm/include/x86_64/svm_util.h
tools/testing/selftests/kvm/x86_64/evmcs_test.c
tools/testing/selftests/kvm/x86_64/hyperv_svm_test.c
tools/testing/selftests/kvm/x86_64/svm_nested_soft_inject_test.c [new file with mode: 0644]

index 4509a3a7eeae338cce1ef89ffb7e74c01b75f7b3..82e764d71ca78d5bf9b0427938d950ee046c2317 100644 (file)
 /x86_64/state_test
 /x86_64/svm_vmcall_test
 /x86_64/svm_int_ctl_test
-/x86_64/tsc_scaling_sync
+/x86_64/svm_nested_soft_inject_test
 /x86_64/sync_regs_test
 /x86_64/tsc_msrs_test
+/x86_64/tsc_scaling_sync
 /x86_64/userspace_io_test
 /x86_64/userspace_msr_exit_test
 /x86_64/vmx_apic_access_test
index 81470a99ed1c04ab4bfeee5ca438b170327573e8..f2647b88a8a05cdfbd2449ef9d29504a25947381 100644 (file)
@@ -66,6 +66,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/state_test
 TEST_GEN_PROGS_x86_64 += x86_64/vmx_preemption_timer_test
 TEST_GEN_PROGS_x86_64 += x86_64/svm_vmcall_test
 TEST_GEN_PROGS_x86_64 += x86_64/svm_int_ctl_test
+TEST_GEN_PROGS_x86_64 += x86_64/svm_nested_soft_inject_test
 TEST_GEN_PROGS_x86_64 += x86_64/tsc_scaling_sync
 TEST_GEN_PROGS_x86_64 += x86_64/sync_regs_test
 TEST_GEN_PROGS_x86_64 += x86_64/userspace_io_test
index d0d51adec76eb88f12564fa38e52d0df2ba535f2..4fd870f37b9e3d0462f392013f3f1ac263900763 100644 (file)
@@ -17,6 +17,8 @@
 
 #include "../kvm_util.h"
 
+#define NMI_VECTOR             0x02
+
 #define X86_EFLAGS_FIXED        (1u << 1)
 
 #define X86_CR4_VME            (1ul << 0)
@@ -385,6 +387,21 @@ static inline void cpu_relax(void)
        asm volatile("rep; nop" ::: "memory");
 }
 
+#define vmmcall()              \
+       __asm__ __volatile__(   \
+               "vmmcall\n"     \
+               )
+
+#define ud2()                  \
+       __asm__ __volatile__(   \
+               "ud2\n" \
+               )
+
+#define hlt()                  \
+       __asm__ __volatile__(   \
+               "hlt\n" \
+               )
+
 bool is_intel_cpu(void);
 bool is_amd_cpu(void);
 
index a25aabd8f5e75e00d1b81e2dddfa47e2022403f3..136ba6a5d0275cd4b8d7fda014b81b55bc5cde54 100644 (file)
@@ -16,6 +16,8 @@
 #define CPUID_SVM_BIT          2
 #define CPUID_SVM              BIT_ULL(CPUID_SVM_BIT)
 
+#define SVM_EXIT_EXCP_BASE     0x040
+#define SVM_EXIT_HLT           0x078
 #define SVM_EXIT_MSR           0x07c
 #define SVM_EXIT_VMMCALL       0x081
 
@@ -36,6 +38,16 @@ struct svm_test_data {
        uint64_t msr_gpa;
 };
 
+#define stgi()                 \
+       __asm__ __volatile__(   \
+               "stgi\n"        \
+               )
+
+#define clgi()                 \
+       __asm__ __volatile__(   \
+               "clgi\n"        \
+               )
+
 struct svm_test_data *vcpu_alloc_svm(struct kvm_vm *vm, vm_vaddr_t *p_svm_gva);
 void generic_svm_setup(struct svm_test_data *svm, void *guest_rip, void *guest_rsp);
 void run_guest(struct vmcb *vmcb, uint64_t vmcb_gpa);
index d12e043aa2ee9ffa934ecdbb70d51b0c276ee0d1..e161c6dd7a023e34f2c3605dd6ed4319ddfa0274 100644 (file)
@@ -19,7 +19,6 @@
 #include "vmx.h"
 
 #define VCPU_ID                5
-#define NMI_VECTOR     2
 
 static int ud_count;
 
index 21f5ca9197da7eb38066d4d95d6ea4464b8ff33e..994b33fd8724124a9e78fe94e2c48dbeaf31d9d8 100644 (file)
@@ -42,11 +42,6 @@ struct hv_enlightenments {
  */
 #define VMCB_HV_NESTED_ENLIGHTENMENTS (1U << 31)
 
-static inline void vmmcall(void)
-{
-       __asm__ __volatile__("vmmcall");
-}
-
 void l2_guest_code(void)
 {
        GUEST_SYNC(3);
diff --git a/tools/testing/selftests/kvm/x86_64/svm_nested_soft_inject_test.c b/tools/testing/selftests/kvm/x86_64/svm_nested_soft_inject_test.c
new file mode 100644 (file)
index 0000000..f94f1b4
--- /dev/null
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 Oracle and/or its affiliates.
+ *
+ * Based on:
+ *   svm_int_ctl_test
+ *
+ *   Copyright (C) 2021, Red Hat, Inc.
+ *
+ */
+
+#include <stdatomic.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "apic.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "svm_util.h"
+#include "test_util.h"
+#include "../lib/kvm_util_internal.h"
+
+#define VCPU_ID                0
+#define INT_NR                 0x20
+#define X86_FEATURE_NRIPS      BIT(3)
+
+static_assert(ATOMIC_INT_LOCK_FREE == 2, "atomic int is not lockless");
+
+static unsigned int bp_fired;
+static void guest_bp_handler(struct ex_regs *regs)
+{
+       bp_fired++;
+}
+
+static unsigned int int_fired;
+static void l2_guest_code_int(void);
+
+static void guest_int_handler(struct ex_regs *regs)
+{
+       int_fired++;
+       GUEST_ASSERT_2(regs->rip == (unsigned long)l2_guest_code_int,
+                      regs->rip, (unsigned long)l2_guest_code_int);
+}
+
+static void l2_guest_code_int(void)
+{
+       GUEST_ASSERT_1(int_fired == 1, int_fired);
+       vmmcall();
+       ud2();
+
+       GUEST_ASSERT_1(bp_fired == 1, bp_fired);
+       hlt();
+}
+
+static atomic_int nmi_stage;
+#define nmi_stage_get() atomic_load_explicit(&nmi_stage, memory_order_acquire)
+#define nmi_stage_inc() atomic_fetch_add_explicit(&nmi_stage, 1, memory_order_acq_rel)
+static void guest_nmi_handler(struct ex_regs *regs)
+{
+       nmi_stage_inc();
+
+       if (nmi_stage_get() == 1) {
+               vmmcall();
+               GUEST_ASSERT(false);
+       } else {
+               GUEST_ASSERT_1(nmi_stage_get() == 3, nmi_stage_get());
+               GUEST_DONE();
+       }
+}
+
+static void l2_guest_code_nmi(void)
+{
+       ud2();
+}
+
+static void l1_guest_code(struct svm_test_data *svm, uint64_t is_nmi, uint64_t idt_alt)
+{
+       #define L2_GUEST_STACK_SIZE 64
+       unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
+       struct vmcb *vmcb = svm->vmcb;
+
+       if (is_nmi)
+               x2apic_enable();
+
+       /* Prepare for L2 execution. */
+       generic_svm_setup(svm,
+                         is_nmi ? l2_guest_code_nmi : l2_guest_code_int,
+                         &l2_guest_stack[L2_GUEST_STACK_SIZE]);
+
+       vmcb->control.intercept_exceptions |= BIT(PF_VECTOR) | BIT(UD_VECTOR);
+       vmcb->control.intercept |= BIT(INTERCEPT_NMI) | BIT(INTERCEPT_HLT);
+
+       if (is_nmi) {
+               vmcb->control.event_inj = SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_NMI;
+       } else {
+               vmcb->control.event_inj = INT_NR | SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_SOFT;
+               /* The return address pushed on stack */
+               vmcb->control.next_rip = vmcb->save.rip;
+       }
+
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_3(vmcb->control.exit_code == SVM_EXIT_VMMCALL,
+                      vmcb->control.exit_code,
+                      vmcb->control.exit_info_1, vmcb->control.exit_info_2);
+
+       if (is_nmi) {
+               clgi();
+               x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | APIC_DM_NMI);
+
+               GUEST_ASSERT_1(nmi_stage_get() == 1, nmi_stage_get());
+               nmi_stage_inc();
+
+               stgi();
+               /* self-NMI happens here */
+               while (true)
+                       cpu_relax();
+       }
+
+       /* Skip over VMMCALL */
+       vmcb->save.rip += 3;
+
+       /* Switch to alternate IDT to cause intervening NPF again */
+       vmcb->save.idtr.base = idt_alt;
+       vmcb->control.clean = 0; /* &= ~BIT(VMCB_DT) would be enough */
+
+       vmcb->control.event_inj = BP_VECTOR | SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_EXEPT;
+       /* The return address pushed on stack, skip over UD2 */
+       vmcb->control.next_rip = vmcb->save.rip + 2;
+
+       run_guest(vmcb, svm->vmcb_gpa);
+       GUEST_ASSERT_3(vmcb->control.exit_code == SVM_EXIT_HLT,
+                      vmcb->control.exit_code,
+                      vmcb->control.exit_info_1, vmcb->control.exit_info_2);
+
+       GUEST_DONE();
+}
+
+static void run_test(bool is_nmi)
+{
+       struct kvm_vm *vm;
+       vm_vaddr_t svm_gva;
+       vm_vaddr_t idt_alt_vm;
+       struct kvm_guest_debug debug;
+
+       pr_info("Running %s test\n", is_nmi ? "NMI" : "soft int");
+
+       vm = vm_create_default(VCPU_ID, 0, (void *) l1_guest_code);
+
+       vm_init_descriptor_tables(vm);
+       vcpu_init_descriptor_tables(vm, VCPU_ID);
+
+       vm_install_exception_handler(vm, NMI_VECTOR, guest_nmi_handler);
+       vm_install_exception_handler(vm, BP_VECTOR, guest_bp_handler);
+       vm_install_exception_handler(vm, INT_NR, guest_int_handler);
+
+       vcpu_alloc_svm(vm, &svm_gva);
+
+       if (!is_nmi) {
+               void *idt, *idt_alt;
+
+               idt_alt_vm = vm_vaddr_alloc_page(vm);
+               idt_alt = addr_gva2hva(vm, idt_alt_vm);
+               idt = addr_gva2hva(vm, vm->idt);
+               memcpy(idt_alt, idt, getpagesize());
+       } else {
+               idt_alt_vm = 0;
+       }
+       vcpu_args_set(vm, VCPU_ID, 3, svm_gva, (uint64_t)is_nmi, (uint64_t)idt_alt_vm);
+
+       memset(&debug, 0, sizeof(debug));
+       vcpu_set_guest_debug(vm, VCPU_ID, &debug);
+
+       struct kvm_run *run = vcpu_state(vm, VCPU_ID);
+       struct ucall uc;
+
+       alarm(2);
+       vcpu_run(vm, VCPU_ID);
+       alarm(0);
+       TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
+                   "Got exit_reason other than KVM_EXIT_IO: %u (%s)\n",
+                   run->exit_reason,
+                   exit_reason_str(run->exit_reason));
+
+       switch (get_ucall(vm, VCPU_ID, &uc)) {
+       case UCALL_ABORT:
+               TEST_FAIL("%s at %s:%ld, vals = 0x%lx 0x%lx 0x%lx", (const char *)uc.args[0],
+                         __FILE__, uc.args[1], uc.args[2], uc.args[3], uc.args[4]);
+               break;
+               /* NOT REACHED */
+       case UCALL_DONE:
+               goto done;
+       default:
+               TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd);
+       }
+done:
+       kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+       struct kvm_cpuid_entry2 *cpuid;
+
+       /* Tell stdout not to buffer its content */
+       setbuf(stdout, NULL);
+
+       nested_svm_check_supported();
+
+       cpuid = kvm_get_supported_cpuid_entry(0x8000000a);
+       TEST_ASSERT(cpuid->edx & X86_FEATURE_NRIPS,
+                   "KVM with nSVM is supposed to unconditionally advertise nRIP Save\n");
+
+       atomic_init(&nmi_stage, 0);
+
+       run_test(false);
+       run_test(true);
+
+       return 0;
+}