Skip to content

Commit 57b6de0

Browse files
committed
ptrace: Admit ptrace_stop can generate spuriuos SIGTRAPs
Long ago and far away there was a BUG_ON at the start of ptrace_stop that did "BUG_ON(!(current->ptrace & PT_PTRACED));" [1]. The BUG_ON had never triggered but examination of the code showed that the BUG_ON could actually trigger. To complement removing the BUG_ON an attempt to better handle the race was added. The code detected the tracer had gone away and did not call do_notify_parent_cldstop. The code also attempted to prevent ptrace_report_syscall from sending spurious SIGTRAPs when the tracer went away. The code to detect when the tracer had gone away before sending a signal to tracer was a legitimate fix and continues to work to this date. The code to prevent sending spurious SIGTRAPs is a failure. At the time and until today the code only catches it when the tracer goes away after siglock is dropped and before read_lock is acquired. If the tracer goes away after read_lock is dropped a spurious SIGTRAP can still be sent to the tracee. The tracer going away after read_lock is dropped is the far likelier case as it is the bigger window. Given that the attempt to prevent the generation of a SIGTRAP was a failure and continues to be a failure remove the code that attempts to do that. This simplifies the code in ptrace_stop and makes ptrace_stop much easier to reason about. To successfully deal with the tracer going away, all of the tracer's instrumentation of the child would need to be removed, and reliably detecting when the tracer has set a signal to continue with would need to be implemented. [1] 66519f5 ("[PATCH] fix ptracer death race yielding bogus BUG_ON") History-Tree: https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git Tested-by: Kees Cook <[email protected]> Reviewed-by: Oleg Nesterov <[email protected]> Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: "Eric W. Biederman" <[email protected]>
1 parent 7b0fe13 commit 57b6de0

File tree

1 file changed

+38
-54
lines changed

1 file changed

+38
-54
lines changed

kernel/signal.c

