|
@@ -697,6 +697,8 @@ static int ptrace_peek_siginfo(struct task_struct *child,
|
|
|
static int ptrace_resume(struct task_struct *child, long request,
|
|
|
unsigned long data)
|
|
|
{
|
|
|
+ bool need_siglock;
|
|
|
+
|
|
|
if (!valid_signal(data))
|
|
|
return -EIO;
|
|
|
|
|
@@ -724,8 +726,26 @@ static int ptrace_resume(struct task_struct *child, long request,
|
|
|
user_disable_single_step(child);
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * Change ->exit_code and ->state under siglock to avoid the race
|
|
|
+ * with wait_task_stopped() in between; a non-zero ->exit_code will
|
|
|
+ * wrongly look like another report from tracee.
|
|
|
+ *
|
|
|
+ * Note that we need siglock even if ->exit_code == data and/or this
|
|
|
+ * status was not reported yet, the new status must not be cleared by
|
|
|
+ * wait_task_stopped() after resume.
|
|
|
+ *
|
|
|
+ * If data == 0 we do not care if wait_task_stopped() reports the old
|
|
|
+ * status and clears the code too; this can't race with the tracee, it
|
|
|
+ * takes siglock after resume.
|
|
|
+ */
|
|
|
+ need_siglock = data && !thread_group_empty(current);
|
|
|
+ if (need_siglock)
|
|
|
+ spin_lock_irq(&child->sighand->siglock);
|
|
|
child->exit_code = data;
|
|
|
wake_up_state(child, __TASK_TRACED);
|
|
|
+ if (need_siglock)
|
|
|
+ spin_unlock_irq(&child->sighand->siglock);
|
|
|
|
|
|
return 0;
|
|
|
}
|