[PATCH v2 35/36] KVM: selftests: Test VMX apic timer virt with inkernel apic disable

isaku.yamahata@intel.com posted 36 patches 3 weeks, 6 days ago
[PATCH v2 35/36] KVM: selftests: Test VMX apic timer virt with inkernel apic disable
Posted by isaku.yamahata@intel.com 3 weeks, 6 days ago
From: Isaku Yamahata <isaku.yamahata@intel.com>

Add a test case to excercize APIC timer virtuliazation support with
inkernel APIC disabled as Syzbot reported GP fault if inkernel APIC is
disabled.

Reported-by: syzbot+ci66a37fb2e2f8de71@syzkaller.appspotmail.com
Signed-off-by: Isaku Yamahata <isaku.yamahata@intel.com>
--
Changes v1 -> v2:
- Newly added.
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../kvm/x86/vmx_tertiary_ctls_test.c          | 168 ++++++++++++++++++
 2 files changed, 169 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/vmx_tertiary_ctls_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 424ae9a56481..928b665f8086 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -148,6 +148,7 @@ TEST_GEN_PROGS_x86 += x86/aperfmperf_test
 TEST_GEN_PROGS_x86 += x86/timer_latency
 TEST_GEN_PROGS_x86 += x86/vmx_apic_timer_virt_test
 TEST_GEN_PROGS_x86 += x86/vmx_apic_timer_virt_vmcs_test
+TEST_GEN_PROGS_x86 += x86/vmx_tertiary_ctls_test
 TEST_GEN_PROGS_x86 += access_tracking_perf_test
 TEST_GEN_PROGS_x86 += coalesced_io_test
 TEST_GEN_PROGS_x86 += dirty_log_perf_test
