Commit 8a35241803eeb0e9fd3fe27835d6b2775c73b641

Authored by Oleg Nesterov
1 parent f701e5b73a

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

... ... @@ -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)