signal: Fix premature completion of group stop when interfered by ptrace
authorTejun Heo <tj@kernel.org>
Wed, 23 Mar 2011 09:37:00 +0000 (10:37 +0100)
committerTejun Heo <tj@kernel.org>
Wed, 23 Mar 2011 09:37:00 +0000 (10:37 +0100)
commite5c1902e9260a0075ea52cb5ef627a8d9aaede89
tree0fc5ec2460fecfe564323d89227bec9293238c2a
parentfe1bc6a0954611b806f9e158eb0817cf8ba21660
signal: Fix premature completion of group stop when interfered by ptrace

task->signal->group_stop_count is used to track the progress of group
stop.  It's initialized to the number of tasks which need to stop for
group stop to finish and each stopping or trapping task decrements.
However, each task doesn't keep track of whether it decremented the
counter or not and if woken up before the group stop is complete and
stops again, it can decrement the counter multiple times.

Please consider the following example code.

 static void *worker(void *arg)
 {
 while (1) ;
 return NULL;
 }

 int main(void)
 {
 pthread_t thread;
 pid_t pid;
 int i;

 pid = fork();
 if (!pid) {
 for (i = 0; i < 5; i++)
 pthread_create(&thread, NULL, worker, NULL);
 while (1) ;
 return 0;
 }

 ptrace(PTRACE_ATTACH, pid, NULL, NULL);
 while (1) {
 waitid(P_PID, pid, NULL, WSTOPPED);
 ptrace(PTRACE_SINGLESTEP, pid, NULL, (void *)(long)SIGSTOP);
 }
 return 0;
 }

The child creates five threads and the parent continuously traps the
first thread and whenever the child gets a signal, SIGSTOP is
delivered.  If an external process sends SIGSTOP to the child, all
other threads in the process should reliably stop.  However, due to
the above bug, the first thread will often end up consuming
group_stop_count multiple times and SIGSTOP often ends up stopping
none or part of the other four threads.

This patch adds a new field task->group_stop which is protected by
siglock and uses GROUP_STOP_CONSUME flag to track which task is still
to consume group_stop_count to fix this bug.

task_clear_group_stop_pending() and task_participate_group_stop() are
added to help manipulating group stop states.  As ptrace_stop() now
also uses task_participate_group_stop(), it will set
SIGNAL_STOP_STOPPED if it completes a group stop.

There still are many issues regarding the interaction between group
stop and ptrace.  Patches to address them will follow.

- Oleg spotted duplicate GROUP_STOP_CONSUME.  Dropped.

Signed-off-by: Tejun Heo <tj@kernel.org>
Acked-by: Oleg Nesterov <oleg@redhat.com>
Cc: Roland McGrath <roland@redhat.com>
include/linux/sched.h
kernel/signal.c