diff --git a/tools/testing/selftests/kvm/x86/vmx_tertiary_ctls_test.c b/tools/testing/selftests/kvm/x86/vmx_tertiary_ctls_test.c
new file mode 100644
index 000000000000..9ef120e00d6d
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/vmx_tertiary_ctls_test.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VMX tertiary controls test
+ *
+ * Copyright (C) 2026 Intel Corporation
+ */
+#include "kvm_util.h"
+#include "vmx.h"
+
+static bool has_guest_apic_timer_virt = true;
+#define TIMER_VECTOR	0xec
+
+#define L2_GUEST_STACK_SIZE 64
+static unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
+
+static void l2_guest_code(void)
+{
+	vmcall();
+
+	/* L1 stops L2 vcpu at above vmcall(). */
+	GUEST_FAIL("L2 should not reach here.");
+}
+
+static void setup_l2(struct vmx_pages *vmx_pages)
+{
+	uint64_t ctls;
+
+	GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));
+	GUEST_ASSERT(load_vmcs(vmx_pages));
+	prepare_vmcs(vmx_pages, l2_guest_code,
+		     &l2_guest_stack[L2_GUEST_STACK_SIZE]);
+
+	GUEST_ASSERT(!vmread(PIN_BASED_VM_EXEC_CONTROL, &ctls));
+	ctls |= PIN_BASED_EXT_INTR_MASK;
+	GUEST_ASSERT(!vmwrite(PIN_BASED_VM_EXEC_CONTROL, ctls));
+
+	GUEST_ASSERT(!vmread(CPU_BASED_VM_EXEC_CONTROL, &ctls));
+	ctls |= CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_TPR_SHADOW |
+		CPU_BASED_ACTIVATE_SECONDARY_CONTROLS;
+	ctls |= CPU_BASED_ACTIVATE_TERTIARY_CONTROLS;
+	GUEST_ASSERT(!vmwrite(CPU_BASED_VM_EXEC_CONTROL, ctls));
+
+	GUEST_ASSERT(!vmread(SECONDARY_VM_EXEC_CONTROL, &ctls));
+	ctls |= SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE |
+		SECONDARY_EXEC_APIC_REGISTER_VIRT |
+		SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY;
+	GUEST_ASSERT(!vmwrite(SECONDARY_VM_EXEC_CONTROL, ctls));
+
+	GUEST_ASSERT(!vmread(TERTIARY_VM_EXEC_CONTROL, &ctls));
+	ctls |= TERTIARY_EXEC_GUEST_APIC_TIMER;
+	/*
+	 * vmwrite() succeeds even if unsuported bit is set.
+	 * follwoing vmenter will fail instead.
+	 */
+	GUEST_ASSERT(!vmwrite(TERTIARY_VM_EXEC_CONTROL, ctls));
+
+	if (has_guest_apic_timer_virt) {
+		GUEST_ASSERT(!vmwrite(GUEST_DEADLINE_VIR, 0));
+		GUEST_ASSERT(!vmwrite(GUEST_DEADLINE_PHY, 0));
+		GUEST_ASSERT(!vmwrite(GUEST_APIC_TIMER_VECTOR, TIMER_VECTOR));
+	} else {
+		GUEST_ASSERT(vmwrite(GUEST_DEADLINE_VIR, 0));
+		GUEST_ASSERT(vmwrite(GUEST_DEADLINE_PHY, 0));
+		GUEST_ASSERT(vmwrite(GUEST_APIC_TIMER_VECTOR, TIMER_VECTOR));
+	}
+}
+
+static void l1_guest_code(struct vmx_pages *vmx_pages)
+{
+	/* Prepare the VMCS for L2 execution. */
+	setup_l2(vmx_pages);
+
+	/* Run L2 */
+	if (!has_guest_apic_timer_virt) {
+		uint64_t ctls3;
+
+		GUEST_ASSERT(vmlaunch());
+
+		GUEST_ASSERT(!vmread(TERTIARY_VM_EXEC_CONTROL, &ctls3));
+		ctls3 &= ~TERTIARY_EXEC_GUEST_APIC_TIMER;
+		GUEST_ASSERT(!vmwrite(TERTIARY_VM_EXEC_CONTROL, ctls3));
+	}
+	GUEST_ASSERT(!vmlaunch());
+	GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
+
+	GUEST_DONE();
+}
+
+static void vmx_tertiary_controls_test(void)
+{
+	uint64_t ctls, ctls3, r;
+	struct kvm_vcpu *vcpu;
+	vm_vaddr_t guest_gva;
+	struct kvm_vm *vm;
+	bool done;
+
+	vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+	vcpu_alloc_vmx(vm, &guest_gva);
+	vcpu_args_set(vcpu, 1, guest_gva);
+
+	ctls = vcpu_get_msr(vcpu, MSR_IA32_VMX_TRUE_PROCBASED_CTLS);
+	ctls |= (uint64_t)CPU_BASED_ACTIVATE_TERTIARY_CONTROLS << 32;
+	vcpu_set_msr(vcpu, MSR_IA32_VMX_TRUE_PROCBASED_CTLS, ctls);
+
+	ctls3 = vcpu_get_msr(vcpu, MSR_IA32_VMX_PROCBASED_CTLS3);
+	ctls3 |= TERTIARY_EXEC_GUEST_APIC_TIMER;
+
+	r = _vcpu_set_msr(vcpu, MSR_IA32_VMX_PROCBASED_CTLS3, ctls3);
+	if (has_guest_apic_timer_virt)
+		TEST_ASSERT_MSR(r == 1,
+			       "KVM_SET_MSR on %s failed value = 0x%lx",
+				MSR_IA32_VMX_PROCBASED_CTLS3,
+				"MSR_IA32_VMX_PROCBASED_CTLS3",
+				ctls3);
+	else {
+		TEST_ASSERT_MSR(r != 1,
+				"KVM_SET_MSR on %s should fail value = 0x%lx",
+				MSR_IA32_VMX_PROCBASED_CTLS3,
+				"MSR_IA32_VMX_PROCBASED_CTLS3",
+				ctls3);
+	}
+	sync_global_to_guest(vm, has_guest_apic_timer_virt);
+
+	done = false;
+	while (!done) {
+		struct ucall uc;
+
+		vcpu_run(vcpu);
+		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+		switch (get_ucall(vcpu, &uc)) {
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+			/* NOT REACHED */
+		case UCALL_DONE:
+			done = true;
+			break;
+		default:
+			TEST_ASSERT(false, "Unknown ucall %lu", uc.cmd);
+		}
+	}
+
+	kvm_vm_free(vm);
+}
+
+int main(void)
+{
+	union vmx_ctrl_msr ctls;
+	uint64_t ctls3;
+
+	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
+
+	ctls.val = kvm_get_feature_msr(MSR_IA32_VMX_TRUE_PROCBASED_CTLS);
+	TEST_REQUIRE(ctls.clr & CPU_BASED_ACTIVATE_TERTIARY_CONTROLS);
+
+	ctls3 = kvm_get_feature_msr(MSR_IA32_VMX_PROCBASED_CTLS3);
+	has_guest_apic_timer_virt = ctls3 & TERTIARY_EXEC_GUEST_APIC_TIMER;
+
+	disable_inkernel_irqchip = false;
+	vmx_tertiary_controls_test();
+
+	/* nested apic timer virtualization requires in-kernel apic */
+	disable_inkernel_irqchip = true;
+	has_guest_apic_timer_virt = false;
+	vmx_tertiary_controls_test();
+
+	return 0;
+}
-- 
2.45.2