Commit 8a35241803eeb0e9fd3fe27835d6b2775c73b641
1 parent
f701e5b73a
Exists in
master
and in
38 other branches
ptrace: fix ptrace_signal() && STOP_DEQUEUED interaction
Simple test-case, int main(void) { int pid, status; pid = fork(); if (!pid) { pause(); assert(0); return 0x23; } assert(ptrace(PTRACE_ATTACH, pid, 0,0) == 0); assert(wait(&status) == pid); assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); kill(pid, SIGCONT); // <--- also clears STOP_DEQUEUD assert(ptrace(PTRACE_CONT, pid, 0,0) == 0); assert(wait(&status) == pid); assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGCONT); assert(ptrace(PTRACE_CONT, pid, 0, SIGSTOP) == 0); assert(wait(&status) == pid); assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); kill(pid, SIGKILL); return 0; } Without the patch it hangs. After the patch SIGSTOP "injected" by the tracer is not ignored and stops the tracee. Note also that if this test-case uses, say, SIGWINCH instead of SIGCONT, everything works without the patch. This can't be right, and this is confusing. The problem is that SIGSTOP (or any other sig_kernel_stop() signal) has no effect without JOBCTL_STOP_DEQUEUED. This means it is simply ignored after PTRACE_CONT unless JOBCTL_STOP_DEQUEUED was set "by accident", say it wasn't cleared after initial SIGSTOP sent by PTRACE_ATTACH. At first glance we could change ptrace_signal() to add STOP_DEQUEUED after return from ptrace_stop(), but this is not right in case when the tracer does not change the reported SIGSTOP and SIGCONT comes in between. This is even more wrong with PT_SEIZED, SIGCONT adds JOBCTL_TRAP_NOTIFY which will be "lost" during the TRAP_STOP | TRAP_NOTIFY report. So lets add STOP_DEQUEUED _before_ we report the signal. It has no effect unless sig_kernel_stop() == T after the tracer resumes us, and in the latter case the pending STOP_DEQUEUED means no SIGCONT in between, we should stop. Note also that if SIGCONT was sent, PT_SEIZED tracee will correctly report PTRACE_EVENT_STOP/SIGTRAP and thus the tracer can notice the fact SIGSTOP was cancelled. Also, move the current->ptrace check from ptrace_signal() to its caller, get_signal_to_deliver(), this looks more natural. Signed-off-by: Oleg Nesterov <oleg@redhat.com> Acked-by: Tejun Heo <tj@kernel.org>
Showing 1 changed file with 11 additions and 6 deletions Side-by-side Diff
kernel/signal.c
... | ... | @@ -2084,12 +2084,17 @@ |
2084 | 2084 | static int ptrace_signal(int signr, siginfo_t *info, |
2085 | 2085 | struct pt_regs *regs, void *cookie) |
2086 | 2086 | { |
2087 | - if (!current->ptrace) | |
2088 | - return signr; | |
2089 | - | |
2090 | 2087 | ptrace_signal_deliver(regs, cookie); |
2091 | - | |
2092 | - /* Let the debugger run. */ | |
2088 | + /* | |
2089 | + * We do not check sig_kernel_stop(signr) but set this marker | |
2090 | + * unconditionally because we do not know whether debugger will | |
2091 | + * change signr. This flag has no meaning unless we are going | |
2092 | + * to stop after return from ptrace_stop(). In this case it will | |
2093 | + * be checked in do_signal_stop(), we should only stop if it was | |
2094 | + * not cleared by SIGCONT while we were sleeping. See also the | |
2095 | + * comment in dequeue_signal(). | |
2096 | + */ | |
2097 | + current->jobctl |= JOBCTL_STOP_DEQUEUED; | |
2093 | 2098 | ptrace_stop(signr, CLD_TRAPPED, 0, info); |
2094 | 2099 | |
2095 | 2100 | /* We're back. Did the debugger cancel the sig? */ |
... | ... | @@ -2193,7 +2198,7 @@ |
2193 | 2198 | if (!signr) |
2194 | 2199 | break; /* will return 0 */ |
2195 | 2200 | |
2196 | - if (signr != SIGKILL) { | |
2201 | + if (unlikely(current->ptrace) && signr != SIGKILL) { | |
2197 | 2202 | signr = ptrace_signal(signr, info, |
2198 | 2203 | regs, cookie); |
2199 | 2204 | if (!signr) |