sh: Fix EINTR race condition in "wait" and "set -T" using sigsuspend().

When waiting for child processes using "wait" or if "set -T" is in effect, a
signal interrupts the wait. Make sure there is no window where the signal
handler may be invoked (setting a flag) just before going to sleep.

There is a similar race condition in the shell language, but scripts can
avoid it by exiting from the trap handler or enforcing synchronization using
a fifo.

If SIGCHLD is not trapped, a signal handler must be installed for it. Only
install this handler for the duration of the wait to avoid triggering
unexpected [EINTR] errors elsewhere.

Note that for some reason only SIGINT and SIGQUIT interrupt a "wait"
command. This remains the case.
This commit is contained in:
Jilles Tjoelker 2012-07-29 18:04:38 +00:00
parent 767a02fb40
commit 1794251add
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=238888
3 changed files with 49 additions and 4 deletions

View File

@ -87,6 +87,10 @@ int in_waitcmd = 0; /* are we in waitcmd()? */
volatile sig_atomic_t breakwaitcmd = 0; /* should wait be terminated? */ volatile sig_atomic_t breakwaitcmd = 0; /* should wait be terminated? */
static int ttyfd = -1; static int ttyfd = -1;
/* mode flags for dowait */
#define DOWAIT_BLOCK 0x1 /* wait until a child exits */
#define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signals */
#if JOBS #if JOBS
static void restartjob(struct job *); static void restartjob(struct job *);
#endif #endif
@ -518,7 +522,7 @@ waitcmd(int argc, char **argv)
break; break;
} }
} }
} while (dowait(1, (struct job *)NULL) != -1); } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1);
in_waitcmd--; in_waitcmd--;
return 0; return 0;
@ -965,7 +969,7 @@ waitforjob(struct job *jp, int *origstatus)
INTOFF; INTOFF;
TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1));
while (jp->state == 0) while (jp->state == 0)
if (dowait(1, jp) == -1) if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG : 0), jp) == -1)
dotrap(); dotrap();
#if JOBS #if JOBS
if (jp->jobctl) { if (jp->jobctl) {
@ -1003,14 +1007,20 @@ waitforjob(struct job *jp, int *origstatus)
} }
static void
dummy_handler(int sig)
{
}
/* /*
* Wait for a process to terminate. * Wait for a process to terminate.
*/ */
static pid_t static pid_t
dowait(int block, struct job *job) dowait(int mode, struct job *job)
{ {
struct sigaction sa, osa;
sigset_t mask, omask;
pid_t pid; pid_t pid;
int status; int status;
struct procstat *sp; struct procstat *sp;
@ -1021,8 +1031,22 @@ dowait(int block, struct job *job)
int sig; int sig;
int coredump; int coredump;
int wflags; int wflags;
int restore_sigchld;
TRACE(("dowait(%d) called\n", block)); TRACE(("dowait(%d) called\n", block));
restore_sigchld = 0;
if ((mode & DOWAIT_SIG) != 0) {
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &omask);
INTOFF;
if (!issigchldtrapped()) {
restore_sigchld = 1;
sa.sa_handler = dummy_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGCHLD, &sa, &osa);
}
}
do { do {
#if JOBS #if JOBS
if (iflag) if (iflag)
@ -1030,13 +1054,25 @@ dowait(int block, struct job *job)
else else
#endif #endif
wflags = 0; wflags = 0;
if (block == 0) if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK)
wflags |= WNOHANG; wflags |= WNOHANG;
pid = wait3(&status, wflags, (struct rusage *)NULL); pid = wait3(&status, wflags, (struct rusage *)NULL);
TRACE(("wait returns %d, status=%d\n", (int)pid, status)); TRACE(("wait returns %d, status=%d\n", (int)pid, status));
if (pid == 0 && (mode & DOWAIT_SIG) != 0) {
sigsuspend(&omask);
pid = -1;
if (int_pending())
break;
}
} while (pid == -1 && errno == EINTR && breakwaitcmd == 0); } while (pid == -1 && errno == EINTR && breakwaitcmd == 0);
if (pid == -1 && errno == ECHILD && job != NULL) if (pid == -1 && errno == ECHILD && job != NULL)
job->state = JOBDONE; job->state = JOBDONE;
if ((mode & DOWAIT_SIG) != 0) {
if (restore_sigchld)
sigaction(SIGCHLD, &osa, NULL);
sigprocmask(SIG_SETMASK, &omask, NULL);
INTON;
}
if (breakwaitcmd != 0) { if (breakwaitcmd != 0) {
breakwaitcmd = 0; breakwaitcmd = 0;
if (pid <= 0) if (pid <= 0)

View File

@ -368,6 +368,14 @@ ignoresig(int signo)
} }
int
issigchldtrapped(void)
{
return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0');
}
/* /*
* Signal handler. * Signal handler.
*/ */

View File

@ -41,6 +41,7 @@ void clear_traps(void);
int have_traps(void); int have_traps(void);
void setsignal(int); void setsignal(int);
void ignoresig(int); void ignoresig(int);
int issigchldtrapped(void);
void onsig(int); void onsig(int);
void dotrap(void); void dotrap(void);
void setinteractive(int); void setinteractive(int);