[PATCH v2] MIPS: Avoid to cause watchpoint exception in kernel mode

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Heiher <r@xxxxxx>

I found an operation can trigger a kernel hang, when we write an user-space
address to hardward watchpoints before access this address in kernel mode.

Looks the problem is the current thread can't return to user mode because memory
accesses blocked by hardward watchpoints. so SIGTRAP not handled and the debugger
can't receive watchpoints event.

Execution flow:
1. write address to cp0 watchpoints.
2. access this address in kernel mode, then an exception triggered by watchpoints.
3. goto do_watch() to handle exception.
4. Force a SIGTRAP to current thread.
5. exception return, goto step 2.

If we clear the watchpoints in do_watch() for exceptions from kernel mode,
and re-install watchpoints on thread return to user mode, it'll works fine.

Test case:
	#include <stdio.h>
	#include <unistd.h>
	#include <signal.h>

	int
	main (int argc, char *argv[])
	{
		char buf[16];

		printf ("%p\n", buf);
		raise (SIGINT);

		write (1, buf, 16);

		return 0;
	}

	# gcc -O0 -o t t.c
	# gdb ./t
	(gdb) r
	(gdb) watch *<printed buf address>
	(gdb) c

Signed-off-by: Heiher <r@xxxxxx>
---
 arch/mips/kernel/entry.S | 23 +++++++++++++++++++++++
 arch/mips/kernel/traps.c |  2 +-
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/arch/mips/kernel/entry.S b/arch/mips/kernel/entry.S
index 38a302919e6b..6094844fc63f 100644
--- a/arch/mips/kernel/entry.S
+++ b/arch/mips/kernel/entry.S
@@ -49,6 +49,13 @@ resume_userspace:
 					# interrupt setting need_resched
 					# between sampling and return
 	LONG_L	a2, TI_FLAGS($28)	# current->work
+	li	t0, _TIF_LOAD_WATCH
+	and	t0, a2
+	beqz	t0, 1f
+	move	a0, $28
+	jal	mips_install_watch_registers
+	LONG_L	a2, TI_FLAGS($28)	# current->work
+1:
 	andi	t0, a2, _TIF_WORK_MASK	# (ignoring syscall_trace)
 	bnez	t0, work_pending
 	j	restore_all
@@ -82,7 +89,15 @@ FEXPORT(syscall_exit)
 	local_irq_disable		# make sure need_resched and
 					# signals dont change between
 					# sampling and return
+
+	LONG_L	a2, TI_FLAGS($28)	# current->work
+	li	t0, _TIF_LOAD_WATCH
+	and	t0, a2
+	beqz	t0, 1f
+	move	a0, $28
+	jal	mips_install_watch_registers
 	LONG_L	a2, TI_FLAGS($28)	# current->work
+1:
 	li	t0, _TIF_ALLWORK_MASK
 	and	t0, a2, t0
 	bnez	t0, syscall_exit_work
@@ -143,7 +158,15 @@ work_notifysig:				# deal with pending signals and
 FEXPORT(syscall_exit_partial)
 	local_irq_disable		# make sure need_resched doesn't
 					# change between and return
+
+	LONG_L	a2, TI_FLAGS($28)	# current->work
+	li	t0, _TIF_LOAD_WATCH
+	and	t0, a2
+	beqz	t0, 1f
+	move	a0, $28
+	jal	mips_install_watch_registers
 	LONG_L	a2, TI_FLAGS($28)	# current->work
+1:
 	li	t0, _TIF_ALLWORK_MASK
 	and	t0, a2
 	beqz	t0, restore_partial
diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c
index 967e9e4e795e..22f671263b27 100644
--- a/arch/mips/kernel/traps.c
+++ b/arch/mips/kernel/traps.c
@@ -1525,7 +1525,7 @@ asmlinkage void do_watch(struct pt_regs *regs)
 	 * their values and send SIGTRAP.  Otherwise another thread
 	 * left the registers set, clear them and continue.
 	 */
-	if (test_tsk_thread_flag(current, TIF_LOAD_WATCH)) {
+	if (user_mode(regs) && test_tsk_thread_flag(current, TIF_LOAD_WATCH)) {
 		mips_read_watch_registers();
 		local_irq_enable();
 		force_sig_info(SIGTRAP, &info, current);
-- 
2.16.3



[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux