aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2016-10-27 23:51:19 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2016-10-27 23:51:19 +0200
commit458c1f218bb6a6bd0325f0b0cedea5ab0980dbc3 (patch)
treebb5383793b49f8e79b605751c88a7d7b767acd2d
parentc0663c7cd218e23a9c256491c787203a07efb666 (diff)
downloadbusybox-w32-458c1f218bb6a6bd0325f0b0cedea5ab0980dbc3.tar.gz
busybox-w32-458c1f218bb6a6bd0325f0b0cedea5ab0980dbc3.tar.bz2
busybox-w32-458c1f218bb6a6bd0325f0b0cedea5ab0980dbc3.zip
ash: [JOBS] Fix dowait signal race
Upstream commit: Date: Sun, 22 Feb 2009 18:10:01 +0800 [JOBS] Fix dowait signal race This test program by Alexey Gladkov can cause dash to enter an infinite loop in waitcmd. #!/bin/dash trap "echo TRAP" USR1 stub() { echo ">>> STUB $1" >&2 sleep $1 echo "<<< STUB $1" >&2 kill -USR1 $$ } stub 3 & stub 2 & until { echo "###"; wait; } do echo "*** $?" done The problem is that if we get a signal after the wait3 system call has returned but before we get to INTON in dowait, then we can jump back up to the top and lose the exit status. So if we then wait for the job that has just exited, then it'll stay there forever. I made the original change that caused this bug to fix pretty much the same bug but in the opposite direction. That is, if we get a signal after we enter wait3 but before we hit the kernel then it too can cause the wait to go on forever (assuming the child doesn't exit). In fact this is pretty much exactly the scenario that you'll find in glibc's documentation on pause(). The solution is given there too, in the form of sigsuspend, which is the only way to do the check and wait atomically. So this patch fixes Alexey's race without reintroducing the old bug by converting the blocking wait3 to a sigsuspend. In order to do this we need to set a signal handler for SIGCHLD, so the code has been modified to always do that. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> I failed to reproduce the bug (it requires precise timing), but it seems real. function old new delta dowait 284 463 +179 setsignal 301 326 +25 signal_handler 59 76 +17 ash_main 1481 1487 +6 localcmd 350 348 -2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/1 up/down: 227/-2) Total: 225 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--shell/ash.c83
1 files changed, 70 insertions, 13 deletions
diff --git a/shell/ash.c b/shell/ash.c
index b060d227a..1ef02b8be 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -295,6 +295,7 @@ struct globals_misc {
295 295
296 volatile int suppress_int; /* counter */ 296 volatile int suppress_int; /* counter */
297 volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */ 297 volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
298 volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */
298 /* last pending signal */ 299 /* last pending signal */
299 volatile /*sig_atomic_t*/ smallint pending_sig; 300 volatile /*sig_atomic_t*/ smallint pending_sig;
300 smallint exception_type; /* kind of exception (0..5) */ 301 smallint exception_type; /* kind of exception (0..5) */
@@ -370,6 +371,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
370#define exception_type (G_misc.exception_type ) 371#define exception_type (G_misc.exception_type )
371#define suppress_int (G_misc.suppress_int ) 372#define suppress_int (G_misc.suppress_int )
372#define pending_int (G_misc.pending_int ) 373#define pending_int (G_misc.pending_int )
374#define got_sigchld (G_misc.got_sigchld )
373#define pending_sig (G_misc.pending_sig ) 375#define pending_sig (G_misc.pending_sig )
374#define isloginsh (G_misc.isloginsh ) 376#define isloginsh (G_misc.isloginsh )
375#define nullstr (G_misc.nullstr ) 377#define nullstr (G_misc.nullstr )
@@ -3388,7 +3390,14 @@ ignoresig(int signo)
3388static void 3390static void
3389signal_handler(int signo) 3391signal_handler(int signo)
3390{ 3392{
3393 if (signo == SIGCHLD) {
3394 got_sigchld = 1;
3395 if (!trap[SIGCHLD])
3396 return;
3397 }
3398
3391 gotsig[signo - 1] = 1; 3399 gotsig[signo - 1] = 1;
3400 pending_sig = signo;
3392 3401
3393 if (signo == SIGINT && !trap[SIGINT]) { 3402 if (signo == SIGINT && !trap[SIGINT]) {
3394 if (!suppress_int) { 3403 if (!suppress_int) {
@@ -3396,8 +3405,6 @@ signal_handler(int signo)
3396 raise_interrupt(); /* does not return */ 3405 raise_interrupt(); /* does not return */
3397 } 3406 }
3398 pending_int = 1; 3407 pending_int = 1;
3399 } else {
3400 pending_sig = signo;
3401 } 3408 }
3402} 3409}
3403 3410
@@ -3455,6 +3462,9 @@ setsignal(int signo)
3455//whereas we have to restore it to what shell got on entry 3462//whereas we have to restore it to what shell got on entry
3456//from the parent. See comment above 3463//from the parent. See comment above
3457 3464
3465 if (signo == SIGCHLD)
3466 new_act = S_CATCH;
3467
3458 t = &sigmode[signo - 1]; 3468 t = &sigmode[signo - 1];
3459 cur_act = *t; 3469 cur_act = *t;
3460 if (cur_act == 0) { 3470 if (cur_act == 0) {
@@ -3507,6 +3517,7 @@ setsignal(int signo)
3507/* mode flags for dowait */ 3517/* mode flags for dowait */
3508#define DOWAIT_NONBLOCK 0 3518#define DOWAIT_NONBLOCK 0
3509#define DOWAIT_BLOCK 1 3519#define DOWAIT_BLOCK 1
3520#define DOWAIT_BLOCK_OR_SIG 2
3510 3521
3511#if JOBS 3522#if JOBS
3512/* pgrp of shell on invocation */ 3523/* pgrp of shell on invocation */
@@ -3928,32 +3939,76 @@ sprint_status48(char *s, int status, int sigonly)
3928} 3939}
3929 3940
3930static int 3941static int
3942wait_block_or_sig(int *status, int wait_flags)
3943{
3944 sigset_t mask;
3945 int pid;
3946
3947 do {
3948 /* Poll all children for changes in their state */
3949 got_sigchld = 0;
3950 pid = waitpid(-1, status, wait_flags | WNOHANG);
3951 if (pid != 0)
3952 break; /* Error (e.g. EINTR) or pid */
3953
3954 /* No child is ready. Sleep until interesting signal is received */
3955 sigfillset(&mask);
3956 sigprocmask(SIG_SETMASK, &mask, &mask);
3957 while (!got_sigchld && !pending_sig)
3958 sigsuspend(&mask);
3959 sigprocmask(SIG_SETMASK, &mask, NULL);
3960
3961 /* If it was SIGCHLD, poll children again */
3962 } while (got_sigchld);
3963
3964 return pid;
3965}
3966
3967
3968static int
3931dowait(int block, struct job *job) 3969dowait(int block, struct job *job)
3932{ 3970{
3933 int wait_flags; 3971 int wait_flags;
3934 int pid; 3972 int pid;
3935 int status; 3973 int status;
3936 struct job *jp; 3974 struct job *jp;
3937 struct job *thisjob; 3975 struct job *thisjob = NULL;
3938 3976
3939 TRACE(("dowait(0x%x) called\n", block)); 3977 TRACE(("dowait(0x%x) called\n", block));
3940 3978
3941 /* Do a wait system call. If job control is compiled in, we accept
3942 * stopped processes.
3943 * NB: _not_ safe_waitpid, we need to detect EINTR.
3944 */
3945 wait_flags = 0; 3979 wait_flags = 0;
3946 if (block == DOWAIT_NONBLOCK) 3980 if (block == DOWAIT_NONBLOCK)
3947 wait_flags = WNOHANG; 3981 wait_flags = WNOHANG;
3982 /* If job control is compiled in, we accept stopped processes too. */
3948 if (doing_jobctl) 3983 if (doing_jobctl)
3949 wait_flags |= WUNTRACED; 3984 wait_flags |= WUNTRACED;
3950 pid = waitpid(-1, &status, wait_flags); 3985
3986 /* It's wrong to call waitpid() outside of INT_OFF region:
3987 * signal can arrive just after syscall return and handler can
3988 * longjmp away, losing stop/exit notification processing.
3989 * Thus, for "jobs" builtin, and for waiting for a fg job,
3990 * we call waitpid() (blocking or non-blocking) inside INT_OFF.
3991 *
3992 * However, for "wait" builtin it is wrong to simply call waitpid()
3993 * in INT_OFF region: "wait" needs to wait for any running job
3994 * to change state, but should exit on any trap too.
3995 * In INT_OFF region, a signal just before syscall entry can set
3996 * pending_sig valiables, but we can't check them, and we would
3997 * either enter a sleeping waitpid() (BUG), or need to busy-loop.
3998 * Because of this, we run inside INT_OFF, but use a special routine
3999 * which combines waitpid() and sigsuspend().
4000 */
4001 INT_OFF;
4002 if (block == DOWAIT_BLOCK_OR_SIG)
4003 pid = wait_block_or_sig(&status, wait_flags);
4004 else
4005 /* NB: _not_ safe_waitpid, we need to detect EINTR. */
4006 pid = waitpid(-1, &status, wait_flags);
3951 TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", 4007 TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n",
3952 pid, status, errno, strerror(errno))); 4008 pid, status, errno, strerror(errno)));
3953 if (pid <= 0) 4009 if (pid <= 0)
3954 return pid; 4010 goto out;
3955 4011
3956 INT_OFF;
3957 thisjob = NULL; 4012 thisjob = NULL;
3958 for (jp = curjob; jp; jp = jp->prev_job) { 4013 for (jp = curjob; jp; jp = jp->prev_job) {
3959 int jobstate; 4014 int jobstate;
@@ -4217,7 +4272,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
4217 * with an exit status greater than 128, immediately after which 4272 * with an exit status greater than 128, immediately after which
4218 * the trap is executed." 4273 * the trap is executed."
4219 */ 4274 */
4220 dowait(DOWAIT_BLOCK, NULL); ///DOWAIT_WAITCMD 4275 dowait(DOWAIT_BLOCK_OR_SIG, NULL);
4221 /* if child sends us a signal *and immediately exits*, 4276 /* if child sends us a signal *and immediately exits*,
4222 * dowait() returns pid > 0. Check this case, 4277 * dowait() returns pid > 0. Check this case,
4223 * not "if (dowait() < 0)"! 4278 * not "if (dowait() < 0)"!
@@ -4244,7 +4299,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
4244 } 4299 }
4245 /* loop until process terminated or stopped */ 4300 /* loop until process terminated or stopped */
4246 while (job->state == JOBRUNNING) { 4301 while (job->state == JOBRUNNING) {
4247 dowait(DOWAIT_BLOCK, NULL); ///DOWAIT_WAITCMD 4302 dowait(DOWAIT_BLOCK_OR_SIG, NULL);
4248 if (pending_sig) 4303 if (pending_sig)
4249 goto sigout; 4304 goto sigout;
4250 } 4305 }
@@ -13113,7 +13168,9 @@ init(void)
13113 /* we will never free this */ 13168 /* we will never free this */
13114 basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); 13169 basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
13115 13170
13116 signal(SIGCHLD, SIG_DFL); 13171 sigmode[SIGCHLD - 1] = S_DFL;
13172 setsignal(SIGCHLD);
13173
13117 /* bash re-enables SIGHUP which is SIG_IGNed on entry. 13174 /* bash re-enables SIGHUP which is SIG_IGNed on entry.
13118 * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$" 13175 * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
13119 */ 13176 */