From: Andy Lutomirski luto@kernel.org
Trying to clear DR7 around a #DB from usermode malfunctions if we schedule when delivering SIGTRAP. Rather than trying to define a special no-recursion region, just allow a single level of recursion. We do the same thing for NMI, and it hasn't caused any problems yet.
Fixes: 9f58fdde95c9 ("x86/db: Split out dr6/7 handling") Reported-by: Kyle Huey me@kylehuey.com Debugged-by: Josh Poimboeuf jpoimboe@redhat.com Signed-off-by: Andy Lutomirski luto@kernel.org Signed-off-by: Peter Zijlstra (Intel) peterz@infradead.org Cc: stable@vger.kernel.org Link: https://lkml.kernel.org/r/8b9bd05f187231df008d48cf818a6a311cbd5c98.159788238... --- arch/x86/kernel/traps.c | 65 ++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 34 deletions(-)
--- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -729,20 +729,9 @@ static bool is_sysenter_singlestep(struc #endif }
-static __always_inline void debug_enter(unsigned long *dr6, unsigned long *dr7) +static __always_inline unsigned long debug_read_clear_dr6(void) { - /* - * Disable breakpoints during exception handling; recursive exceptions - * are exceedingly 'fun'. - * - * Since this function is NOKPROBE, and that also applies to - * HW_BREAKPOINT_X, we can't hit a breakpoint before this (XXX except a - * HW_BREAKPOINT_W on our stack) - * - * Entry text is excluded for HW_BP_X and cpu_entry_area, which - * includes the entry stack is excluded for everything. - */ - *dr7 = local_db_save(); + unsigned long dr6;
/* * The Intel SDM says: @@ -755,15 +744,12 @@ static __always_inline void debug_enter( * * Keep it simple: clear DR6 immediately. */ - get_debugreg(*dr6, 6); + get_debugreg(dr6, 6); set_debugreg(0, 6); /* Filter out all the reserved bits which are preset to 1 */ - *dr6 &= ~DR6_RESERVED; -} + dr6 &= ~DR6_RESERVED;
-static __always_inline void debug_exit(unsigned long dr7) -{ - local_db_restore(dr7); + return dr6; }
/* @@ -863,6 +849,18 @@ static void handle_debug(struct pt_regs static __always_inline void exc_debug_kernel(struct pt_regs *regs, unsigned long dr6) { + /* + * Disable breakpoints during exception handling; recursive exceptions + * are exceedingly 'fun'. + * + * Since this function is NOKPROBE, and that also applies to + * HW_BREAKPOINT_X, we can't hit a breakpoint before this (XXX except a + * HW_BREAKPOINT_W on our stack) + * + * Entry text is excluded for HW_BP_X and cpu_entry_area, which + * includes the entry stack is excluded for everything. + */ + unsigned long dr7 = local_db_save(); bool irq_state = idtentry_enter_nmi(regs); instrumentation_begin();
@@ -883,6 +881,8 @@ static __always_inline void exc_debug_ke
instrumentation_end(); idtentry_exit_nmi(regs, irq_state); + + local_db_restore(dr7); }
static __always_inline void exc_debug_user(struct pt_regs *regs, @@ -894,6 +894,15 @@ static __always_inline void exc_debug_us */ WARN_ON_ONCE(!user_mode(regs));
+ /* + * NB: We can't easily clear DR7 here because + * idtentry_exit_to_usermode() can invoke ptrace, schedule, access + * user memory, etc. This means that a recursive #DB is possible. If + * this happens, that #DB will hit exc_debug_kernel() and clear DR7. + * Since we're not on the IST stack right now, everything will be + * fine. + */ + irqentry_enter_from_user_mode(regs); instrumentation_begin();
@@ -907,36 +916,24 @@ static __always_inline void exc_debug_us /* IST stack entry */ DEFINE_IDTENTRY_DEBUG(exc_debug) { - unsigned long dr6, dr7; - - debug_enter(&dr6, &dr7); - exc_debug_kernel(regs, dr6); - debug_exit(dr7); + exc_debug_kernel(regs, debug_read_clear_dr6()); }
/* User entry, runs on regular task stack */ DEFINE_IDTENTRY_DEBUG_USER(exc_debug) { - unsigned long dr6, dr7; - - debug_enter(&dr6, &dr7); - exc_debug_user(regs, dr6); - debug_exit(dr7); + exc_debug_user(regs, debug_read_clear_dr6()); } #else /* 32 bit does not have separate entry points. */ DEFINE_IDTENTRY_RAW(exc_debug) { - unsigned long dr6, dr7; - - debug_enter(&dr6, &dr7); + unsigned long dr6 = debug_read_clear_dr6();
if (user_mode(regs)) exc_debug_user(regs, dr6); else exc_debug_kernel(regs, dr6); - - debug_exit(dr7); } #endif
Hi
[This is an automated email]
This commit has been processed because it contains a "Fixes:" tag fixing commit: 9f58fdde95c9 ("x86/db: Split out dr6/7 handling").
The bot has tested the following trees: v5.8.5.
v5.8.5: Failed to apply! Possible dependencies: 0b085e68f407 ("x86/entry: Consolidate 32/64 bit syscall entry") 27d6b4d14f5c ("x86/entry: Use generic syscall entry function") 517e499227be ("x86/entry: Cleanup idtentry_entry/exit_user") 8d5ea35c5e91 ("x86/entry: Consolidate check_user_regs()") a377ac1cd9d7 ("x86/entry: Move user return notifier out of loop") b037b09b9058 ("x86/entry: Rename idtentry_enter/exit_cond_rcu() to idtentry_enter/exit()") ba1f2b2eaa2a ("x86/entry: Fix NMI vs IRQ state tracking")
NOTE: The patch will not be queued to stable trees until it is upstream.
How should we proceed with this patch?
On Wed, Sep 02, 2020 at 03:25:51PM +0200, Peter Zijlstra wrote:
@@ -863,6 +849,18 @@ static void handle_debug(struct pt_regs static __always_inline void exc_debug_kernel(struct pt_regs *regs, unsigned long dr6) {
- /*
* Disable breakpoints during exception handling; recursive exceptions
* are exceedingly 'fun'.
*
* Since this function is NOKPROBE, and that also applies to
* HW_BREAKPOINT_X, we can't hit a breakpoint before this (XXX except a
* HW_BREAKPOINT_W on our stack)
*
* Entry text is excluded for HW_BP_X and cpu_entry_area, which
* includes the entry stack is excluded for everything.
*/
I know this comment was copy/pasted, but I had to stare at the last paragraph like one of those 3D paintings they used to have at the mall.
Recommended rewording:
* HW_BREAKPOINT_X is disallowed for entry text; all breakpoints * are disallowed for cpu_entry_area (which includes the entry * stack).
linux-stable-mirror@lists.linaro.org