Lines changed: 38 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,13 +2187,12 @@ static void do_notify_parent_cldstop(struct task_struct *tsk,
21872187
* with. If the code did not stop because the tracer is gone,
21882188
* the stop signal remains unchanged unless clear_code.
21892189
*/
2190-
static int ptrace_stop(int exit_code, int why, int clear_code,
2191-
unsigned long message, kernel_siginfo_t *info)
2190+
static int ptrace_stop(int exit_code, int why, unsigned long message,
2191+
kernel_siginfo_t *info)
21922192
__releases(&current->sighand->siglock)
21932193
__acquires(&current->sighand->siglock)
21942194
{
21952195
bool gstop_done = false;
2196-
bool read_code = true;
21972196

21982197
if (arch_ptrace_stop_needed()) {
21992198
/*
@@ -2212,7 +2211,14 @@ static int ptrace_stop(int exit_code, int why, int clear_code,
22122211
/*
22132212
* schedule() will not sleep if there is a pending signal that
22142213
* can awaken the task.
2214+
*
2215+
* After this point ptrace_signal_wake_up will clear TASK_TRACED
2216+
* if ptrace_unlink happens. Handle previous ptrace_unlinks
2217+
* here to prevent ptrace_stop sleeping in schedule.
22152218
*/
2219+
if (!current->ptrace)
2220+
return exit_code;
2221+
22162222
set_special_state(TASK_TRACED);
22172223

22182224
/*
@@ -2259,63 +2265,41 @@ static int ptrace_stop(int exit_code, int why, int clear_code,
22592265

22602266
spin_unlock_irq(&current->sighand->siglock);
22612267
read_lock(&tasklist_lock);
2262-
if (likely(current->ptrace)) {
2263-
/*
2264-
* Notify parents of the stop.
2265-
*
2266-
* While ptraced, there are two parents - the ptracer and
2267-
* the real_parent of the group_leader. The ptracer should
2268-
* know about every stop while the real parent is only
2269-
* interested in the completion of group stop. The states
2270-
* for the two don't interact with each other. Notify
2271-
* separately unless they're gonna be duplicates.
2272-
*/
2268+
/*
2269+
* Notify parents of the stop.
2270+
*
2271+
* While ptraced, there are two parents - the ptracer and
2272+
* the real_parent of the group_leader. The ptracer should
2273+
* know about every stop while the real parent is only
2274+
* interested in the completion of group stop. The states
2275+
* for the two don't interact with each other. Notify
2276+
* separately unless they're gonna be duplicates.
2277+
*/
2278+
if (current->ptrace)
22732279
do_notify_parent_cldstop(current, true, why);
2274-
if (gstop_done && ptrace_reparented(current))
2275-
do_notify_parent_cldstop(current, false, why);
2280+
if (gstop_done && (!current->ptrace || ptrace_reparented(current)))
2281+
do_notify_parent_cldstop(current, false, why);
22762282

2277-
/*
2278-
* Don't want to allow preemption here, because
2279-
* sys_ptrace() needs this task to be inactive.
2280-
*
2281-
* XXX: implement read_unlock_no_resched().
2282-
*/
2283-
preempt_disable();
2284-
read_unlock(&tasklist_lock);
2285-
cgroup_enter_frozen();
2286-
preempt_enable_no_resched();
2287-
freezable_schedule();
2288-
cgroup_leave_frozen(true);
2289-
} else {
2290-
/*
2291-
* By the time we got the lock, our tracer went away.
2292-
* Don't drop the lock yet, another tracer may come.
2293-
*
2294-
* If @gstop_done, the ptracer went away between group stop
2295-
* completion and here. During detach, it would have set
2296-
* JOBCTL_STOP_PENDING on us and we'll re-enter
2297-
* TASK_STOPPED in do_signal_stop() on return, so notifying
2298-
* the real parent of the group stop completion is enough.
2299-
*/
2300-
if (gstop_done)
2301-
do_notify_parent_cldstop(current, false, why);
2302-
2303-
/* tasklist protects us from ptrace_freeze_traced() */
2304-
__set_current_state(TASK_RUNNING);
2305-
read_code = false;
2306-
if (clear_code)
2307-
exit_code = 0;
2308-
read_unlock(&tasklist_lock);
2309-
}
2283+
/*
2284+
* Don't want to allow preemption here, because
2285+
* sys_ptrace() needs this task to be inactive.
2286+
*
2287+
* XXX: implement read_unlock_no_resched().
2288+
*/
2289+
preempt_disable();
2290+
read_unlock(&tasklist_lock);
2291+
cgroup_enter_frozen();
2292+
preempt_enable_no_resched();
2293+
freezable_schedule();
2294+
cgroup_leave_frozen(true);
23102295

23112296
/*
23122297
* We are back. Now reacquire the siglock before touching
23132298
* last_siginfo, so that we are sure to have synchronized with
23142299
* any signal-sending on another CPU that wants to examine it.
23152300
*/
23162301
spin_lock_irq(&current->sighand->siglock);
2317-
if (read_code)
2318-
exit_code = current->exit_code;
2302+
exit_code = current->exit_code;
23192303
current->last_siginfo = NULL;
23202304
current->ptrace_message = 0;
23212305
current->exit_code = 0;
@@ -2343,7 +2327,7 @@ static int ptrace_do_notify(int signr, int exit_code, int why, unsigned long mes
23432327
info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
23442328

23452329
/* Let the debugger run. */
2346-
return ptrace_stop(exit_code, why, 1, message, &info);
2330+
return ptrace_stop(exit_code, why, message, &info);
23472331
}
23482332

23492333
int ptrace_notify(int exit_code, unsigned long message)
@@ -2515,7 +2499,7 @@ static void do_jobctl_trap(void)
25152499
CLD_STOPPED, 0);
25162500
} else {
25172501
WARN_ON_ONCE(!signr);
2518-
ptrace_stop(signr, CLD_STOPPED, 0, 0, NULL);
2502+
ptrace_stop(signr, CLD_STOPPED, 0, NULL);
25192503
}
25202504
}
25212505

@@ -2568,7 +2552,7 @@ static int ptrace_signal(int signr, kernel_siginfo_t *info, enum pid_type type)
25682552
* comment in dequeue_signal().
25692553
*/
25702554
current->jobctl |= JOBCTL_STOP_DEQUEUED;
2571-
signr = ptrace_stop(signr, CLD_TRAPPED, 0, 0, info);
2555+
signr = ptrace_stop(signr, CLD_TRAPPED, 0, info);
25722556

25732557
/* We're back. Did the debugger cancel the sig? */
25742558
if (signr == 0)

0 commit comments

Comments
 (0)