| From 5402e97af667e35e54177af8f6575518bf251d51 Mon Sep 17 00:00:00 2001 |
| From: "bsegall@google.com" <bsegall@google.com> |
| Date: Fri, 7 Apr 2017 16:04:51 -0700 |
| Subject: ptrace: fix PTRACE_LISTEN race corrupting task->state |
| |
| From: bsegall@google.com <bsegall@google.com> |
| |
| commit 5402e97af667e35e54177af8f6575518bf251d51 upstream. |
| |
| In PT_SEIZED + LISTEN mode STOP/CONT signals cause a wakeup against |
| __TASK_TRACED. If this races with the ptrace_unfreeze_traced at the end |
| of a PTRACE_LISTEN, this can wake the task /after/ the check against |
| __TASK_TRACED, but before the reset of state to TASK_TRACED. This |
| causes it to instead clobber TASK_WAKING, allowing a subsequent wakeup |
| against TRACED while the task is still on the rq wake_list, corrupting |
| it. |
| |
| Oleg said: |
| "The kernel can crash or this can lead to other hard-to-debug problems. |
| In short, "task->state = TASK_TRACED" in ptrace_unfreeze_traced() |
| assumes that nobody else can wake it up, but PTRACE_LISTEN breaks the |
| contract. Obviusly it is very wrong to manipulate task->state if this |
| task is already running, or WAKING, or it sleeps again" |
| |
| [akpm@linux-foundation.org: coding-style fixes] |
| Fixes: 9899d11f ("ptrace: ensure arch_ptrace/ptrace_request can never race with SIGKILL") |
| Link: http://lkml.kernel.org/r/xm26y3vfhmkp.fsf_-_@bsegall-linux.mtv.corp.google.com |
| Signed-off-by: Ben Segall <bsegall@google.com> |
| Acked-by: Oleg Nesterov <oleg@redhat.com> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| kernel/ptrace.c | 14 ++++++++++---- |
| 1 file changed, 10 insertions(+), 4 deletions(-) |
| |
| --- a/kernel/ptrace.c |
| +++ b/kernel/ptrace.c |
| @@ -151,11 +151,17 @@ static void ptrace_unfreeze_traced(struc |
| |
| WARN_ON(!task->ptrace || task->parent != current); |
| |
| + /* |
| + * PTRACE_LISTEN can allow ptrace_trap_notify to wake us up remotely. |
| + * Recheck state under the lock to close this race. |
| + */ |
| spin_lock_irq(&task->sighand->siglock); |
| - if (__fatal_signal_pending(task)) |
| - wake_up_state(task, __TASK_TRACED); |
| - else |
| - task->state = TASK_TRACED; |
| + if (task->state == __TASK_TRACED) { |
| + if (__fatal_signal_pending(task)) |
| + wake_up_state(task, __TASK_TRACED); |
| + else |
| + task->state = TASK_TRACED; |
| + } |
| spin_unlock_irq(&task->sighand->siglock); |
| } |
| |