Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

powerpc/watchpoint: Fix exception handling for CONFIG_HAVE_HW_BREAKPOINT=N

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

authored by

Ravi Bangoria and committed by
Michael Ellerman
5b905d77 edc8dd99

+54 -1
+3
arch/powerpc/include/asm/hw_breakpoint.h
··· 18 18 u16 type; 19 19 u16 len; /* length of the target data symbol */ 20 20 u16 hw_len; /* length programmed in hw */ 21 + u8 flags; 21 22 }; 22 23 23 24 /* Note: Don't change the first 6 bits below as they are in the same order ··· 37 36 #define HW_BRK_TYPE_DABR (HW_BRK_TYPE_RDWR | HW_BRK_TYPE_TRANSLATE) 38 37 #define HW_BRK_TYPE_PRIV_ALL (HW_BRK_TYPE_USER | HW_BRK_TYPE_KERNEL | \ 39 38 HW_BRK_TYPE_HYP) 39 + 40 + #define HW_BRK_FLAG_DISABLED 0x1 40 41 41 42 /* Minimum granularity */ 42 43 #ifdef CONFIG_PPC_8xx
+48
arch/powerpc/kernel/process.c
··· 642 642 (void __user *)address); 643 643 } 644 644 #else /* !CONFIG_PPC_ADV_DEBUG_REGS */ 645 + 646 + static void do_break_handler(struct pt_regs *regs) 647 + { 648 + struct arch_hw_breakpoint null_brk = {0}; 649 + struct arch_hw_breakpoint *info; 650 + struct ppc_inst instr = ppc_inst(0); 651 + int type = 0; 652 + int size = 0; 653 + unsigned long ea; 654 + int i; 655 + 656 + /* 657 + * If underneath hw supports only one watchpoint, we know it 658 + * caused exception. 8xx also falls into this category. 659 + */ 660 + if (nr_wp_slots() == 1) { 661 + __set_breakpoint(0, &null_brk); 662 + current->thread.hw_brk[0] = null_brk; 663 + current->thread.hw_brk[0].flags |= HW_BRK_FLAG_DISABLED; 664 + return; 665 + } 666 + 667 + /* Otherwise findout which DAWR caused exception and disable it. */ 668 + wp_get_instr_detail(regs, &instr, &type, &size, &ea); 669 + 670 + for (i = 0; i < nr_wp_slots(); i++) { 671 + info = &current->thread.hw_brk[i]; 672 + if (!info->address) 673 + continue; 674 + 675 + if (wp_check_constraints(regs, instr, ea, type, size, info)) { 676 + __set_breakpoint(i, &null_brk); 677 + current->thread.hw_brk[i] = null_brk; 678 + current->thread.hw_brk[i].flags |= HW_BRK_FLAG_DISABLED; 679 + } 680 + } 681 + } 682 + 645 683 void do_break (struct pt_regs *regs, unsigned long address, 646 684 unsigned long error_code) 647 685 { ··· 690 652 691 653 if (debugger_break_match(regs)) 692 654 return; 655 + 656 + /* 657 + * We reach here only when watchpoint exception is generated by ptrace 658 + * event (or hw is buggy!). Now if CONFIG_HAVE_HW_BREAKPOINT is set, 659 + * watchpoint is already handled by hw_breakpoint_handler() so we don't 660 + * have to do anything. But when CONFIG_HAVE_HW_BREAKPOINT is not set, 661 + * we need to manually handle the watchpoint here. 662 + */ 663 + if (!IS_ENABLED(CONFIG_HAVE_HW_BREAKPOINT)) 664 + do_break_handler(regs); 693 665 694 666 /* Deliver the signal to userspace */ 695 667 force_sig_fault(SIGTRAP, TRAP_HWBKPT, (void __user *)address);
+3 -1
arch/powerpc/kernel/ptrace/ptrace-noadv.c
··· 286 286 } 287 287 return ret; 288 288 #else /* CONFIG_HAVE_HW_BREAKPOINT */ 289 - if (child->thread.hw_brk[data - 1].address == 0) 289 + if (!(child->thread.hw_brk[data - 1].flags & HW_BRK_FLAG_DISABLED) && 290 + child->thread.hw_brk[data - 1].address == 0) 290 291 return -ENOENT; 291 292 292 293 child->thread.hw_brk[data - 1].address = 0; 293 294 child->thread.hw_brk[data - 1].type = 0; 295 + child->thread.hw_brk[data - 1].flags = 0; 294 296 #endif /* CONFIG_HAVE_HW_BREAKPOINT */ 295 297 296 298 return 0;