ptrace fixes for 3.2
Subsystems (2)
Oleg Nesterov
maintainercover ptrace fixes for 3.2
Hello.
2 simple fixes for 3.2. Both changes are temporary hacks, hopefully we will properly fix the problems in the next devel cycle.
Oleg.
Oleg Nesterov
maintainerPATCH 1/2 ptrace: partially fix the do_wait(WEXITED) vs EXIT_DEAD->EXIT_ZOMBIE race
Test-case:
int main(void)
{
int pid, status;
pid = fork();
if (!pid) {
for (;;) {
if (!fork())
return 0;
if (waitpid(-1, &status, 0) < 0) {
printf("ERR!! wait: %m\n");
return 0;
}
}
}
assert(ptrace(PTRACE_ATTACH, pid, 0,0) == 0);
assert(waitpid(-1, NULL, 0) == pid);
assert(ptrace(PTRACE_SETOPTIONS, pid, 0,
PTRACE_O_TRACEFORK) == 0);
do {
ptrace(PTRACE_CONT, pid, 0, 0);
pid = waitpid(-1, NULL, 0);
} while (pid > 0);
return 1;
}It fails because ->real_parent sees its child in EXIT_DEAD state while the tracer is going to change the state back to EXIT_ZOMBIE in wait_task_zombie().
The offending commit is 823b018e which moved the EXIT_DEAD check, but in fact we should not blame it. The original code was not correct as well because it didn't take ptrace_reparented() into account and because we can't really trust ->ptrace.
This patch adds the additional check to close this particular race but it doesn't solve the whole problem. We simply can't rely on ->ptrace in this case, it can be cleared if the tracer is multithreaded by the exiting ->parent.
I think we should kill EXIT_DEAD altogether, we should always remove the soon-to-be-reaped child from ->children or at least we should never do the DEAD->ZOMBIE transition. But this is too complex for 3.2.
--- kernel/exit.c | 9 ++++++++- 1 files changed, 8 insertions(+), 1 deletions(-)
kernel/exit.c +8 -1
@@ -1540,8 +1540,15 @@ static int wait_consider_task(struct wait_opts *wo, int ptrace, }/* dead body doesn't have much to contribute */ -if (p->exit_state ==EXIT_DEAD ) +if (unlikely (p->exit_state ==EXIT_DEAD )) { +/* +* But do not ignore this task until the tracer does +* wait_task_zombie()->do_notify_parent(). +*/ +if (likely (!ptrace) &&unlikely (ptrace_reparented (p))) + wo->notask_error =0 ;return 0 ; + }/* slay zombie? */ if (p->exit_state ==EXIT_ZOMBIE ) {
Signature · 1 line
1.5.5.1
Oleg Nesterov
maintainerPATCH 2/2 ptrace: ensure JOBCTL_STOP_SIGMASK is not zero after detach
In reply to Oleg NesterovThis is the temporary simple fix for 3.2, we need more changes in this area.
1. do_signal_stop() assumes that the running untraced thread in the stopped thread group is not possible. This was our goal but it is not yet achieved: a stopped-but-resumed tracee can clone the running thread which can initiate another group-stop.
Remove WARN_ON_ONCE(!current->ptrace).
2. A new thread always starts with ->jobctl = 0. If it is auto-attached and this group is stopped, __ptrace_unlink() sets JOBCTL_STOP_PENDING but JOBCTL_STOP_SIGMASK part is zero, this triggers WANR_ON(!signr) in do_jobctl_trap() if another debugger attaches.
Change __ptrace_unlink() to set the artificial SIGSTOP for report.
Alternatively we could change ptrace_init_task() to copy signr from current, but this means we can copy it for no reason and hide the possible similar problems.
--- kernel/ptrace.c | 13 ++++++++++++- kernel/signal.c | 2 -- 2 files changed, 12 insertions(+), 3 deletions(-)
kernel/ptrace.c +12 -1
@@ -96,9 +96,20 @@ void __ptrace_unlink(struct task_struct *child)*/ if (!(child->flags &PF_EXITING ) && (child->signal->flags &SIGNAL_STOP_STOPPED || - child->signal->group_stop_count)) + child->signal->group_stop_count)) { child->jobctl |=JOBCTL_STOP_PENDING ; +/* +* This is only possible if this thread was cloned by the +* traced task running in the stopped group, set the signal +* for the future reports. +* FIXME: we should change ptrace_init_task() to handle this +* case. +*/ +if (!(child->jobctl &JOBCTL_STOP_SIGMASK )) + child->jobctl |=SIGSTOP ; + } +/* * If transition to TASK_STOPPED is pending or in TASK_TRACED, kick * @child in the butt. Note that @resume should be used iff @child
kernel/signal.c +0 -2
@@ -1994,8 +1994,6 @@ static bool do_signal_stop(int signr)*/ if (!(sig->flags &SIGNAL_STOP_STOPPED )) sig->group_exit_code = signr; -else -WARN_ON_ONCE (!current->ptrace); sig->group_stop_count =0 ;
Signature · 1 line
1.5.5.1