|
@@ -122,6 +122,40 @@ void __ptrace_unlink(struct task_struct *child)
|
|
|
spin_unlock(&child->sighand->siglock);
|
|
|
}
|
|
|
|
|
|
+/* Ensure that nothing can wake it up, even SIGKILL */
|
|
|
+static bool ptrace_freeze_traced(struct task_struct *task)
|
|
|
+{
|
|
|
+ bool ret = false;
|
|
|
+
|
|
|
+ /* Lockless, nobody but us can set this flag */
|
|
|
+ if (task->jobctl & JOBCTL_LISTENING)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ spin_lock_irq(&task->sighand->siglock);
|
|
|
+ if (task_is_traced(task) && !__fatal_signal_pending(task)) {
|
|
|
+ task->state = __TASK_TRACED;
|
|
|
+ ret = true;
|
|
|
+ }
|
|
|
+ spin_unlock_irq(&task->sighand->siglock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void ptrace_unfreeze_traced(struct task_struct *task)
|
|
|
+{
|
|
|
+ if (task->state != __TASK_TRACED)
|
|
|
+ return;
|
|
|
+
|
|
|
+ WARN_ON(!task->ptrace || task->parent != current);
|
|
|
+
|
|
|
+ spin_lock_irq(&task->sighand->siglock);
|
|
|
+ if (__fatal_signal_pending(task))
|
|
|
+ wake_up_state(task, __TASK_TRACED);
|
|
|
+ else
|
|
|
+ task->state = TASK_TRACED;
|
|
|
+ spin_unlock_irq(&task->sighand->siglock);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ptrace_check_attach - check whether ptracee is ready for ptrace operation
|
|
|
* @child: ptracee to check for
|
|
@@ -151,24 +185,29 @@ static int ptrace_check_attach(struct task_struct *child, bool ignore_state)
|
|
|
* be changed by us so it's not changing right after this.
|
|
|
*/
|
|
|
read_lock(&tasklist_lock);
|
|
|
- if ((child->ptrace & PT_PTRACED) && child->parent == current) {
|
|
|
+ if (child->ptrace && child->parent == current) {
|
|
|
+ WARN_ON(child->state == __TASK_TRACED);
|
|
|
/*
|
|
|
* child->sighand can't be NULL, release_task()
|
|
|
* does ptrace_unlink() before __exit_signal().
|
|
|
*/
|
|
|
- spin_lock_irq(&child->sighand->siglock);
|
|
|
- WARN_ON_ONCE(task_is_stopped(child));
|
|
|
- if (ignore_state || (task_is_traced(child) &&
|
|
|
- !(child->jobctl & JOBCTL_LISTENING)))
|
|
|
+ if (ignore_state || ptrace_freeze_traced(child))
|
|
|
ret = 0;
|
|
|
- spin_unlock_irq(&child->sighand->siglock);
|
|
|
}
|
|
|
read_unlock(&tasklist_lock);
|
|
|
|
|
|
- if (!ret && !ignore_state)
|
|
|
- ret = wait_task_inactive(child, TASK_TRACED) ? 0 : -ESRCH;
|
|
|
+ if (!ret && !ignore_state) {
|
|
|
+ if (!wait_task_inactive(child, __TASK_TRACED)) {
|
|
|
+ /*
|
|
|
+ * This can only happen if may_ptrace_stop() fails and
|
|
|
+ * ptrace_stop() changes ->state back to TASK_RUNNING,
|
|
|
+ * so we should not worry about leaking __TASK_TRACED.
|
|
|
+ */
|
|
|
+ WARN_ON(child->state == __TASK_TRACED);
|
|
|
+ ret = -ESRCH;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- /* All systems go.. */
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -900,6 +939,8 @@ SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
|
|
|
goto out_put_task_struct;
|
|
|
|
|
|
ret = arch_ptrace(child, request, addr, data);
|
|
|
+ if (ret || request != PTRACE_DETACH)
|
|
|
+ ptrace_unfreeze_traced(child);
|
|
|
|
|
|
out_put_task_struct:
|
|
|
put_task_struct(child);
|
|
@@ -1039,8 +1080,11 @@ asmlinkage long compat_sys_ptrace(compat_long_t request, compat_long_t pid,
|
|
|
|
|
|
ret = ptrace_check_attach(child, request == PTRACE_KILL ||
|
|
|
request == PTRACE_INTERRUPT);
|
|
|
- if (!ret)
|
|
|
+ if (!ret) {
|
|
|
ret = compat_arch_ptrace(child, request, addr, data);
|
|
|
+ if (ret || request != PTRACE_DETACH)
|
|
|
+ ptrace_unfreeze_traced(child);
|
|
|
+ }
|
|
|
|
|
|
out_put_task_struct:
|
|
|
put_task_struct(child);
|