From: Masami Hiramatsu (Google) <[email protected]>

In exc_machine_check_user(), local_db_save() and local_db_restore() are
invoked in the outer entry stubs (DEFINE_IDTENTRY_MCE_USER,
DEFINE_FREDENTRY_MCE, and DEFINE_IDTENTRY_RAW), surrounding
exc_machine_check_user().

However, exc_machine_check_user() calls irqentry_exit_to_user_mode(),
which handles pending thread work and may schedule() if TIF_NEED_RESCHED
is set. If the task migrates to another CPU during schedule(),
local_db_restore() runs on the new CPU with the dr7 state saved from the
old CPU. This corrupts the new CPU's DR7 hardware debug register and
leaves the old CPU's DR7 disabled.
In short, local_db_save() and local_db_restore() pair must be run
on the same CPU.

To fix this, move local_db_save() and local_db_restore() inside
exc_machine_check_user() and exc_machine_check_kernel() so that DR7
is saved and restored strictly around do_machine_check().

Fixes: cd840e424f27 ("x86/entry, mce: Disallow #DB during #MC")
Assisted-by: Antigravity:gemini-3.7-flash
Signed-off-by: Masami Hiramatsu (Google) <[email protected]>
---
Changes in v14:
 - Newly added.
---
 arch/x86/kernel/cpu/mce/core.c |   23 +++++++----------------
 1 file changed, 7 insertions(+), 16 deletions(-)

diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
index cfb74be19994..61ade8b5c9d7 100644
--- a/arch/x86/kernel/cpu/mce/core.c
+++ b/arch/x86/kernel/cpu/mce/core.c
@@ -2108,6 +2108,7 @@ bool filter_mce(struct mce *m)
 static __always_inline void exc_machine_check_kernel(struct pt_regs *regs)
 {
        irqentry_state_t irq_state;
+       unsigned long dr7;
 
        WARN_ON_ONCE(user_mode(regs));
 
@@ -2118,18 +2119,24 @@ static __always_inline void 
exc_machine_check_kernel(struct pt_regs *regs)
        if (mca_cfg.initialized && mce_check_crashing_cpu())
                return;
 
+       dr7 = local_db_save();
        irq_state = irqentry_nmi_enter(regs);
 
        do_machine_check(regs);
 
        irqentry_nmi_exit(regs, irq_state);
+       local_db_restore(dr7);
 }
 
 static __always_inline void exc_machine_check_user(struct pt_regs *regs)
 {
+       unsigned long dr7;
+
        irqentry_enter_from_user_mode(regs);
 
+       dr7 = local_db_save();
        do_machine_check(regs);
+       local_db_restore(dr7);
 
        irqentry_exit_to_user_mode(regs);
 }
@@ -2138,21 +2145,13 @@ static __always_inline void 
exc_machine_check_user(struct pt_regs *regs)
 /* MCE hit kernel mode */
 DEFINE_IDTENTRY_MCE(exc_machine_check)
 {
-       unsigned long dr7;
-
-       dr7 = local_db_save();
        exc_machine_check_kernel(regs);
-       local_db_restore(dr7);
 }
 
 /* The user mode variant. */
 DEFINE_IDTENTRY_MCE_USER(exc_machine_check)
 {
-       unsigned long dr7;
-
-       dr7 = local_db_save();
        exc_machine_check_user(regs);
-       local_db_restore(dr7);
 }
 
 #ifdef CONFIG_X86_FRED
@@ -2169,28 +2168,20 @@ DEFINE_IDTENTRY_MCE_USER(exc_machine_check)
  */
 DEFINE_FREDENTRY_MCE(exc_machine_check)
 {
-       unsigned long dr7;
-
-       dr7 = local_db_save();
        if (user_mode(regs))
                exc_machine_check_user(regs);
        else
                exc_machine_check_kernel(regs);
-       local_db_restore(dr7);
 }
 #endif
 #else
 /* 32bit unified entry point */
 DEFINE_IDTENTRY_RAW(exc_machine_check)
 {
-       unsigned long dr7;
-
-       dr7 = local_db_save();
        if (user_mode(regs))
                exc_machine_check_user(regs);
        else
                exc_machine_check_kernel(regs);
-       local_db_restore(dr7);
 }
 #endif
 


Reply via email to