aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/ptrace.c
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
commitd79fdd6d96f46fabb779d86332e3677c6f5c2a4f (patch)
tree2797e34888c687b47997b7c7ea3150468bcbb737 /kernel/ptrace.c
parent5224fa3660ad3881d2f2ad726d22614117963f10 (diff)
ptrace: Clean transitions between TASK_STOPPED and TRACED
Currently, if the task is STOPPED on ptrace attach, it's left alone and the state is silently changed to TRACED on the next ptrace call. The behavior breaks the assumption that arch_ptrace_stop() is called before any task is poked by ptrace and is ugly in that a task manipulates the state of another task directly. With GROUP_STOP_PENDING, the transitions between TASK_STOPPED and TRACED can be made clean. The tracer can use the flag to tell the tracee to retry stop on attach and detach. On retry, the tracee will enter the desired state in the correct way. The lower 16bits of task->group_stop is used to remember the signal number which caused the last group stop. This is used while retrying for ptrace attach as the original group_exit_code could have been consumed with wait(2) by then. As the real parent may wait(2) and consume the group_exit_code anytime, the group_exit_code needs to be saved separately so that it can be used when switching from regular sleep to ptrace_stop(). This is recorded in the lower 16bits of task->group_stop. If a task is already stopped and there's no intervening SIGCONT, a ptrace request immediately following a successful PTRACE_ATTACH should always succeed even if the tracer doesn't wait(2) for attach completion; however, with this change, the tracee might still be TASK_RUNNING trying to enter TASK_TRACED which would cause the following request to fail with -ESRCH. This intermediate state is hidden from the ptracer by setting GROUP_STOP_TRAPPING on attach and making ptrace_check_attach() wait for it to clear on its signal->wait_chldexit. Completing the transition or getting killed clears TRAPPING and wakes up the tracer. Note that the STOPPED -> RUNNING -> TRACED transition is still visible to other threads which are in the same group as the ptracer and the reverse transition is visible to all. Please read the comments for details. Oleg: * Spotted a race condition where a task may retry group stop without proper bookkeeping. Fixed by redoing bookkeeping on retry. * Spotted that the transition is visible to userland in several different ways. Most are fixed with GROUP_STOP_TRAPPING. Unhandled corner case is documented. * Pointed out not setting GROUP_STOP_SIGMASK on an already stopped task would result in more consistent behavior. * Pointed out that calling ptrace_stop() from do_signal_stop() in TASK_STOPPED can race with group stop start logic and then confuse the TRAPPING wait in ptrace_check_attach(). ptrace_stop() is now called with TASK_RUNNING. * Suggested using signal->wait_chldexit instead of bit wait. * Spotted a race condition between TRACED transition and clearing of TRAPPING. Signed-off-by: Tejun Heo <tj@kernel.org> Acked-by: Oleg Nesterov <oleg@redhat.com> Cc: Roland McGrath <roland@redhat.com> Cc: Jan Kratochvil <jan.kratochvil@redhat.com>
Diffstat (limited to 'kernel/ptrace.c')
-rw-r--r--kernel/ptrace.c49
1 files changed, 44 insertions, 5 deletions
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index 6acf8954017c..745fc2dd00c5 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -49,14 +49,22 @@ static void ptrace_untrace(struct task_struct *child)
49 spin_lock(&child->sighand->siglock); 49 spin_lock(&child->sighand->siglock);
50 if (task_is_traced(child)) { 50 if (task_is_traced(child)) {
51 /* 51 /*
52 * If the group stop is completed or in progress, 52 * If group stop is completed or in progress, it should
53 * this thread was already counted as stopped. 53 * participate in the group stop. Set GROUP_STOP_PENDING
54 * before kicking it.
55 *
56 * This involves TRACED -> RUNNING -> STOPPED transition
57 * which is similar to but in the opposite direction of
58 * what happens while attaching to a stopped task.
59 * However, in this direction, the intermediate RUNNING
60 * state is not hidden even from the current ptracer and if
61 * it immediately re-attaches and performs a WNOHANG
62 * wait(2), it may fail.
54 */ 63 */
55 if (child->signal->flags & SIGNAL_STOP_STOPPED || 64 if (child->signal->flags & SIGNAL_STOP_STOPPED ||
56 child->signal->group_stop_count) 65 child->signal->group_stop_count)
57 __set_task_state(child, TASK_STOPPED); 66 child->group_stop |= GROUP_STOP_PENDING;
58 else 67 signal_wake_up(child, 1);
59 signal_wake_up(child, 1);
60 } 68 }
61 spin_unlock(&child->sighand->siglock); 69 spin_unlock(&child->sighand->siglock);
62} 70}
@@ -165,6 +173,7 @@ bool ptrace_may_access(struct task_struct *task, unsigned int mode)
165 173
166static int ptrace_attach(struct task_struct *task) 174static int ptrace_attach(struct task_struct *task)
167{ 175{
176 bool wait_trap = false;
168 int retval; 177 int retval;
169 178
170 audit_ptrace(task); 179 audit_ptrace(task);
@@ -204,12 +213,42 @@ static int ptrace_attach(struct task_struct *task)
204 __ptrace_link(task, current); 213 __ptrace_link(task, current);
205 send_sig_info(SIGSTOP, SEND_SIG_FORCED, task); 214 send_sig_info(SIGSTOP, SEND_SIG_FORCED, task);
206 215
216 spin_lock(&task->sighand->siglock);
217
218 /*
219 * If the task is already STOPPED, set GROUP_STOP_PENDING and
220 * TRAPPING, and kick it so that it transits to TRACED. TRAPPING
221 * will be cleared if the child completes the transition or any
222 * event which clears the group stop states happens. We'll wait
223 * for the transition to complete before returning from this
224 * function.
225 *
226 * This hides STOPPED -> RUNNING -> TRACED transition from the
227 * attaching thread but a different thread in the same group can
228 * still observe the transient RUNNING state. IOW, if another
229 * thread's WNOHANG wait(2) on the stopped tracee races against
230 * ATTACH, the wait(2) may fail due to the transient RUNNING.
231 *
232 * The following task_is_stopped() test is safe as both transitions
233 * in and out of STOPPED are protected by siglock.
234 */
235 if (task_is_stopped(task)) {
236 task->group_stop |= GROUP_STOP_PENDING | GROUP_STOP_TRAPPING;
237 signal_wake_up(task, 1);
238 wait_trap = true;
239 }
240
241 spin_unlock(&task->sighand->siglock);
242
207 retval = 0; 243 retval = 0;
208unlock_tasklist: 244unlock_tasklist:
209 write_unlock_irq(&tasklist_lock); 245 write_unlock_irq(&tasklist_lock);
210unlock_creds: 246unlock_creds:
211 mutex_unlock(&task->signal->cred_guard_mutex); 247 mutex_unlock(&task->signal->cred_guard_mutex);
212out: 248out:
249 if (wait_trap)
250 wait_event(current->signal->wait_chldexit,
251 !(task->group_stop & GROUP_STOP_TRAPPING));
213 return retval; 252 return retval;
214} 253}
215 254