aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-03-23 05:37:00 -0400
committerTejun Heo <tj@kernel.org>2011-03-23 05:37:00 -0400
commite5c1902e9260a0075ea52cb5ef627a8d9aaede89 (patch)
tree0fc5ec2460fecfe564323d89227bec9293238c2a
parentfe1bc6a0954611b806f9e158eb0817cf8ba21660 (diff)
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>
-rw-r--r--include/linux/sched.h6
-rw-r--r--kernel/signal.c62
2 files changed, 60 insertions, 8 deletions
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 4b601be3dace..85f51042c2b8 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1260,6 +1260,7 @@ struct task_struct {
1260 int exit_state; 1260 int exit_state;
1261 int exit_code, exit_signal; 1261 int exit_code, exit_signal;
1262 int pdeath_signal; /* The signal sent when the parent dies */ 1262 int pdeath_signal; /* The signal sent when the parent dies */
1263 unsigned int group_stop; /* GROUP_STOP_*, siglock protected */
1263 /* ??? */ 1264 /* ??? */
1264 unsigned int personality; 1265 unsigned int personality;
1265 unsigned did_exec:1; 1266 unsigned did_exec:1;
@@ -1771,6 +1772,11 @@ extern void thread_group_times(struct task_struct *p, cputime_t *ut, cputime_t *
1771#define tsk_used_math(p) ((p)->flags & PF_USED_MATH) 1772#define tsk_used_math(p) ((p)->flags & PF_USED_MATH)
1772#define used_math() tsk_used_math(current) 1773#define used_math() tsk_used_math(current)
1773 1774
1775/*
1776 * task->group_stop flags
1777 */
1778#define GROUP_STOP_CONSUME (1 << 17) /* consume group stop count */
1779
1774#ifdef CONFIG_PREEMPT_RCU 1780#ifdef CONFIG_PREEMPT_RCU
1775 1781
1776#define RCU_READ_UNLOCK_BLOCKED (1 << 0) /* blocked while in RCU read-side. */ 1782#define RCU_READ_UNLOCK_BLOCKED (1 << 0) /* blocked while in RCU read-side. */
diff --git a/kernel/signal.c b/kernel/signal.c
index 95ac42dc3bcb..ecb20089eaff 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -223,6 +223,52 @@ static inline void print_dropped_signal(int sig)
223 current->comm, current->pid, sig); 223 current->comm, current->pid, sig);
224} 224}
225 225
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 */
235static 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 */
252static 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
226/* 272/*
227 * allocate a new signal queue record 273 * allocate a new signal queue record
228 * - this may be called without locks if and only if t == current, otherwise an 274 * - 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)
1645 * we must participate in the bookkeeping. 1691 * we must participate in the bookkeeping.
1646 */ 1692 */
1647 if (current->signal->group_stop_count > 0) 1693 if (current->signal->group_stop_count > 0)
1648 --current->signal->group_stop_count; 1694 task_participate_group_stop(current);
1649 1695
1650 current->last_siginfo = info; 1696 current->last_siginfo = info;
1651 current->exit_code = exit_code; 1697 current->exit_code = exit_code;
@@ -1730,6 +1776,7 @@ static int do_signal_stop(int signr)
1730 int notify = 0; 1776 int notify = 0;
1731 1777
1732 if (!sig->group_stop_count) { 1778 if (!sig->group_stop_count) {
1779 unsigned int gstop = GROUP_STOP_CONSUME;
1733 struct task_struct *t; 1780 struct task_struct *t;
1734 1781
1735 if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED) || 1782 if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED) ||
@@ -1741,6 +1788,7 @@ static int do_signal_stop(int signr)
1741 */ 1788 */
1742 sig->group_exit_code = signr; 1789 sig->group_exit_code = signr;
1743 1790
1791 current->group_stop = gstop;
1744 sig->group_stop_count = 1; 1792 sig->group_stop_count = 1;
1745 for (t = next_thread(current); t != current; t = next_thread(t)) 1793 for (t = next_thread(current); t != current; t = next_thread(t))
1746 /* 1794 /*
@@ -1750,19 +1798,19 @@ static int do_signal_stop(int signr)
1750 */ 1798 */
1751 if (!(t->flags & PF_EXITING) && 1799 if (!(t->flags & PF_EXITING) &&
1752 !task_is_stopped_or_traced(t)) { 1800 !task_is_stopped_or_traced(t)) {
1801 t->group_stop = gstop;
1753 sig->group_stop_count++; 1802 sig->group_stop_count++;
1754 signal_wake_up(t, 0); 1803 signal_wake_up(t, 0);
1755 } 1804 } else
1805 task_clear_group_stop_pending(t);
1756 } 1806 }
1757 /* 1807 /*
1758 * If there are no other threads in the group, or if there is 1808 * If there are no other threads in the group, or if there is
1759 * a group stop in progress and we are the last to stop, report 1809 * a group stop in progress and we are the last to stop, report
1760 * to the parent. When ptraced, every thread reports itself. 1810 * to the parent. When ptraced, every thread reports itself.
1761 */ 1811 */
1762 if (!--sig->group_stop_count) { 1812 if (task_participate_group_stop(current))
1763 sig->flags = SIGNAL_STOP_STOPPED;
1764 notify = CLD_STOPPED; 1813 notify = CLD_STOPPED;
1765 }
1766 if (task_ptrace(current)) 1814 if (task_ptrace(current))
1767 notify = CLD_STOPPED; 1815 notify = CLD_STOPPED;
1768 1816
@@ -2026,10 +2074,8 @@ void exit_signals(struct task_struct *tsk)
2026 recalc_sigpending_and_wake(t); 2074 recalc_sigpending_and_wake(t);
2027 2075
2028 if (unlikely(tsk->signal->group_stop_count) && 2076 if (unlikely(tsk->signal->group_stop_count) &&
2029 !--tsk->signal->group_stop_count) { 2077 task_participate_group_stop(tsk))
2030 tsk->signal->flags = SIGNAL_STOP_STOPPED;
2031 group_stop = CLD_STOPPED; 2078 group_stop = CLD_STOPPED;
2032 }
2033out: 2079out:
2034 spin_unlock_irq(&tsk->sighand->siglock); 2080 spin_unlock_irq(&tsk->sighand->siglock);
2035 2081