From: Andy Lutomirski <l...@kernel.org>

commit d5c678aed5eddb944b8e7ce451b107b39245962d upstream.

Trying to clear DR7 around a #DB from usermode malfunctions if the tasks
schedules when delivering SIGTRAP.

Rather than trying to define a special no-recursion region, just allow a
single level of recursion.  The same mechanism is used for NMI, and it
hasn't caused any problems yet.

Fixes: 9f58fdde95c9 ("x86/db: Split out dr6/7 handling")
Reported-by: Kyle Huey <m...@kylehuey.com>
Debugged-by: Josh Poimboeuf <jpoim...@redhat.com>
Signed-off-by: Andy Lutomirski <l...@kernel.org>
Signed-off-by: Peter Zijlstra (Intel) <pet...@infradead.org>
Signed-off-by: Thomas Gleixner <t...@linutronix.de>
Tested-by: Daniel Thompson <daniel.thomp...@linaro.org>
Cc: sta...@vger.kernel.org
Link: 
https://lkml.kernel.org/r/8b9bd05f187231df008d48cf818a6a311cbd5c98.1597882384.git.l...@kernel.org
Link: https://lore.kernel.org/r/20200902133200.726584...@infradead.org
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>

---
 arch/x86/kernel/traps.c |   66 +++++++++++++++++++++++-------------------------
 1 file changed, 32 insertions(+), 34 deletions(-)

--- a/arch/x86/kernel/traps.c
+++ b/arch/x86/kernel/traps.c
@@ -733,20 +733,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:
@@ -759,15 +748,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;
 }
 
 /*
@@ -867,6 +853,19 @@ out:
 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();
+
        nmi_enter();
        instrumentation_begin();
        trace_hardirqs_off_finish();
@@ -890,6 +889,8 @@ static __always_inline void exc_debug_ke
                trace_hardirqs_on_prepare();
        instrumentation_end();
        nmi_exit();
+
+       local_db_restore(dr7);
 }
 
 static __always_inline void exc_debug_user(struct pt_regs *regs,
@@ -901,6 +902,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.
+        */
+
        idtentry_enter_user(regs);
        instrumentation_begin();
 
@@ -913,36 +923,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
 


Reply via email to