| From 87dc669ba25777b67796d7262c569429e58b1ed4 Mon Sep 17 00:00:00 2001 |
| From: Frederic Weisbecker <fweisbec@gmail.com> |
| Date: Fri, 8 Apr 2011 17:29:36 +0200 |
| Subject: x86, hw_breakpoints: Fix racy access to ptrace breakpoints |
| |
| From: Frederic Weisbecker <fweisbec@gmail.com> |
| |
| commit 87dc669ba25777b67796d7262c569429e58b1ed4 upstream. |
| |
| While the tracer accesses ptrace breakpoints, the child task may |
| concurrently exit due to a SIGKILL and thus release its breakpoints |
| at the same time. We can then dereference some freed pointers. |
| |
| To fix this, hold a reference on the child breakpoints before |
| manipulating them. |
| |
| Reported-by: Oleg Nesterov <oleg@redhat.com> |
| Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> |
| Cc: Ingo Molnar <mingo@elte.hu> |
| Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> |
| Cc: Will Deacon <will.deacon@arm.com> |
| Cc: Prasad <prasad@linux.vnet.ibm.com> |
| Cc: Paul Mundt <lethal@linux-sh.org> |
| Link: http://lkml.kernel.org/r/1302284067-7860-3-git-send-email-fweisbec@gmail.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| |
| --- |
| arch/x86/kernel/ptrace.c | 36 ++++++++++++++++++++++++++---------- |
| 1 file changed, 26 insertions(+), 10 deletions(-) |
| |
| --- a/arch/x86/kernel/ptrace.c |
| +++ b/arch/x86/kernel/ptrace.c |
| @@ -635,6 +635,9 @@ static int ptrace_write_dr7(struct task_ |
| unsigned len, type; |
| struct perf_event *bp; |
| |
| + if (ptrace_get_breakpoints(tsk) < 0) |
| + return -ESRCH; |
| + |
| data &= ~DR_CONTROL_RESERVED; |
| old_dr7 = ptrace_get_dr7(thread->ptrace_bps); |
| restore: |
| @@ -682,6 +685,9 @@ restore: |
| } |
| goto restore; |
| } |
| + |
| + ptrace_put_breakpoints(tsk); |
| + |
| return ((orig_ret < 0) ? orig_ret : rc); |
| } |
| |
| @@ -695,10 +701,17 @@ static unsigned long ptrace_get_debugreg |
| |
| if (n < HBP_NUM) { |
| struct perf_event *bp; |
| + |
| + if (ptrace_get_breakpoints(tsk) < 0) |
| + return -ESRCH; |
| + |
| bp = thread->ptrace_bps[n]; |
| if (!bp) |
| - return 0; |
| - val = bp->hw.info.address; |
| + val = 0; |
| + else |
| + val = bp->hw.info.address; |
| + |
| + ptrace_put_breakpoints(tsk); |
| } else if (n == 6) { |
| val = thread->debugreg6; |
| } else if (n == 7) { |
| @@ -713,6 +726,10 @@ static int ptrace_set_breakpoint_addr(st |
| struct perf_event *bp; |
| struct thread_struct *t = &tsk->thread; |
| struct perf_event_attr attr; |
| + int err = 0; |
| + |
| + if (ptrace_get_breakpoints(tsk) < 0) |
| + return -ESRCH; |
| |
| if (!t->ptrace_bps[nr]) { |
| hw_breakpoint_init(&attr); |
| @@ -736,24 +753,23 @@ static int ptrace_set_breakpoint_addr(st |
| * writing for the user. And anyway this is the previous |
| * behaviour. |
| */ |
| - if (IS_ERR(bp)) |
| - return PTR_ERR(bp); |
| + if (IS_ERR(bp)) { |
| + err = PTR_ERR(bp); |
| + goto put; |
| + } |
| |
| t->ptrace_bps[nr] = bp; |
| } else { |
| - int err; |
| - |
| bp = t->ptrace_bps[nr]; |
| |
| attr = bp->attr; |
| attr.bp_addr = addr; |
| err = modify_user_hw_breakpoint(bp, &attr); |
| - if (err) |
| - return err; |
| } |
| |
| - |
| - return 0; |
| +put: |
| + ptrace_put_breakpoints(tsk); |
| + return err; |
| } |
| |
| /* |