Files
vfio-native/patches/kvm/0004-KVM-SVM-intercept-ICEBP-and-skip-it-before-injecting-its-DB.patch

94 lines
3.9 KiB
Diff

From 93bb902ad3516b0c5a31482e27a5ecfd93f6799d Mon Sep 17 00:00:00 2001
From: Sandwich <sandwich@archworks.co>
Date: Sat, 5 Sep 2026 12:31:16 +0200
Subject: [PATCH 4/5] KVM: SVM: intercept ICEBP and skip it before injecting
its #DB
ICEBP (INT1, opcode F1) generates a trap-like #DB: the return RIP pushed
for the exception is that of the instruction following the ICEBP.
SVM does not report ICEBP's #DB through the #DB exception intercept, so
the guest normally takes it directly and correctly. But when delivery
of that #DB is interrupted by a VM-exit - a nested page fault on a cold
IDT, handler or stack page is the common case - the exit reports the
pending #DB in EXITINTINFO with the saved RIP still on the ICEBP, and
svm_complete_interrupts() re-queues it as a plain hardware exception.
The injected #DB then pushes that RIP, so the guest's handler sees the
ICEBP's own address and an IRET re-executes it. The divergence is
observable from unprivileged guest code (an SEH/signal handler reading
the context RIP) and is sporadic, since it needs the delivery to touch
a page KVM has not mapped.
VMX already handles ICEBP: the #DB VM-exit is tagged as a privileged
software exception (is_icebp()) and handle_exception_nmi() skips the
instruction before queueing the #DB. SVM has the equivalent signal in
the dedicated ICEBP instruction intercept, which fires before the #DB
exists.
Enable the ICEBP intercept alongside the #DB exception intercept, skip
the instruction and queue the #DB with the same DR6 payload the
exception path uses. Once RIP has been advanced, an injection that is
itself interrupted is re-injected with the advanced RIP. The vendor
skip helper is used deliberately, as on VMX: a pending single-step #DB
on the ICEBP itself is superseded by the ICEBP's #DB. The emulator
decodes 0xF1, so the non-NRIPS fallback works.
For nested guests the #DB is reflected to L1 with L2's RIP past the
ICEBP, matching what the VMX path does today.
Signed-off-by: Sandwich <sandwich@archworks.co>
---
arch/x86/kvm/svm/svm.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index b0db02615..5b37855ee 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -1153,6 +1153,7 @@ static void init_vmcb(struct kvm_vcpu *vcpu, bool init_event)
set_exception_intercept(svm, MC_VECTOR);
set_exception_intercept(svm, AC_VECTOR);
set_exception_intercept(svm, DB_VECTOR);
+ svm_set_intercept(svm, INTERCEPT_ICEBP);
/*
* Guest access to VMware backdoor ports could legitimately
* trigger #GP because of TSS I/O permission bitmap.
@@ -2078,6 +2079,26 @@ static int db_interception(struct kvm_vcpu *vcpu)
return 1;
}
+static int icebp_interception(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ u32 payload = svm->vmcb->save.dr6 ^ DR6_ACTIVE_LOW;
+
+ /*
+ * 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
+ * superseded by the ICEBP's own #DB.
+ */
+ if (!svm_skip_emulated_instruction(vcpu))
+ return 0;
+
+ kvm_queue_exception_p(vcpu, DB_VECTOR, payload);
+ return 1;
+}
+
static int bp_interception(struct kvm_vcpu *vcpu)
{
struct vcpu_svm *svm = to_svm(vcpu);
@@ -3369,6 +3390,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
[SVM_EXIT_WRITE_DR6] = dr_interception,
[SVM_EXIT_WRITE_DR7] = dr_interception,
[SVM_EXIT_EXCP_BASE + DB_VECTOR] = db_interception,
+ [SVM_EXIT_ICEBP] = icebp_interception,
[SVM_EXIT_EXCP_BASE + BP_VECTOR] = bp_interception,
[SVM_EXIT_EXCP_BASE + UD_VECTOR] = ud_interception,
[SVM_EXIT_EXCP_BASE + PF_VECTOR] = pf_interception,
--
2.55.0