Files
vfio-native/patches/kvm/0005-KVM-selftests-verify-ICEBP-DB-reports-RIP-past-the-ICEBP.patch

128 lines
4.3 KiB
Diff

From 1cc0b5d1d2f61b76c2b9124646b827abda525828 Mon Sep 17 00:00:00 2001
From: Sandwich <sandwich@archworks.co>
Date: Sat, 5 Sep 2026 12:31:16 +0200
Subject: [PATCH 5/5] KVM: selftests: verify ICEBP #DB reports RIP past the
ICEBP
Execute ICEBP (INT1) in the guest under a #DB handler that records the
exception frame's RIP, and assert it is the address of the following
instruction, as on bare metal. The handler advances RIP itself if it
finds it still on the ICEBP so a failing KVM does not loop forever.
The first ICEBP in a fresh VM faults on the unmapped IDT and handler
pages during delivery, which is the interrupted-delivery case where SVM
re-injected the #DB with the wrong RIP. Fails on SVM without the
preceding patch, passes with it, and passes on VMX, which already skips
the instruction.
Signed-off-by: Sandwich <sandwich@archworks.co>
---
arch/x86/kvm/svm/svm.c | 7 ++-
tools/testing/selftests/kvm/Makefile.kvm | 1 +
tools/testing/selftests/kvm/x86/icebp_test.c | 63 ++++++++++++++++++++
3 files changed, 68 insertions(+), 3 deletions(-)
create mode 100644 tools/testing/selftests/kvm/x86/icebp_test.c
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 5b37855ee..34e025cba 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -2087,9 +2087,10 @@ static int icebp_interception(struct kvm_vcpu *vcpu)
/*
* ICEBP generates a trap-like #DB, but is intercepted as an
* instruction, i.e. RIP still points at the ICEBP itself. Skip it
- * before injecting the #DB so the guest observes the RIP of the next
- * instruction, as it does on bare metal and under VMX (see is_icebp()).
- * Use the vendor skip helper: a single-step #DB on the ICEBP is
+ * before injecting the #DB so that the guest, and any re-injection
+ * after an exit interrupts the delivery, sees the RIP of the next
+ * instruction, as on bare metal and under VMX (see is_icebp()). Use
+ * the vendor skip helper: a single-step #DB on the ICEBP is
* superseded by the ICEBP's own #DB.
*/
if (!svm_skip_emulated_instruction(vcpu))
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 6fc34e9bf..b45fe0a13 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -138,6 +138,7 @@ TEST_GEN_PROGS_x86 += x86/xapic_tpr_test
TEST_GEN_PROGS_x86 += x86/xcr0_cpuid_test
TEST_GEN_PROGS_x86 += x86/xss_msr_test
TEST_GEN_PROGS_x86 += x86/debug_regs
+TEST_GEN_PROGS_x86 += x86/icebp_test
TEST_GEN_PROGS_x86 += x86/tsc_msrs_test
TEST_GEN_PROGS_x86 += x86/vmx_pmu_caps_test
TEST_GEN_PROGS_x86 += x86/xen_shinfo_test
diff --git a/tools/testing/selftests/kvm/x86/icebp_test.c b/tools/testing/selftests/kvm/x86/icebp_test.c
new file mode 100644
index 000000000..7890fa949
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/icebp_test.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * icebp_test
+ *
+ * Verify that the #DB generated by ICEBP (INT1) is delivered with RIP
+ * pointing at the instruction following the ICEBP, as on bare metal.
+ *
+ * The first ICEBP in a fresh VM faults on the not-yet-mapped IDT and
+ * handler pages while the #DB is being delivered, so the delivery is
+ * interrupted by a VM-exit and re-injected by KVM. That is exactly the
+ * case where SVM used to re-inject with RIP still on the ICEBP.
+ */
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+
+static u64 db_rip;
+
+static void db_handler(struct ex_regs *regs)
+{
+ db_rip = regs->rip;
+
+ /* Don't loop forever if RIP was left on the ICEBP itself. */
+ if (*(u8 *)regs->rip == 0xf1)
+ regs->rip++;
+}
+
+static void guest_code(void)
+{
+ u64 next_rip;
+
+ asm volatile(".byte 0xf1\n\t" /* icebp */
+ "1: lea 1b(%%rip), %0"
+ : "=r"(next_rip) : : "memory");
+
+ GUEST_ASSERT_EQ(db_rip, next_rip);
+ GUEST_DONE();
+}
+
+int main(int argc, char *argv[])
+{
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+ vm_install_exception_handler(vm, DB_VECTOR, db_handler);
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ case UCALL_DONE:
+ break;
+ default:
+ TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd);
+ }
+
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.55.0