diff mbox series

[4/4] selftests: KVM: Add new test for faulty mmio usage

Message ID 20240923141810.76331-5-iorlov@amazon.com
State New
Headers show
Series Process some MMIO-related errors without KVM exit | expand

Commit Message

Ivan Orlov Sept. 23, 2024, 2:18 p.m. UTC
Implement the test which covers "weird" mmio usage. The test has 4 test
cases:

1) Guest sets IDT/GDT base to point to an MMIO region. Triple fault and
shutdown are expected there.
2) Guest jumps to MMIO address. Fetches from MMIO are not permitted, so
UD is expected there.
3) Guest sets an IDT entry to point to MMIO range. MMIO here happens
after event delivery, so UD is expected.
4) Guest points the UD IDT entry to MMIO range and causes UD after that.
We should not go into infinite loop here, as we are constantly putting
exception info onto the stack and it will eventually overflow.

These test cases depend on previous patches in this patch series.

Signed-off-by: Ivan Orlov <iorlov@amazon.com>
---
 tools/testing/selftests/kvm/Makefile          |   1 +
 .../selftests/kvm/x86_64/faulty_mmio.c        | 199 ++++++++++++++++++
 2 files changed, 200 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86_64/faulty_mmio.c
diff mbox series

Patch

diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 0c4b254ab56b..d9928c54e851 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -129,6 +129,7 @@  TEST_GEN_PROGS_x86_64 += x86_64/amx_test
 TEST_GEN_PROGS_x86_64 += x86_64/max_vcpuid_cap_test
 TEST_GEN_PROGS_x86_64 += x86_64/triple_fault_event_test
 TEST_GEN_PROGS_x86_64 += x86_64/recalc_apic_map_test
+TEST_GEN_PROGS_x86_64 += x86_64/faulty_mmio
 TEST_GEN_PROGS_x86_64 += access_tracking_perf_test
 TEST_GEN_PROGS_x86_64 += demand_paging_test
 TEST_GEN_PROGS_x86_64 += dirty_log_test
