Thread (32 messages) 32 messages, 2 authors, 19h ago

Re: [PATCH v13 02/12] x86/hw_breakpoints: Make DR7 updates NMI safe

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Date: 2026-08-30 05:35:13
Also in: linux-perf-users, sashiko-reviews

On Sat, 22 Aug 2026 09:41:06 +0000
sashiko-bot@kernel.org wrote:
quoted
-static __always_inline void local_db_restore(unsigned long dr7)
+static __always_inline void local_db_restore(unsigned long dr7,
+					     unsigned int dr7_seq)
 {
+	unsigned int seq;
+
 	/*
 	 * Ensure the compiler doesn't raise this statement into
 	 * the critical section; enabling breakpoints early would
 	 * not be good.
 	 */
 	barrier();
-	if (dr7)
+
+	do {
+		seq = this_cpu_read(cpu_dr7_seq);
+		if (seq == dr7_seq) {
+			if (!dr7)
+				return;
+		} else {
+			dr7 = this_cpu_read(cpu_dr7);
+			if (!dr7)
+				dr7 = DR7_FIXED_1;
+		}
+
 		set_debugreg(dr7, 7);
+		barrier();
+	} while (unlikely(seq != this_cpu_read(cpu_dr7_seq)));
 }
[Severity: High]
If KVM intentionally clears the hardware DR7 state to isolate the guest from
host breakpoints before VMENTER without modifying the host's cpu_dr7 shadow
state:

arch/x86/kvm/x86.c:vcpu_enter_guest() {
    ...
    if (unlikely(vcpu->arch.switch_db_regs &&
                 !(vcpu->arch.switch_db_regs & KVM_DEBUGREG_AUTO_SWITCH))) {
        set_debugreg(DR7_FIXED_1, 7);
    ...
    } else if (unlikely(hw_breakpoint_active())) {
        set_debugreg(DR7_FIXED_1, 7);
    }
    ...
}

Could an NMI firing shortly after this permanently undo this isolation
upon return?

If an NMI (such as a perf event) fires before vmlaunch/vmresume, exc_nmi
calls local_db_save, saving dr7=0. If the NMI modifies a breakpoint,
cpu_dr7_seq increments.

When exc_nmi calls local_db_restore, the sequence mismatch causes it to
discard the saved dr7=0 state and instead unconditionally restore the host's
active breakpoints (this_cpu_read(cpu_dr7)) to the hardware DR7. This could
leak host hardware breakpoints into a KVM guest and cause unexpected #DB
exceptions.
OK, this seems only happen when KGDB/KDB sets the watchpoint from
NMI context (right after KVM disables dr7 for transient to guest).
We can avoid this by early returning if dr7 == 0 in local_db_restore().
(But..., if user intensively set the dr7 in that timing, it will be
ignored.)
[ ... ]
quoted
diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
index 9bba1e2f03af7..8dba9cd04bfa5 100644
--- a/arch/x86/kernel/cpu/mce/core.c
+++ b/arch/x86/kernel/cpu/mce/core.c
[ ... ]
quoted
@@ -2139,20 +2139,22 @@ static __always_inline void exc_machine_check_user(struct pt_regs *regs)
 DEFINE_IDTENTRY_MCE(exc_machine_check)
 {
 	unsigned long dr7;
+	unsigned int dr7_seq;
 
-	dr7 = local_db_save();
+	local_db_save(&dr7, &dr7_seq);
 	exc_machine_check_kernel(regs);
-	local_db_restore(dr7);
+	local_db_restore(dr7, dr7_seq);
 }
 
 /* The user mode variant. */
 DEFINE_IDTENTRY_MCE_USER(exc_machine_check)
 {
 	unsigned long dr7;
+	unsigned int dr7_seq;
 
-	dr7 = local_db_save();
+	local_db_save(&dr7, &dr7_seq);
 	exc_machine_check_user(regs);
-	local_db_restore(dr7);
+	local_db_restore(dr7, dr7_seq);
 }
[Severity: High]
This is a pre-existing issue, but does task migration during user-mode
machine check exceptions corrupt hardware debug registers on the new CPU?

exc_machine_check_user invokes irqentry_exit_to_user_mode, which processes
pending thread work (like TIF_NEED_RESCHED), enabling interrupts and allowing
the task to be scheduled out and migrate to a new CPU:

CPU1
exc_machine_check()
    local_db_save(&dr7, &dr7_seq);
    exc_machine_check_user(regs)
        irqentry_exit_to_user_mode(regs)
            // Task migrates to CPU2

CPU2
    local_db_restore(dr7, dr7_seq);

When it resumes on the new CPU, local_db_restore is executed with the dr7
and dr7_seq values saved from the old CPU. If the new CPU's cpu_dr7_seq
matches the old CPU's saved dr7_seq, it would erroneously write the old CPU's
hardware DR7 state to the new CPU's hardware DR7 register. Could this cause
spurious #DB exceptions or break hardware breakpoints on the new CPU?
OK, this is another problem to be fixed in a bugfix patch. We should do
local_db_save/restore() inside exc_machine_check_user() before calling
irqentry_exit_to_user_mode().

Thank you,

-- 
Masami Hiramatsu (Google) [off-list ref]
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help