Skip to content

Commit d79fdd6

Browse files
committed
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>
1 parent 5224fa3 commit d79fdd6

File tree

3 files changed

+112
-18
lines changed

3 files changed

+112
-18
lines changed

include/linux/sched.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,8 +1775,10 @@ extern void thread_group_times(struct task_struct *p, cputime_t *ut, cputime_t *
17751775
/*
17761776
* task->group_stop flags
17771777
*/
1778+
#define GROUP_STOP_SIGMASK 0xffff /* signr of the last group stop */
17781779
#define GROUP_STOP_PENDING (1 << 16) /* task should stop for group stop */
17791780
#define GROUP_STOP_CONSUME (1 << 17) /* consume group stop count */
1781+
#define GROUP_STOP_TRAPPING (1 << 18) /* switching from STOPPED to TRACED */
17801782

17811783
extern void task_clear_group_stop_pending(struct task_struct *task);
17821784

kernel/ptrace.c

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,22 @@ static void ptrace_untrace(struct task_struct *child)
4949
spin_lock(&child->sighand->siglock);
5050
if (task_is_traced(child)) {
5151
/*
52-
* If the group stop is completed or in progress,
53-
* this thread was already counted as stopped.
52+
* If group stop is completed or in progress, it should
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.
5463
*/
5564
if (child->signal->flags & SIGNAL_STOP_STOPPED ||
5665
child->signal->group_stop_count)
57-
__set_task_state(child, TASK_STOPPED);
58-
else
59-
signal_wake_up(child, 1);
66+
child->group_stop |= GROUP_STOP_PENDING;
67+
signal_wake_up(child, 1);
6068
}
6169
spin_unlock(&child->sighand->siglock);
6270
}
@@ -165,6 +173,7 @@ bool ptrace_may_access(struct task_struct *task, unsigned int mode)
165173

166174
static int ptrace_attach(struct task_struct *task)
167175
{
176+
bool wait_trap = false;
168177
int retval;
169178

170179
audit_ptrace(task);
@@ -204,12 +213,42 @@ static int ptrace_attach(struct task_struct *task)
204213
__ptrace_link(task, current);
205214
send_sig_info(SIGSTOP, SEND_SIG_FORCED, task);
206215

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+
207243
retval = 0;
208244
unlock_tasklist:
209245
write_unlock_irq(&tasklist_lock);
210246
unlock_creds:
211247
mutex_unlock(&task->signal->cred_guard_mutex);
212248
out:
249+
if (wait_trap)
250+
wait_event(current->signal->wait_chldexit,
251+
!(task->group_stop & GROUP_STOP_TRAPPING));
213252
return retval;
214253
}
215254

kernel/signal.c

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

226+
/**
227+
* task_clear_group_stop_trapping - clear group stop trapping bit
228+
* @task: target task
229+
*
230+
* If GROUP_STOP_TRAPPING is set, a ptracer is waiting for us. Clear it
231+
* and wake up the ptracer. Note that we don't need any further locking.
232+
* @task->siglock guarantees that @task->parent points to the ptracer.
233+
*
234+
* CONTEXT:
235+
* Must be called with @task->sighand->siglock held.
236+
*/
237+
static void task_clear_group_stop_trapping(struct task_struct *task)
238+
{
239+
if (unlikely(task->group_stop & GROUP_STOP_TRAPPING)) {
240+
task->group_stop &= ~GROUP_STOP_TRAPPING;
241+
__wake_up_sync(&task->parent->signal->wait_chldexit,
242+
TASK_UNINTERRUPTIBLE, 1);
243+
}
244+
}
245+
226246
/**
227247
* task_clear_group_stop_pending - clear pending group stop
228248
* @task: target task
@@ -1706,8 +1726,20 @@ static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
17061726
current->last_siginfo = info;
17071727
current->exit_code = exit_code;
17081728

1709-
/* Let the debugger run. */
1710-
__set_current_state(TASK_TRACED);
1729+
/*
1730+
* TRACED should be visible before TRAPPING is cleared; otherwise,
1731+
* the tracer might fail do_wait().
1732+
*/
1733+
set_current_state(TASK_TRACED);
1734+
1735+
/*
1736+
* We're committing to trapping. Clearing GROUP_STOP_TRAPPING and
1737+
* transition to TASK_TRACED should be atomic with respect to
1738+
* siglock. This hsould be done after the arch hook as siglock is
1739+
* released and regrabbed across it.
1740+
*/
1741+
task_clear_group_stop_trapping(current);
1742+
17111743
spin_unlock_irq(&current->sighand->siglock);
17121744
read_lock(&tasklist_lock);
17131745
if (may_ptrace_stop()) {
@@ -1788,6 +1820,9 @@ static int do_signal_stop(int signr)
17881820
unsigned int gstop = GROUP_STOP_PENDING | GROUP_STOP_CONSUME;
17891821
struct task_struct *t;
17901822

1823+
/* signr will be recorded in task->group_stop for retries */
1824+
WARN_ON_ONCE(signr & ~GROUP_STOP_SIGMASK);
1825+
17911826
if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED) ||
17921827
unlikely(signal_group_exit(sig)))
17931828
return 0;
@@ -1797,25 +1832,27 @@ static int do_signal_stop(int signr)
17971832
*/
17981833
sig->group_exit_code = signr;
17991834

