Skip to content

Commit

Permalink
[JOBS] Fix dowait signal race
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
herbertx committed Feb 22, 2009
1 parent 6045fe2 commit 3800d49
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 33 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
2009-02-22 Herbert Xu <herbert@gondor.apana.org.au>

* Fix dowait signal race.

2009-01-14 Herbert Xu <herbert@gondor.apana.org.au>

* Add arith_yacc.h to dash_SOURCES.
Expand Down
64 changes: 34 additions & 30 deletions src/jobs.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
/* mode flags for dowait */
#define DOWAIT_NORMAL 0
#define DOWAIT_BLOCK 1
#define DOWAIT_WAITCMD 2

/* array of jobs */
static struct job *jobtab;
Expand Down Expand Up @@ -589,8 +590,6 @@ waitcmd(int argc, char **argv)
int retval;
struct job *jp;

EXSIGON();

nextopt(nullstr);
retval = 0;

Expand All @@ -609,7 +608,8 @@ waitcmd(int argc, char **argv)
jp->waited = 1;
jp = jp->prev_job;
}
dowait(DOWAIT_BLOCK, 0);
if (dowait(DOWAIT_WAITCMD, 0) <= 0)
goto sigout;
}
}

Expand All @@ -631,7 +631,8 @@ waitcmd(int argc, char **argv)
job = getjob(*argv, 0);
/* loop until process terminated or stopped */
while (job->state == JOBRUNNING)
dowait(DOWAIT_BLOCK, 0);
if (dowait(DOWAIT_WAITCMD, 0) <= 0)
goto sigout;
job->waited = 1;
retval = getstatus(job);
repeat:
Expand All @@ -640,6 +641,10 @@ waitcmd(int argc, char **argv)

out:
return retval;

sigout:
retval = 128 + pendingsigs;
goto out;
}


Expand Down Expand Up @@ -998,16 +1003,16 @@ dowait(int block, struct job *job)
int pid;
int status;
struct job *jp;
struct job *thisjob;
struct job *thisjob = NULL;
int state;

INTOFF;
TRACE(("dowait(%d) called\n", block));
pid = waitproc(block, &status);
TRACE(("wait returns pid %d, status=%d\n", pid, status));
if (pid <= 0)
return pid;
INTOFF;
thisjob = NULL;
goto out;

for (jp = curjob; jp; jp = jp->prev_job) {
struct procstat *sp;
struct procstat *spend;
Expand Down Expand Up @@ -1115,34 +1120,33 @@ STATIC int onsigchild() {
STATIC int
waitproc(int block, int *status)
{
#ifdef BSD
int flags = 0;
sigset_t mask, oldmask;
int flags = block == DOWAIT_BLOCK ? 0 : WNOHANG;
int err;
int sig;

#if JOBS
if (jobctl)
flags |= WUNTRACED;
#endif
if (block == 0)
flags |= WNOHANG;
return wait3(status, flags, (struct rusage *)NULL);
#else
#ifdef SYSV
int (*save)();

if (block == 0) {
gotsigchild = 0;
save = signal(SIGCLD, onsigchild);
signal(SIGCLD, save);
if (gotsigchild == 0)
return 0;
}
return wait(status);
#else
if (block == 0)
return 0;
return wait(status);
#endif
#endif
do {
err = wait3(status, flags, NULL);
if (err || !block)
break;

block = 0;

sigfillset(&mask);
sigprocmask(SIG_SETMASK, &mask, &oldmask);

while (!(sig = pendingsigs))
sigsuspend(&oldmask);

sigclearmask();
} while (sig == SIGCHLD);

return err;
}

/*
Expand Down
10 changes: 7 additions & 3 deletions src/trap.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
/* trap handler commands */
char *trap[NSIG];
/* current value of signal */
static char sigmode[NSIG - 1];
char sigmode[NSIG - 1];
/* indicates specified signal received */
char gotsig[NSIG - 1];
/* last pending signal */
Expand All @@ -82,9 +82,10 @@ int exsig;
extern char *signal_names[];

#ifdef mkinit
INCLUDE <signal.h>
INCLUDE "trap.h"
INIT {
signal(SIGCHLD, SIG_DFL);
sigmode[SIGCHLD - 1] = S_DFL;
setsignal(SIGCHLD);
}
#endif

Expand Down Expand Up @@ -207,6 +208,9 @@ setsignal(int signo)
}
}

if (signo == SIGCHLD)
action = S_CATCH;

t = &sigmode[signo - 1];
tsig = *t;
if (tsig == 0) {
Expand Down
1 change: 1 addition & 0 deletions src/trap.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

extern char *trap[];
extern char gotsig[];
extern char sigmode[];
extern volatile sig_atomic_t pendingsigs;

int trapcmd(int, char **);
Expand Down

0 comments on commit 3800d49

Please sign in to comment.