diff --git a/tools/testing/selftests/kvm/x86_64/faulty_mmio.c b/tools/testing/selftests/kvm/x86_64/faulty_mmio.c
new file mode 100644
index 000000000000..b83c1d646696
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86_64/faulty_mmio.c
@@ -0,0 +1,199 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This test covers error processing when doing weird things with MMIO addresses,
+ * i.e. jumping into MMIO range or specifying it as IDT / GDT descriptor base.
+ */
+#include <stdio.h>
+#include "kvm_util.h"
+#include "processor.h"
+#include <stdint.h>
+
+#define MMIO_ADDR 0xDEADBEE000UL
+/* This address is not canonical, so any reference will result in #GP */
+#define GP_ADDR 0xDEADBEEFDEADBEEFULL
+
+enum test_desc_type {
+	TEST_DESC_IDT,
+	TEST_DESC_GDT,
+};
+
+static const struct desc_ptr faulty_desc = {
+	.address = MMIO_ADDR,
+	.size = 0xFFF,
+};
+
+static void faulty_desc_guest_code(enum test_desc_type dtype)
+{
+	if (dtype == TEST_DESC_IDT)
+		__asm__ __volatile__("lidt %0"::"m"(faulty_desc));
+	else
+		__asm__ __volatile__("lgdt %0"::"m"(faulty_desc));
+
+	/* Generate a #GP */
+	*((uint8_t *)GP_ADDR) = 0x1;
+
+	/* We should never reach this point */
+	GUEST_ASSERT(0);
+}
+
+/*
+ * This test tries to point the IDT / GDT descriptor to an MMIO range.
+ * This action should cause a triple fault in guest, as it happens when
+ * your descriptors are messed up on the actual hardware.
+ */
+static void test_faulty_desc(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+	int i;
+
+	enum test_desc_type dtype_tests[] = { TEST_DESC_IDT, TEST_DESC_GDT };
+
+	for (i = 0; i < ARRAY_SIZE(dtype_tests); i++) {
+		vm = vm_create_with_one_vcpu(&vcpu, faulty_desc_guest_code);
+		vcpu_args_set(vcpu, 1, dtype_tests[i]);
+		virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
+
+		vcpu_run(vcpu);
+		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_SHUTDOWN);
+		kvm_vm_free(vm);
+	}
+}
+
+static void jump_to_mmio_guest_code(bool write_first)
+{
+	void (*f)(void) = (void *)(MMIO_ADDR);
+
+	if (write_first) {
+		/*
+		 * We get different vmexit codes when accessing the MMIO address for the second
+		 * time with VMX. For the first time it is an EPT violation, for the second -
+		 * EPT misconfig. We need to check that we get #UD in both cases.
+		 */
+		*((char *)MMIO_ADDR) = 0x1;
+	}
+
+	f();
+
+	/* We should never reach this point */
+	GUEST_ASSERT(0);
+}
+
+static void guest_ud_handler(struct ex_regs *regs)
+{
+	GUEST_DONE();
+}
+
+/*
+ * This test tries to jump to an MMIO address. As fetching the instructions
+ * from MMIO is not supported by KVM and doesn't make any practical sense,
+ * KVM should handle it gracefully and inject #UD into guest.
+ */
+static void test_jump_to_mmio(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+	struct kvm_run *run;
+	struct ucall uc;
+	int i;
+
+	bool test_cases_write_first[] = { false, true };
+
+	for (i = 0; i < ARRAY_SIZE(test_cases_write_first); i++) {
+		vm = vm_create_with_one_vcpu(&vcpu, jump_to_mmio_guest_code);
+		virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
+		vcpu_args_set(vcpu, 1, test_cases_write_first[i]);
+		vm_install_exception_handler(vm, UD_VECTOR, guest_ud_handler);
+
+		run = vcpu->run;
+
+		vcpu_run(vcpu);
+		if (test_cases_write_first[i] && run->exit_reason == KVM_EXIT_MMIO) {
+			/* Process first MMIO access if required */
+			vcpu_run(vcpu);
+		}
+
+		/* If #UD was injected correctly, our #UD handler will issue UCALL_DONE */
+		TEST_ASSERT_KVM_EXIT_REASON(vcpu, UCALL_EXIT_REASON);
+		TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_DONE,
+			    "Guest should have gone into #UD handler when jumping to MMIO address, however it didn't happen");
+		kvm_vm_free(vm);
+	}
+}
+
+static void faulty_idte_guest_code(void)
+{
+	/*
+	 * We are triggering #GP here, and as it's IDT entry points to an MMIO range,
+	 * we should get an #UD as instruction fetching from MMIO address is prohibited
+	 */
+	*((uint8_t *)GP_ADDR) = 0x1;
+
+	/* We should never reach this point */
+	GUEST_ASSERT(0);
+}
+
+/*
+ * When IDT entry points to an MMIO address, it should be handled as a jump to MMIO address
+ * and should cause #UD in the guest, as fetches from MMIO are not supported. It should not
+ * cause a triple fault in such a case, so we don't expect KVM_EXIT_SHUTDOWN here.
+ */
+static void test_faulty_idte(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+	struct ucall uc;
+
+	vm = vm_create_with_one_vcpu(&vcpu, faulty_idte_guest_code);
+	virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
+
+	/* GP vector points to MMIO range, jumping to it will trigger an #UD */
+	vm_install_exception_handler(vm, GP_VECTOR, (void *)MMIO_ADDR);
+	vm_install_exception_handler(vm, UD_VECTOR, guest_ud_handler);
+
+	vcpu_run(vcpu);
+	/* If we reach #UD handler it will issue UCALL_DONE */
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, UCALL_EXIT_REASON);
+	TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_DONE,
+		    "Guest should have gone into #UD handler when jumping to MMIO address, however it didn't happen");
+	kvm_vm_free(vm);
+}
+
+static void faulty_ud_idte_guest_code(void)
+{
+	asm("ud2");
+
+	/* We should never reach this point */
+	GUEST_ASSERT(0);
+}
+
+/*
+ * This test checks that we won't hang in the infinite loop if the #UD handler
+ * also causes #UD (as it points to an MMIO address). In this situation, we will
+ * run out of stack eventually, which will cause a triple fault
+ */
+static void test_faulty_ud_handler(void)
+{
+	struct kvm_vm *vm;
+	struct kvm_vcpu *vcpu;
+
+	vm = vm_create_with_one_vcpu(&vcpu, faulty_ud_idte_guest_code);
+	virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
+
+	vm_install_exception_handler(vm, UD_VECTOR, (void *)MMIO_ADDR);
+
+	vcpu_run(vcpu);
+	/* #UD caused when jumping to #UD handler should overflow stack causing a triple fault */
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_SHUTDOWN);
+	kvm_vm_free(vm);
+}
+
+int main(void)
+{
+	test_faulty_desc();
+	test_jump_to_mmio();
+	test_faulty_idte();
+	test_faulty_ud_handler();
+
+	return EXIT_SUCCESS;
+}