Skip to content

Commit e5c1902

Browse files
committed
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>
1 parent fe1bc6a commit e5c1902

File tree

2 files changed

+60
-8
lines changed

2 files changed

+60
-8
lines changed

include/linux/sched.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,7 @@ struct task_struct {
12601260
int exit_state;
12611261
int exit_code, exit_signal;
12621262
int pdeath_signal; /* The signal sent when the parent dies */
1263+
unsigned int group_stop; /* GROUP_STOP_*, siglock protected */
12631264
/* ??? */
12641265
unsigned int personality;
12651266
unsigned did_exec:1;
@@ -1771,6 +1772,11 @@ extern void thread_group_times(struct task_struct *p, cputime_t *ut, cputime_t *
17711772
#define tsk_used_math(p) ((p)->flags & PF_USED_MATH)
17721773
#define used_math() tsk_used_math(current)
17731774

1775+
/*
1776+
* task->group_stop flags
1777+
*/
1778+
#define GROUP_STOP_CONSUME (1 << 17) /* consume group stop count */
1779+
17741780
#ifdef CONFIG_PREEMPT_RCU
17751781

17761782
#define RCU_READ_UNLOCK_BLOCKED (1 << 0) /* blocked while in RCU read-side. */

kernel/signal.c

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,52 @@ static inline void print_dropped_signal(int sig)
223223
current->comm, current->pid, sig);
224224
}
225225

226+
/**
227+
* task_clear_group_stop_pending - clear pending group stop
228+
* @task: target task
229+
*
230+
* Clear group stop states for @task.
231+
*
232+
* CONTEXT:
233+
* Must be called with @task->sighand->siglock held.
234+
*/
235+
static void task_clear_group_stop_pending(struct task_struct *task)
236+
{
237+
task->group_stop &= ~GROUP_STOP_CONSUME;
238+
}
239+
240+
/**
241+
* task_participate_group_stop - participate in a group stop
242+
* @task: task participating in a group stop
243+
*
244+
* @task is participating in a group stop. Group stop states are cleared
245+
* and the group stop count is consumed if %GROUP_STOP_CONSUME was set. If
246+
* the consumption completes the group stop, the appropriate %SIGNAL_*
247+
* flags are set.
248+
*
249+
* CONTEXT:
250+
* Must be called with @task->sighand->siglock held.
251+
*/
252+
static bool task_participate_group_stop(struct task_struct *task)
253+
{
254+
struct signal_struct *sig = task->signal;
255+
bool consume = task->group_stop & GROUP_STOP_CONSUME;
256+
257+
task_clear_group_stop_pending(task);
258+
259+
if (!consume)
260+
return false;
261+
262+
if (!WARN_ON_ONCE(sig->group_stop_count == 0))
263+
sig->group_stop_count--;
264+
265+
if (!sig->group_stop_count) {
266+
sig->flags = SIGNAL_STOP_STOPPED;
267+
return true;
268+
}
269+
return false;
270+
}
271+
226272
/*
227273
* allocate a new signal queue record
228274
* - this may be called without locks if and only if t == current, otherwise an
@@ -1645,7 +1691,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
16451691
* we must participate in the bookkeeping.
16461692
*/
16471693
if (current->signal->group_stop_count > 0)
1648-
--current->signal->group_stop_count;
1694+
task_participate_group_stop(current);
16491695

16501696
current->last_siginfo = info;
16511697
current->exit_code = exit_code;
@@ -1730,6 +1776,7 @@ static int do_signal_stop(int signr)
17301776
int notify = 0;
17311777

17321778
if (!sig->group_stop_count) {
1779+
unsigned int gstop = GROUP_STOP_CONSUME;
17331780
struct task_struct *t;
17341781

17351782
if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED) ||
@@ -1741,6 +1788,7 @@ static int do_signal_stop(int signr)
17411788
*/
17421789
sig->group_exit_code = signr;
17431790

1791+
current->group_stop = gstop;
17441792
sig->group_stop_count = 1;
17451793
for (t = next_thread(current); t != current; t = next_thread(t))
17461794
/*
@@ -1750,19 +1798,19 @@ static int do_signal_stop(int signr)
17501798
*/
17511799
if (!(t->flags & PF_EXITING) &&
17521800
!task_is_stopped_or_traced(t)) {
1801+
t->group_stop = gstop;
17531802
sig->group_stop_count++;
17541803
signal_wake_up(t, 0);
1755-
}
1804+
} else
1805+
task_clear_group_stop_pending(t);
17561806
}
17571807
/*
17581808
* If there are no other threads in the group, or if there is
17591809
* a group stop in progress and we are the last to stop, report
17601810
* to the parent. When ptraced, every thread reports itself.
17611811
*/
1762-
if (!--sig->group_stop_count) {
1763-
sig->flags = SIGNAL_STOP_STOPPED;
1812+
if (task_participate_group_stop(current))
17641813
notify = CLD_STOPPED;
1765-
}
17661814
if (task_ptrace(current))
17671815
notify = CLD_STOPPED;
17681816

@@ -2026,10 +2074,8 @@ void exit_signals(struct task_struct *tsk)
20262074
recalc_sigpending_and_wake(t);
20272075

20282076
if (unlikely(tsk->signal->group_stop_count) &&
2029-
!--tsk->signal->group_stop_count) {
2030-
tsk->signal->flags = SIGNAL_STOP_STOPPED;
2077+
task_participate_group_stop(tsk))
20312078
group_stop = CLD_STOPPED;
2032-
}
20332079
out:
20342080
spin_unlock_irq(&tsk->sighand->siglock);
20352081

0 commit comments

Comments
 (0)