powerpc/watchpoint: Fix exception handling for CONFIG_HAVE_HW_BREAKPOINT=N
authorRavi Bangoria <ravi.bangoria@linux.ibm.com>
Wed, 2 Sep 2020 04:29:42 +0000 (09:59 +0530)
committerMichael Ellerman <mpe@ellerman.id.au>
Tue, 15 Sep 2020 12:13:20 +0000 (22:13 +1000)
On powerpc, ptrace watchpoint works in one-shot mode. i.e. kernel
disables event every time it fires and user has to re-enable it.
Also, in case of ptrace watchpoint, kernel notifies ptrace user
before executing instruction.

With CONFIG_HAVE_HW_BREAKPOINT=N, kernel is missing to disable
ptrace event and thus it's causing infinite loop of exceptions.
This is especially harmful when user watches on a data which is
also read/written by kernel, eg syscall parameters. In such case,
infinite exceptions happens in kernel mode which causes soft-lockup.

Fixes: 9422de3e953d ("powerpc: Hardware breakpoints rewrite to handle non DABR breakpoint registers")
Reported-by: Pedro Miraglia Franco de Carvalho <pedromfc@linux.ibm.com>
Signed-off-by: Ravi Bangoria <ravi.bangoria@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20200902042945.129369-6-ravi.bangoria@linux.ibm.com
arch/powerpc/include/asm/hw_breakpoint.h
arch/powerpc/kernel/process.c
arch/powerpc/kernel/ptrace/ptrace-noadv.c

index 81872c420476330ce8a3b3e5a02523d636d8411d..abebfbee5b1c3b74e7a8b117a0b7d04f889c7c4a 100644 (file)
@@ -18,6 +18,7 @@ struct arch_hw_breakpoint {
        u16             type;
        u16             len; /* length of the target data symbol */
        u16             hw_len; /* length programmed in hw */
+       u8              flags;
 };
 
 /* Note: Don't change the first 6 bits below as they are in the same order
@@ -37,6 +38,8 @@ struct arch_hw_breakpoint {
 #define HW_BRK_TYPE_PRIV_ALL   (HW_BRK_TYPE_USER | HW_BRK_TYPE_KERNEL | \
                                 HW_BRK_TYPE_HYP)
 
+#define HW_BRK_FLAG_DISABLED   0x1
+
 /* Minimum granularity */
 #ifdef CONFIG_PPC_8xx
 #define HW_BREAKPOINT_SIZE  0x4
index 142680e885ad00a4ad1fb7e78f6985b27b5f09da..483e36a42617632ca8e18d0c8845a306bafc064f 100644 (file)
@@ -642,6 +642,44 @@ void do_send_trap(struct pt_regs *regs, unsigned long address,
                                    (void __user *)address);
 }
 #else  /* !CONFIG_PPC_ADV_DEBUG_REGS */
+
+static void do_break_handler(struct pt_regs *regs)
+{
+       struct arch_hw_breakpoint null_brk = {0};
+       struct arch_hw_breakpoint *info;
+       struct ppc_inst instr = ppc_inst(0);
+       int type = 0;
+       int size = 0;
+       unsigned long ea;
+       int i;
+
+       /*
+        * If underneath hw supports only one watchpoint, we know it
+        * caused exception. 8xx also falls into this category.
+        */
+       if (nr_wp_slots() == 1) {
+               __set_breakpoint(0, &null_brk);
+               current->thread.hw_brk[0] = null_brk;
+               current->thread.hw_brk[0].flags |= HW_BRK_FLAG_DISABLED;
+               return;
+       }
+
+       /* Otherwise findout which DAWR caused exception and disable it. */
+       wp_get_instr_detail(regs, &instr, &type, &size, &ea);
+
+       for (i = 0; i < nr_wp_slots(); i++) {
+               info = &current->thread.hw_brk[i];
+               if (!info->address)
+                       continue;
+
+               if (wp_check_constraints(regs, instr, ea, type, size, info)) {
+                       __set_breakpoint(i, &null_brk);
+                       current->thread.hw_brk[i] = null_brk;
+                       current->thread.hw_brk[i].flags |= HW_BRK_FLAG_DISABLED;
+               }
+       }
+}
+
 void do_break (struct pt_regs *regs, unsigned long address,
                    unsigned long error_code)
 {
@@ -653,6 +691,16 @@ void do_break (struct pt_regs *regs, unsigned long address,
        if (debugger_break_match(regs))
                return;
 
+       /*
+        * We reach here only when watchpoint exception is generated by ptrace
+        * event (or hw is buggy!). Now if CONFIG_HAVE_HW_BREAKPOINT is set,
+        * watchpoint is already handled by hw_breakpoint_handler() so we don't
+        * have to do anything. But when CONFIG_HAVE_HW_BREAKPOINT is not set,
+        * we need to manually handle the watchpoint here.
+        */
+       if (!IS_ENABLED(CONFIG_HAVE_HW_BREAKPOINT))
+               do_break_handler(regs);
+
        /* Deliver the signal to userspace */
        force_sig_fault(SIGTRAP, TRAP_HWBKPT, (void __user *)address);
 }
index 57a0ab822334f5c0d103ec9a9c13a3470a56d26c..c9122ed9134024de3651d8aa6ceae73e40353c83 100644 (file)
@@ -286,11 +286,13 @@ long ppc_del_hwdebug(struct task_struct *child, long data)
        }
        return ret;
 #else /* CONFIG_HAVE_HW_BREAKPOINT */
-       if (child->thread.hw_brk[data - 1].address == 0)
+       if (!(child->thread.hw_brk[data - 1].flags & HW_BRK_FLAG_DISABLED) &&
+           child->thread.hw_brk[data - 1].address == 0)
                return -ENOENT;
 
        child->thread.hw_brk[data - 1].address = 0;
        child->thread.hw_brk[data - 1].type = 0;
+       child->thread.hw_brk[data - 1].flags = 0;
 #endif /* CONFIG_HAVE_HW_BREAKPOINT */
 
        return 0;