1800-
current->group_stop = gstop;
1835+
current->group_stop &= ~GROUP_STOP_SIGMASK;
1836+
current->group_stop |= signr | gstop;
18011837
sig->group_stop_count = 1;
1802-
for (t = next_thread(current); t != current; t = next_thread(t))
1838+
for (t = next_thread(current); t != current;
1839+
t = next_thread(t)) {
1840+
t->group_stop &= ~GROUP_STOP_SIGMASK;
18031841
/*
18041842
* Setting state to TASK_STOPPED for a group
18051843
* stop is always done with the siglock held,
18061844
* so this check has no races.
18071845
*/
18081846
if (!(t->flags & PF_EXITING) && !task_is_stopped(t)) {
1809-
t->group_stop = gstop;
1847+
t->group_stop |= signr | gstop;
18101848
sig->group_stop_count++;
18111849
signal_wake_up(t, 0);
1812-
} else
1850+
} else {
18131851
task_clear_group_stop_pending(t);
1852+
}
1853+
}
18141854
}
1815-
1816-
current->exit_code = sig->group_exit_code;
1817-
__set_current_state(TASK_STOPPED);
1818-
1855+
retry:
18191856
if (likely(!task_ptrace(current))) {
18201857
int notify = 0;
18211858

@@ -1827,6 +1864,7 @@ static int do_signal_stop(int signr)
18271864
if (task_participate_group_stop(current))
18281865
notify = CLD_STOPPED;
18291866

1867+
__set_current_state(TASK_STOPPED);
18301868
spin_unlock_irq(&current->sighand->siglock);
18311869

18321870
if (notify) {
@@ -1839,13 +1877,28 @@ static int do_signal_stop(int signr)
18391877
schedule();
18401878

18411879
spin_lock_irq(&current->sighand->siglock);
1842-
} else
1843-
ptrace_stop(current->exit_code, CLD_STOPPED, 0, NULL);
1880+
} else {
1881+
ptrace_stop(current->group_stop & GROUP_STOP_SIGMASK,
1882+
CLD_STOPPED, 0, NULL);
1883+
current->exit_code = 0;
1884+
}
1885+
1886+
/*
1887+
* GROUP_STOP_PENDING could be set if another group stop has
1888+
* started since being woken up or ptrace wants us to transit
1889+
* between TASK_STOPPED and TRACED. Retry group stop.
1890+
*/
1891+
if (current->group_stop & GROUP_STOP_PENDING) {
1892+
WARN_ON_ONCE(!(current->group_stop & GROUP_STOP_SIGMASK));
1893+
goto retry;
1894+
}
1895+
1896+
/* PTRACE_ATTACH might have raced with task killing, clear trapping */
1897+
task_clear_group_stop_trapping(current);
18441898

18451899
spin_unlock_irq(&current->sighand->siglock);
18461900

18471901
tracehook_finish_jctl();
1848-
current->exit_code = 0;
18491902

18501903
return 1;
18511904
}

0 commit comments

Comments
 (0)