aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/signal.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-03-23 05:37:01 -0400
committerTejun Heo <tj@kernel.org>2011-03-23 05:37:01 -0400
commit408a37de6c95832a4880a88a742f89f0cc554d06 (patch)
tree14e89583cd01a91a75e93b262e162e623847d2ef /kernel/signal.c
parent0e9f0a4abfd80f8adca624538d479d95159b16d8 (diff)
job control: Don't set group_stop exit_code if re-entering job control stop
While ptraced, a task may be resumed while the containing process is still job control stopped. If the task receives another stop signal in this state, it will still initiate group stop, which generates group_exit_code, which the real parent would be able to see once the ptracer detaches. In this scenario, the real parent may see two consecutive CLD_STOPPED events from two stop signals without intervening SIGCONT, which normally is impossible. Test case follows. #include <stdio.h> #include <unistd.h> #include <sys/ptrace.h> #include <sys/wait.h> int main(void) { pid_t tracee; siginfo_t si; tracee = fork(); if (!tracee) while (1) pause(); kill(tracee, SIGSTOP); waitid(P_PID, tracee, &si, WSTOPPED); if (!fork()) { ptrace(PTRACE_ATTACH, tracee, NULL, NULL); waitid(P_PID, tracee, &si, WSTOPPED); ptrace(PTRACE_CONT, tracee, NULL, (void *)(long)si.si_status); waitid(P_PID, tracee, &si, WSTOPPED); ptrace(PTRACE_CONT, tracee, NULL, (void *)(long)si.si_status); waitid(P_PID, tracee, &si, WSTOPPED); ptrace(PTRACE_DETACH, tracee, NULL, NULL); return 0; } while (1) { si.si_pid = 0; waitid(P_PID, tracee, &si, WSTOPPED | WNOHANG); if (si.si_pid) printf("st=%02d c=%02d\n", si.si_status, si.si_code); } return 0; } Before the patch, the latter waitid() in polling mode reports the second stopped event generated by the implied SIGSTOP of PTRACE_ATTACH. st=19 c=05 ^C After the patch, the second event is not reported. Signed-off-by: Tejun Heo <tj@kernel.org> Acked-by: Oleg Nesterov <oleg@redhat.com>
Diffstat (limited to 'kernel/signal.c')
-rw-r--r--kernel/signal.c23
1 files changed, 20 insertions, 3 deletions
diff --git a/kernel/signal.c b/kernel/signal.c
index 1e199919ae09..2f2c8f6ee01f 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1827,10 +1827,27 @@ static int do_signal_stop(int signr)
1827 unlikely(signal_group_exit(sig))) 1827 unlikely(signal_group_exit(sig)))
1828 return 0; 1828 return 0;
1829 /* 1829 /*
1830 * There is no group stop already in progress. 1830 * There is no group stop already in progress. We must
1831 * We must initiate one now. 1831 * initiate one now.
1832 *
1833 * While ptraced, a task may be resumed while group stop is
1834 * still in effect and then receive a stop signal and
1835 * initiate another group stop. This deviates from the
1836 * usual behavior as two consecutive stop signals can't
1837 * cause two group stops when !ptraced.
1838 *
1839 * The condition can be distinguished by testing whether
1840 * SIGNAL_STOP_STOPPED is already set. Don't generate
1841 * group_exit_code in such case.
1842 *
1843 * This is not necessary for SIGNAL_STOP_CONTINUED because
1844 * an intervening stop signal is required to cause two
1845 * continued events regardless of ptrace.
1832 */ 1846 */
1833 sig->group_exit_code = signr; 1847 if (!(sig->flags & SIGNAL_STOP_STOPPED))
1848 sig->group_exit_code = signr;
1849 else
1850 WARN_ON_ONCE(!task_ptrace(current));
1834 1851
1835 current->group_stop &= ~GROUP_STOP_SIGMASK; 1852 current->group_stop &= ~GROUP_STOP_SIGMASK;
1836 current->group_stop |= signr | gstop; 1853 current->group_stop |= signr | gstop;