[PATCH v2 3/3] KVM: selftests: Verify nested VM-Exits while vectoring INT3 and INTn

From: ma . mandourr

Date: Thu Oct 22 2026 - 20:39:21 EST


From: Mahmoud Abumandour <ma.mandourr@xxxxxxxxx>

Verify that a VM-Exit while L2 is vectoring INT3 or INTn through the
empty IDT reports the event and its instruction length, and leaves the
VM-Entry instruction length alone. Only VMX is tested, as both lengths
are VMX fields.

Assisted-by: LLM
Signed-off-by: Mahmoud Abumandour <ma.mandourr@xxxxxxxxx>
---
.../kvm/x86/nested_exceptions_test.c | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)

diff --git a/tools/testing/selftests/kvm/x86/nested_exceptions_test.c b/tools/testing/selftests/kvm/x86/nested_exceptions_test.c
index f47432526ea1..9311af590566 100644
--- a/tools/testing/selftests/kvm/x86/nested_exceptions_test.c
+++ b/tools/testing/selftests/kvm/x86/nested_exceptions_test.c
@@ -40,12 +40,25 @@
#define GP_ERROR_CODE_INTEL ((SS_VECTOR * 8) | ERROR_CODE_IDT_FLAG | ERROR_CODE_EXT_FLAG)
#define GP_ERROR_CODE_ZHAOXIN ((SS_VECTOR * 8) | ERROR_CODE_IDT_FLAG)

+/* No "External event" flag, as INT3 and INTn are software events. */
+#define GP_ERROR_CODE_SOFT_INT(vector) (((vector) * 8) | ERROR_CODE_IDT_FLAG)
+
/*
* Intel and AMD both shove '0' into the error code on #DF, regardless of what
* led to the double fault.
*/
#define DF_ERROR_CODE 0

+/* Arbitrary, any vector #GPs with the empty IDT. */
+#define INTN_VECTOR 0x40
+
+#define INTR_INFO_VALID_MASK BIT(31)
+#define INTR_TYPE_SOFT_INTR (4 << 8)
+#define INTR_TYPE_SOFT_EXCEPTION (6 << 8)
+
+/* Arbitrary, but not the length of INT3 or INTn. */
+#define VM_ENTRY_INSN_LEN 7
+
#define INTERCEPT_SS (BIT_ULL(SS_VECTOR))
#define INTERCEPT_SS_DF (INTERCEPT_SS | BIT_ULL(DF_VECTOR))
#define INTERCEPT_SS_GP_DF (INTERCEPT_SS_DF | BIT_ULL(GP_VECTOR))
@@ -70,6 +83,16 @@ static void l2_ss_injected_tf_test(void)
GUEST_SYNC(FAKE_TRIPLE_FAULT_VECTOR);
}

+static void l2_int3_test(void)
+{
+ asm volatile("int3");
+}
+
+static void l2_intn_test(void)
+{
+ asm volatile("int $" __stringify(INTN_VECTOR));
+}
+
static void svm_run_l2(struct svm_test_data *svm, void *l2_code, int vector,
u32 error_code)
{
@@ -127,6 +150,18 @@ static void vmx_run_l2(void *l2_code, int vector, u32 error_code)
GUEST_ASSERT(!vmread(GUEST_INTERRUPTIBILITY_INFO));
}

+static void vmx_run_l2_soft_int(void *l2_code, u32 type, int vector,
+ u32 insn_len)
+{
+ vmwrite(VM_ENTRY_INSTRUCTION_LEN, VM_ENTRY_INSN_LEN);
+ vmx_run_l2(l2_code, GP_VECTOR, GP_ERROR_CODE_SOFT_INT(vector));
+
+ GUEST_ASSERT_EQ(vmread(IDT_VECTORING_INFO_FIELD),
+ INTR_INFO_VALID_MASK | type | vector);
+ GUEST_ASSERT_EQ(vmread(VM_EXIT_INSTRUCTION_LEN), insn_len);
+ GUEST_ASSERT_EQ(vmread(VM_ENTRY_INSTRUCTION_LEN), VM_ENTRY_INSN_LEN);
+}
+
static void l1_vmx_code(struct vmx_pages *vmx)
{
prepare_for_vmx_operation(vmx);
@@ -146,6 +181,10 @@ static void l1_vmx_code(struct vmx_pages *vmx)
vmx_run_l2(l2_ss_injected_gp_test, GP_VECTOR,
host_cpu_is_zhaoxin ? GP_ERROR_CODE_ZHAOXIN : GP_ERROR_CODE_INTEL);

+ /* INT3 and INTn also #GP, due to the empty IDT. */
+ vmx_run_l2_soft_int(l2_int3_test, INTR_TYPE_SOFT_EXCEPTION, BP_VECTOR, 1);
+ vmx_run_l2_soft_int(l2_intn_test, INTR_TYPE_SOFT_INTR, INTN_VECTOR, 2);
+
vmwrite(EXCEPTION_BITMAP, INTERCEPT_SS_DF);
vmx_run_l2(l2_ss_injected_df_test, DF_VECTOR, DF_ERROR_CODE);

--
2.43.0