On Mon, 07 Sep 2026 04:00:16 +0000
[email protected] wrote:

> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) 
> to consider:
> 
> New issues:
> - [High] local_db_restore() incorrectly uses dr7 == 0 to discard 
> NMI-installed hardware breakpoints, permanently losing debug state and 
> corrupting KVM guest debug registers.
> - [High] local_db_save() destructively clears hardware DR7 inside its 
> sequence retry loop, permanently corrupting the restored state if a retry 
> occurs.
> 
> Pre-existing issues:
> - [High] The early return in local_db_save() bypasses the sequence retry 
> loop, leaving a race window where an NMI can leave hardware breakpoints 
> enabled during critical exception handlers.
> --
> 
> commit 48808d0a31b45134e9ef897c90c0f96bd987d9ce
> Author: Jinchao Wang <[email protected]>
> 
> x86/hw_breakpoints: Make DR7 updates NMI safe
> 
> This commit introduces atomic per-CPU operations and sequence numbers to
> make DR7 hardware breakpoint updates NMI safe. It updates local_db_save()
> and local_db_restore() to use retry loops that detect NMI interruptions,
> preventing state corruption.
> 
> Link: 
> https://lore.kernel.org/all/4ee0a2efc9e8387af83286b8495b7d490247e165.1785067572.git.wangjinchao...@gmail.com/
> 
> > diff --git a/arch/x86/include/asm/debugreg.h 
> > b/arch/x86/include/asm/debugreg.h
> > index 854d82b88ff4e..3399e166d3044 100644
> > --- a/arch/x86/include/asm/debugreg.h
> > +++ b/arch/x86/include/asm/debugreg.h
> [ ... ]
> > @@ -125,40 +126,66 @@ static __always_inline bool hw_breakpoint_active(void)
> >  
> >  extern void hw_breakpoint_restore(void);
> >  
> > -static __always_inline unsigned long local_db_save(void)
> > +static __always_inline void local_db_save(unsigned long *dr7,
> > +                                     unsigned int *dr7_seq)
> >  {
> > -   unsigned long dr7;
> > +   do {
> > +           *dr7_seq = this_cpu_read(cpu_dr7_seq);
> > +           *dr7 = 0;
> >  
> > -   if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR) && 
> > !hw_breakpoint_active())
> > -           return 0;
> > -
> > -   get_debugreg(dr7, 7);
> > +           if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR) &&
> > +               !hw_breakpoint_active())
> > +                   return;
> 
> [Severity: High]
> This isn't a bug introduced by this patch, but could this early return
> in local_db_save() bypass the sequence retry loop?
> 
> If an NMI hits exactly before the return instruction and installs a
> breakpoint, the function exits immediately. This leaves the newly installed
> hardware breakpoint active during exception handlers like MCE, which could
> trigger a recursive #DB exception and lead to a kernel panic.

Hmm, OK. But this should be fixed in this patch because this is for solving
this kind of NMI related issue.

Let me update checking cpu_dr7_seq is updated or not before returning.

> 
> >  
> > +           get_debugreg(*dr7, 7);
> >  
> > -   /* Architecturally set bit */
> > -   dr7 &= ~DR7_FIXED_1;
> > -   if (dr7)
> > -           set_debugreg(DR7_FIXED_1, 7);
> > +           /* Architecturally set bit */
> > +           *dr7 &= ~DR7_FIXED_1;
> > +           if (*dr7)
> > +                   set_debugreg(DR7_FIXED_1, 7);
> 
> [Severity: High]
> Can this destructively overwrite the hardware DR7 state inside the sequence
> retry loop?
> 
> If an NMI interrupts execution between reading and clearing DR7, installs a
> breakpoint, and increments cpu_dr7_seq, this set_debugreg() call will
> overwrite the NMI's new hardware state with 0x400.
> 
> When the loop detects the sequence mismatch and retries, the next
> iteration's get_debugreg() will read this clobbered 0x400 value instead of
> the state installed by the NMI, permanently corrupting the restored state.

Ah, OK. Let me add sequence number check there and use cpu_dr7 instead of
raw dr7 value.

> 
> >  
> > -   /*
> > -    * Ensure the compiler doesn't lower the above statements into
> > -    * the critical section; disabling breakpoints late would not
> > -    * be good.
> > -    */
> > -   barrier();
> > +           /*
> > +            * Ensure the compiler doesn't lower the above statements into
> > +            * the critical section; disabling breakpoints late would not
> > +            * be good.
> > +            */
> > +           barrier();
> > +   } while (unlikely(*dr7_seq != this_cpu_read(cpu_dr7_seq)));
> >  }
> [ ... ]
> > -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 long val;
> > +   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)
> > -           set_debugreg(dr7, 7);
> > +
> > +   do {
> > +           seq = this_cpu_read(cpu_dr7_seq);
> > +           if (seq == dr7_seq) {
> > +                   if (!dr7)
> > +                           return;
> > +                   val = dr7;
> > +           } else {
> > +                   /*
> > +                    * If an NMI modified breakpoints while DR7 was saved 
> > as 0
> > +                    * (e.g. KVM guest entry), arch_install_hw_breakpoint()
> > +                    * clobbered hardware DR7. Restore val = 0 (DR7_FIXED_1)
> > +                    * to clean up the NMI's write and preserve guest 
> > isolation.
> > +                    * Otherwise, load the latest cpu_dr7 state.
> > +                    */
> > +                   val = dr7 ? this_cpu_read(cpu_dr7) : 0;
> 
> [Severity: High]
> Does this logic permanently discard the hardware breakpoint installed by an
> NMI if dr7 was initially 0?
> 
> If dr7 was saved as 0, which happens when no host breakpoints were active,
> and an NMI subsequently installs a breakpoint, this logic evaluates to 0
> and ignores the new cpu_dr7 state.
> 
> It will then write 0x400 to hardware DR7, completely overriding the NMI
> update. Furthermore, if an NMI interrupts a KVM guest, the guest's DR7
> state is completely destroyed and replaced with 0x400.

OK, then I always use cpu_dr7 for value.

Thanks,

> 
> > +           }
> > +
> > +           set_debugreg(val | DR7_FIXED_1, 7);
> > +           barrier();
> > +   } while (unlikely(seq != this_cpu_read(cpu_dr7_seq)));
> >  }
> 
> -- 
> Sashiko AI review ยท 
> https://sashiko.dev/#/patchset/178875277830.93794.14247844688761142429.stgit@devnote2?part=2


-- 
Masami Hiramatsu (Google) <[email protected]>

Reply via email to