Use a pipe to pass back errno to the parent if execve() fails.

If we get an error in script_child(), kill the command and exit.
This commit is contained in:
Todd C. Miller
2010-04-13 19:40:32 -04:00
parent f19be6da31
commit ea523b17a4

View File

@@ -746,6 +746,22 @@ deliver_signal(pid_t pid, int signo)
}
}
static int
send_status(int fd, struct command_status *cstat)
{
int n;
do {
n = send(fd, cstat, sizeof(*cstat), 0);
} while (n == -1 && errno == EINTR);
if (n != sizeof(*cstat)) {
sudo_debug(8, "unable to send status to parent: %s", strerror(errno));
} else {
sudo_debug(8, "sent status to parent");
}
return n;
}
int
script_child(const char *path, char *argv[], char *envp[], int backchannel, int rbac)
{
@@ -753,7 +769,8 @@ script_child(const char *path, char *argv[], char *envp[], int backchannel, int
fd_set *fdsr;
sigaction_t sa;
pid_t pid;
int n, status;
int errpipe[2], maxfd, n, status;
int alive = TRUE;
/* Close unused fds. */
close(script_fds[SFD_MASTER]);
@@ -813,6 +830,8 @@ script_child(const char *path, char *argv[], char *envp[], int backchannel, int
foreground = 0;
/* Start command and wait for it to stop or exit */
if (pipe(errpipe) == -1)
error(1, "unable to create pipe");
child = fork();
if (child == -1) {
warning("Can't fork");
@@ -833,13 +852,19 @@ script_child(const char *path, char *argv[], char *envp[], int backchannel, int
sigaction(SIGUSR2, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
/* We pass errno back to our parent via pipe on exec failure. */
close(backchannel);
close(errpipe[0]);
fcntl(errpipe[1], F_SETFD, FD_CLOEXEC);
/* setup tty and exec command */
script_run(path, argv, envp, rbac);
cstat.type = CMD_ERRNO;
cstat.val = errno;
send(backchannel, &cstat, sizeof(cstat), 0);
write(errpipe[1], &cstat, sizeof(cstat));
_exit(1);
}
close(errpipe[1]);
/*
* Put child in its own process group. If we are starting the command
@@ -852,10 +877,11 @@ script_child(const char *path, char *argv[], char *envp[], int backchannel, int
} while (status == -1 && errno == EINTR);
}
/* Wait for signal on backchannel or for SIGCHLD */
fdsr = (fd_set *)emalloc2(howmany(backchannel + 1, NFDBITS), sizeof(fd_mask));
zero_bytes(fdsr, howmany(backchannel + 1, NFDBITS) * sizeof(fd_mask));
FD_SET(backchannel, fdsr);
/* Wait for errno on pipe, signal on backchannel or for SIGCHLD */
maxfd = MAX(errpipe[0], backchannel);
fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));
zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));
zero_bytes(&cstat, sizeof(cstat));
for (;;) {
/* Read child status */
while (recvsig[SIGCHLD]) {
@@ -865,57 +891,85 @@ script_child(const char *path, char *argv[], char *envp[], int backchannel, int
pid = waitpid(child, &status, WUNTRACED|WNOHANG);
} while (pid == -1 && errno == EINTR);
if (pid == child) {
if (WIFSTOPPED(status))
sudo_debug(8, "command stopped, signal %d", WSTOPSIG(status));
else if (WIFSIGNALED(status))
sudo_debug(8, "command killed, signal %d", WTERMSIG(status));
else
sudo_debug(8, "command exited: %d", WEXITSTATUS(status));
cstat.type = CMD_WSTATUS;
cstat.val = status;
do {
n = send(backchannel, &cstat, sizeof(cstat), 0);
} while (n == -1 && errno == EINTR);
if (n != sizeof(cstat)) {
/*
* If child failed to exec it sends its own status
* which will result in ECONNRERFUSED here.
* XXX - treat other errno as fatal
*/
sudo_debug(8, "unable to send wait status: %s",
strerror(errno));
if (WIFSTOPPED(status)) {
sudo_debug(8, "command stopped, signal %d",
WSTOPSIG(status));
} else if (WIFSIGNALED(status)) {
sudo_debug(8, "command killed, signal %d",
WTERMSIG(status));
} else {
sudo_debug(8, "sent wait status to parent");
sudo_debug(8, "command exited: %d", WEXITSTATUS(status));
alive = FALSE;
}
if (!WIFSTOPPED(status)) {
/* XXX */
_exit(1); /* child dead */
/* Send wait status unless we previously sent errno. */
if (cstat.type != CMD_ERRNO) {
cstat.type = CMD_WSTATUS;
cstat.val = status;
n = send_status(backchannel, &cstat);
if (n == -1)
goto done;
}
if (!alive)
goto done;
}
}
n = select(backchannel + 1, fdsr, NULL, NULL, NULL);
/* Check for signal on backchannel or errno on errpipe. */
FD_SET(backchannel, fdsr);
if (errpipe[0] != -1)
FD_SET(errpipe[0], fdsr);
maxfd = MAX(errpipe[0], backchannel);
n = select(maxfd + 1, fdsr, NULL, NULL, NULL);
if (n == -1) {
if (errno == EINTR)
continue;
error(1, "select failed");
}
/* read command from backchannel, should be a signal */
n = recv(backchannel, &cstat, sizeof(cstat), 0);
if (n == -1) {
if (errno == EINTR)
if (FD_ISSET(errpipe[0], fdsr)) {
/* read errno or EOF from command pipe */
n = read(errpipe[0], &cstat, sizeof(cstat));
if (n == -1) {
if (errno == EINTR)
continue;
warning("error reading from pipe");
goto done;
}
if (n == sizeof(cstat)) {
/* execve() failed, relay errno back to parent */
if (cstat.type == CMD_ERRNO) {
n = send_status(backchannel, &cstat);
if (n == -1)
goto done;
} else
warningx("unexpected reply type on pipe: %d", cstat.type);
}
/* Got errno or EOF, either way we are done with errpipe. */
FD_CLR(errpipe[0], fdsr);
close(errpipe[0]);
errpipe[0] = -1;
}
if (FD_ISSET(backchannel, fdsr)) {
/* read command from backchannel, should be a signal */
n = recv(backchannel, &cstat, sizeof(cstat), 0);
if (n == -1) {
if (errno == EINTR)
continue;
warning("error reading from socketpair");
goto done;
}
if (cstat.type != CMD_SIGNO) {
warningx("unexpected reply type on backchannel: %d", cstat.type);
continue;
warning("error reading command status");
break;
}
deliver_signal(child, cstat.val);
}
if (cstat.type != CMD_SIGNO) {
warningx("unexpected reply type on backchannel: %d", cstat.type);
continue;
}
deliver_signal(child, cstat.val);
}
_exit(1); /* XXX */
done:
if (alive)
kill(child, SIGKILL);
_exit(1);
bad:
return errno;