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? */
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
static void restartjob(struct job *);
#endif
@ -518,7 +522,7 @@ waitcmd(int argc, char **argv)
break;
}
}
} while (dowait(1, (struct job *)NULL) != -1);
} while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1);
in_waitcmd--;
return 0;
@ -965,7 +969,7 @@ waitforjob(struct job *jp, int *origstatus)
INTOFF;
TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1));
while (jp->state == 0)
if (dowait(1, jp) == -1)
if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG : 0), jp) == -1)
dotrap();
#if JOBS
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.
*/
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;
int status;
struct procstat *sp;
@ -1021,8 +1031,22 @@ dowait(int block, struct job *job)
int sig;
int coredump;
int wflags;
int restore_sigchld;
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 {
#if JOBS
if (iflag)
@ -1030,13 +1054,25 @@ dowait(int block, struct job *job)
else
#endif
wflags = 0;
if (block == 0)
if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK)
wflags |= WNOHANG;
pid = wait3(&status, wflags, (struct rusage *)NULL);
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);
if (pid == -1 && errno == ECHILD && job != NULL)
job->state = JOBDONE;
if ((mode & DOWAIT_SIG) != 0) {
if (restore_sigchld)
sigaction(SIGCHLD, &osa, NULL);
sigprocmask(SIG_SETMASK, &omask, NULL);
INTON;
}
if (breakwaitcmd != 0) {
breakwaitcmd = 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.
*/

View File

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