signal_pipe explicitly.
#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
-volatile pid_t cmnd_pid = -1;
-volatile pid_t ppgrp = -1;
-
-/*
- * Generic handler for signals received by the sudo front end while the
- * command is running. The other end is checked in the main event loop.
- */
-#ifdef SA_SIGINFO
-void
-exec_handler(int s, siginfo_t *info, void *context)
-{
- unsigned char signo = (unsigned char)s;
-
- /*
- * Do not forward signals sent by a process in the command's process
- * group, do not forward it as we don't want the child to indirectly
- * kill itself. For example, this can happen with some versions of
- * reboot that call kill(-1, SIGTERM) to kill all other processes.
- */
- if (s != SIGCHLD && USER_SIGNALED(info) && info->si_pid != 0) {
- pid_t si_pgrp = getpgid(info->si_pid);
- if (si_pgrp != -1) {
- if (si_pgrp == ppgrp || si_pgrp == cmnd_pid)
- return;
- } else if (info->si_pid == cmnd_pid) {
- return;
- }
- }
-
- /*
- * The pipe is non-blocking, if we overflow the kernel's pipe
- * buffer we drop the signal. This is not a problem in practice.
- */
- while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
- if (errno != EINTR)
- break;
- }
-}
-#else
-void
-exec_handler(int s)
-{
- unsigned char signo = (unsigned char)s;
-
- /*
- * The pipe is non-blocking, if we overflow the kernel's pipe
- * buffer we drop the signal. This is not a problem in practice.
- */
- while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
- if (errno != EINTR)
- break;
- }
-}
-#endif
-
/*
* Setup the execution environment and execute the command.
* If SELinux is enabled, run the command via sesh, otherwise
}
/*
- * Drain pending signals from signal_pipe written by sudo_handler().
- * Handles the case where the signal was sent to us before
- * we have executed the command.
- * Returns 1 if we should terminate, else 0.
+ * Check for caught signals sent to sudo before command execution.
+ * Also suspends the process if SIGTSTP was caught.
+ * Returns true if we should terminate, else false.
*/
-static int
-dispatch_pending_signals(struct command_status *cstat)
+bool
+sudo_terminated(struct command_status *cstat)
{
- ssize_t nread;
- struct sigaction sa;
- unsigned char signo = 0;
- int ret = 0;
- debug_decl(dispatch_pending_signals, SUDO_DEBUG_EXEC)
-
- for (;;) {
- nread = read(signal_pipe[0], &signo, sizeof(signo));
- if (nread <= 0) {
- /* It should not be possible to get EOF but just in case. */
- if (nread == 0)
- errno = ECONNRESET;
- /* Restart if interrupted by signal so the pipe doesn't fill. */
- if (errno == EINTR)
- continue;
- /* If pipe is empty, we are done. */
- if (errno == EAGAIN)
+ int signo;
+ bool sigtstp = false;
+ debug_decl(sudo_terminated, SUDO_DEBUG_EXEC)
+
+ for (signo = 0; signo < NSIG; signo++) {
+ if (signal_pending(signo)) {
+ switch (signo) {
+ case SIGTSTP:
+ /* Suspend below if not terminated. */
+ sigtstp = true;
break;
- sudo_debug_printf(SUDO_DEBUG_ERROR, "error reading signal pipe %s",
- strerror(errno));
- cstat->type = CMD_ERRNO;
- cstat->val = errno;
- ret = 1;
- break;
- }
- /* Take the first terminal signal. */
- if (signo == SIGINT || signo == SIGQUIT) {
- cstat->type = CMD_WSTATUS;
- cstat->val = signo + 128;
- ret = 1;
- break;
+ default:
+ /* Terminal signal, do not exec command. */
+ cstat->type = CMD_WSTATUS;
+ cstat->val = signo + 128;
+ debug_return_bool(true);
+ break;
+ }
}
}
- /* Only stop if we haven't already been terminated. */
- if (signo == SIGTSTP) {
+ if (sigtstp) {
+ struct sigaction sa;
+ sigset_t set, oset;
+
+ /* Send SIGTSTP to ourselves, unblocking it if needed. */
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_DFL;
if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
+ sigemptyset(&set);
+ sigaddset(&set, SIGTSTP);
+ sigprocmask(SIG_UNBLOCK, &set, &oset);
if (kill(getpid(), SIGTSTP) != 0)
sudo_warn("kill(%d, SIGTSTP)", (int)getpid());
- /* No need to reinstall SIGTSTP handler. */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ /* No need to restore old SIGTSTP handler. */
}
- debug_return_int(ret);
+ debug_return_bool(false);
}
/*
{
debug_decl(sudo_execute, SUDO_DEBUG_EXEC)
- if (dispatch_pending_signals(cstat) != 0) {
- /* Killed by SIGINT or SIGQUIT */
- debug_return_int(0);
- }
-
/* If running in background mode, fork and exit. */
if (ISSET(details->flags, CD_BACKGROUND)) {
switch (sudo_debug_fork()) {
* as sudoedit, there is no command timeout and there is no close
* function, just exec directly. Only returns on error.
*/
- exec_cmnd(details, -1);
- cstat->type = CMD_ERRNO;
- cstat->val = errno;
+ if (!sudo_terminated(cstat)) {
+ exec_cmnd(details, -1);
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ }
} else {
/*
* No pty but we need to wait for the command to finish to
#include "sudo_plugin_int.h"
struct monitor_closure {
+ pid_t cmnd_pid;
+ pid_t cmnd_pgrp;
+ pid_t mon_pgrp;
+ int backchannel;
+ struct command_status *cstat;
struct sudo_event_base *evbase;
struct sudo_event *errpipe_event;
struct sudo_event *backchannel_event;
- struct sudo_event *signal_pipe_event;
- struct command_status *cstat;
- int backchannel;
+ struct sudo_event *sigint_event;
+ struct sudo_event *sigquit_event;
+ struct sudo_event *sigtstp_event;
+ struct sudo_event *sigterm_event;
+ struct sudo_event *sighup_event;
+ struct sudo_event *sigusr1_event;
+ struct sudo_event *sigusr2_event;
+ struct sudo_event *sigchld_event;
};
-static volatile pid_t cmnd_pgrp;
-static pid_t mon_pgrp;
-
-extern int io_fds[6]; /* XXX */
-
-/*
- * Generic handler for signals recieved by the monitor process.
- * The other end of signal_pipe is checked in the monitor event loop.
- */
-#ifdef SA_SIGINFO
-static void
-mon_handler(int s, siginfo_t *info, void *context)
-{
- unsigned char signo = (unsigned char)s;
-
- /*
- * If the signal came from the process group of the command we ran,
- * do not forward it as we don't want the child to indirectly kill
- * itself. This can happen with, e.g., BSD-derived versions of
- * reboot that call kill(-1, SIGTERM) to kill all other processes.
- */
- if (s != SIGCHLD && USER_SIGNALED(info) && info->si_pid != 0) {
- pid_t si_pgrp = getpgid(info->si_pid);
- if (si_pgrp != -1) {
- if (si_pgrp == cmnd_pgrp)
- return;
- } else if (info->si_pid == cmnd_pid) {
- return;
- }
- }
-
- /*
- * The pipe is non-blocking, if we overflow the kernel's pipe
- * buffer we drop the signal. This is not a problem in practice.
- */
- while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
- if (errno != EINTR)
- break;
- }
-}
-#else
-static void
-mon_handler(int s)
-{
- unsigned char signo = (unsigned char)s;
-
- /*
- * The pipe is non-blocking, if we overflow the kernel's pipe
- * buffer we drop the signal. This is not a problem in practice.
- */
- while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
- if (errno != EINTR)
- break;
- }
-}
-#endif
-
/*
* Deliver a signal to the running command.
* The signal was either forwarded to us by the parent sudo process
* also specify whether the command should have the controlling tty.
*/
static void
-deliver_signal(pid_t pid, int signo, bool from_parent)
+deliver_signal(struct monitor_closure *mc, int signo, bool from_parent)
{
char signame[SIG2STR_MAX];
int status;
debug_decl(deliver_signal, SUDO_DEBUG_EXEC);
/* Avoid killing more than a single process or process group. */
- if (pid <= 0)
+ if (mc->cmnd_pid <= 0)
debug_return;
if (signo == SIGCONT_FG)
signame, from_parent ? " from parent" : "");
switch (signo) {
case SIGALRM:
- terminate_command(pid, true);
+ terminate_command(mc->cmnd_pid, true);
break;
case SIGCONT_FG:
/* Continue in foreground, grant it controlling tty. */
do {
- status = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
+ status = tcsetpgrp(io_fds[SFD_SLAVE], mc->cmnd_pgrp);
} while (status == -1 && errno == EINTR);
- killpg(pid, SIGCONT);
+ killpg(mc->cmnd_pid, SIGCONT);
break;
case SIGCONT_BG:
/* Continue in background, I take controlling tty. */
do {
- status = tcsetpgrp(io_fds[SFD_SLAVE], mon_pgrp);
+ status = tcsetpgrp(io_fds[SFD_SLAVE], mc->mon_pgrp);
} while (status == -1 && errno == EINTR);
- killpg(pid, SIGCONT);
+ killpg(mc->cmnd_pid, SIGCONT);
break;
case SIGKILL:
_exit(1); /* XXX */
/* NOTREACHED */
default:
/* Relay signal to command. */
- killpg(pid, signo);
+ killpg(mc->cmnd_pid, signo);
break;
}
debug_return;
* Otherwise, cstat is filled in but not sent.
*/
static void
-mon_handle_sigchld(int backchannel, struct command_status *cstat)
+mon_handle_sigchld(struct monitor_closure *mc)
{
char signame[SIG2STR_MAX];
int status;
/* Read command status. */
do {
- pid = waitpid(cmnd_pid, &status, WUNTRACED|WCONTINUED|WNOHANG);
+ pid = waitpid(mc->cmnd_pid, &status, WUNTRACED|WCONTINUED|WNOHANG);
} while (pid == -1 && errno == EINTR);
switch (pid) {
case 0:
if (WIFCONTINUED(status)) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) resumed",
- __func__, (int)cmnd_pid);
+ __func__, (int)mc->cmnd_pid);
} else if (WIFSTOPPED(status)) {
if (sig2str(WSTOPSIG(status), signame) == -1)
snprintf(signame, sizeof(signame), "%d", WSTOPSIG(status));
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s",
- __func__, (int)cmnd_pid, signame);
+ __func__, (int)mc->cmnd_pid, signame);
} else if (WIFSIGNALED(status)) {
if (sig2str(WTERMSIG(status), signame) == -1)
snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
- __func__, (int)cmnd_pid, signame);
- cmnd_pid = -1;
+ __func__, (int)mc->cmnd_pid, signame);
+ mc->cmnd_pid = -1;
} else if (WIFEXITED(status)) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
- __func__, (int)cmnd_pid, WEXITSTATUS(status));
- cmnd_pid = -1;
+ __func__, (int)mc->cmnd_pid, WEXITSTATUS(status));
+ mc->cmnd_pid = -1;
} else {
sudo_debug_printf(SUDO_DEBUG_WARN,
"%s: unexpected wait status %d for command (%d)",
- __func__, status, (int)cmnd_pid);
+ __func__, status, (int)mc->cmnd_pid);
}
/* Don't overwrite execve() failure with child exit status. */
- if (cstat->type != CMD_ERRNO) {
+ if (mc->cstat->type != CMD_ERRNO) {
/*
* Store wait status in cstat and forward to parent if stopped.
*/
- cstat->type = CMD_WSTATUS;
- cstat->val = status;
+ mc->cstat->type = CMD_WSTATUS;
+ mc->cstat->val = status;
if (WIFSTOPPED(status)) {
/* Save the foreground pgid so we can restore it later. */
do {
pid = tcgetpgrp(io_fds[SFD_SLAVE]);
} while (pid == -1 && errno == EINTR);
- if (pid != mon_pgrp)
- cmnd_pgrp = pid;
- send_status(backchannel, cstat);
+ if (pid != mc->mon_pgrp)
+ mc->cmnd_pgrp = pid;
+ send_status(mc->backchannel, mc->cstat);
}
}
}
static void
-mon_signal_pipe_cb(int fd, int what, void *v)
+mon_signal_cb(int signo, int what, void *v)
{
- struct monitor_closure *mc = v;
- unsigned char signo;
- ssize_t nread;
- debug_decl(mon_signal_pipe_cb, SUDO_DEBUG_EXEC);
-
- nread = read(fd, &signo, sizeof(signo));
- if (nread <= 0) {
- /* It should not be possible to get EOF but just in case. */
- if (nread == 0)
- errno = ECONNRESET;
- if (errno != EINTR && errno != EAGAIN) {
- sudo_warn(U_("error reading from signal pipe"));
- sudo_ev_loopbreak(mc->evbase);
+ struct sudo_ev_siginfo_container *sc = v;
+ struct monitor_closure *mc = sc->closure;
+ debug_decl(mon_signal_cb, SUDO_DEBUG_EXEC);
+
+ /*
+ * Handle SIGCHLD specially and deliver other signals
+ * directly to the command.
+ */
+ if (signo == SIGCHLD) {
+ mon_handle_sigchld(mc);
+ if (mc->cmnd_pid == -1) {
+ /* Command exited or was killed, exit event loop. */
+ sudo_ev_loopexit(mc->evbase);
}
} else {
/*
- * Handle SIGCHLD specially and deliver other signals
- * directly to the command.
+ * If the signal came from the process group of the command we ran,
+ * do not forward it as we don't want the child to indirectly kill
+ * itself. This can happen with, e.g., BSD-derived versions of
+ * reboot that call kill(-1, SIGTERM) to kill all other processes.
*/
- if (signo == SIGCHLD) {
- mon_handle_sigchld(mc->backchannel, mc->cstat);
- if (cmnd_pid == -1) {
- /* Remove all but the errpipe event. */
- sudo_ev_del(mc->evbase, mc->backchannel_event);
- sudo_ev_del(mc->evbase, mc->signal_pipe_event);
+ if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == mc->cmnd_pgrp)
+ debug_return;
+ } else if (sc->siginfo->si_pid == mc->cmnd_pid) {
+ debug_return;
}
- } else {
- deliver_signal(cmnd_pid, signo, false);
}
+ deliver_signal(mc, signo, false);
}
debug_return;
}
ssize_t n;
debug_decl(mon_backchannel_cb, SUDO_DEBUG_EXEC);
- /* Read command from backchannel, should be a signal. */
+ /*
+ * Read command from backchannel, should be a signal.
+ * Note that the backchannel is a *blocking* socket.
+ */
n = recv(fd, &cstmp, sizeof(cstmp), MSG_WAITALL);
if (n != sizeof(cstmp)) {
if (n == -1) {
} else {
/* short read or EOF, parent process died? */
}
+ /* XXX - need a way to distinguish non-exec error. */
+ mc->cstat->type = CMD_ERRNO;
+ mc->cstat->val = n ? EIO : ECONNRESET;
sudo_ev_loopbreak(mc->evbase);
} else {
if (cstmp.type == CMD_SIGNO) {
- deliver_signal(cmnd_pid, cstmp.val, true);
+ deliver_signal(mc, cstmp.val, true);
} else {
sudo_warnx(U_("unexpected reply type on backchannel: %d"), cstmp.type);
}
debug_decl(fill_exec_closure_monitor, SUDO_DEBUG_EXEC);
/* Fill in the non-event part of the closure. */
- cstat->type = CMD_INVALID;
- cstat->val = 0;
mc->cstat = cstat;
mc->backchannel = backchannel;
+ mc->mon_pgrp = getpgrp();
/* Setup event base and events. */
mc->evbase = sudo_ev_base_alloc();
if (mc->evbase == NULL)
sudo_fatal(NULL);
- /* Event for local signals via signal_pipe. */
- mc->signal_pipe_event = sudo_ev_alloc(signal_pipe[0],
- SUDO_EV_READ|SUDO_EV_PERSIST, mon_signal_pipe_cb, mc);
- if (mc->signal_pipe_event == NULL)
- sudo_fatal(NULL);
- if (sudo_ev_add(mc->evbase, mc->signal_pipe_event, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
-
/* Event for command status via errfd. */
mc->errpipe_event = sudo_ev_alloc(errfd,
SUDO_EV_READ|SUDO_EV_PERSIST, mon_errpipe_cb, mc);
sudo_fatal(NULL);
if (sudo_ev_add(mc->evbase, mc->backchannel_event, NULL, false) == -1)
sudo_fatal(U_("unable to add event to queue"));
+
+ /* Events for local signals. */
+ mc->sigint_event = sudo_ev_alloc(SIGINT,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigint_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->sigint_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ mc->sigquit_event = sudo_ev_alloc(SIGQUIT,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigquit_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->sigquit_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ mc->sigtstp_event = sudo_ev_alloc(SIGTSTP,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigtstp_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->sigtstp_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ mc->sigterm_event = sudo_ev_alloc(SIGTERM,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigterm_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->sigterm_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ mc->sighup_event = sudo_ev_alloc(SIGHUP,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sighup_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->sighup_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ mc->sigusr1_event = sudo_ev_alloc(SIGUSR1,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigusr1_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->sigusr1_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ mc->sigusr2_event = sudo_ev_alloc(SIGUSR2,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigusr2_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->sigusr2_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ mc->sigchld_event = sudo_ev_alloc(SIGCHLD,
+ SUDO_EV_SIGINFO, mon_signal_cb, mc);
+ if (mc->sigchld_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->sigchld_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
}
/*
* resets signal handlers and forks a child to call exec_cmnd_pty().
* Waits for status changes from the command and relays them to the
* parent and relays signals from the parent to the command.
+ * Must be called with signals blocked and the old signal mask in oset.
* Returns an error if fork(2) fails, else calls _exit(2).
*/
int
-exec_monitor(struct command_details *details, bool foreground, int backchannel)
+exec_monitor(struct command_details *details, sigset_t *oset,
+ bool foreground, int backchannel)
{
+ struct monitor_closure mc = { 0 };
struct command_status cstat;
- struct monitor_closure mc;
sigaction_t sa;
- int errpipe[2], n;
+ int errpipe[2];
debug_decl(exec_monitor, SUDO_DEBUG_EXEC);
/* Close unused fds. */
if (io_fds[SFD_USERTTY] != -1)
close(io_fds[SFD_USERTTY]);
- /*
- * We use a pipe to atomically handle signal notification within
- * the event loop.
- */
- if (pipe2(signal_pipe, O_NONBLOCK) != 0)
- sudo_fatal(U_("unable to create pipe"));
-
- /* Reset SIGWINCH and SIGALRM. */
+ /* Ignore any SIGTTIN or SIGTTOU we receive (shouldn't be possible). */
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
- sa.sa_handler = SIG_DFL;
- if (sudo_sigaction(SIGWINCH, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGWINCH);
- if (sudo_sigaction(SIGALRM, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGALRM);
-
- /* Ignore any SIGTTIN or SIGTTOU we get. */
sa.sa_handler = SIG_IGN;
if (sudo_sigaction(SIGTTIN, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTTIN);
if (sudo_sigaction(SIGTTOU, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTTOU);
- /* Block all signals in mon_handler(). */
- sigfillset(&sa.sa_mask);
-
- /* Note: HP-UX poll() will not be interrupted if SA_RESTART is set. */
- sa.sa_flags = SA_INTERRUPT;
-#ifdef SA_SIGINFO
- sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = mon_handler;
-#else
- sa.sa_handler = mon_handler;
-#endif
- if (sudo_sigaction(SIGCHLD, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGCHLD);
-
- /* Catch common signals so we can cleanup properly. */
- sa.sa_flags = SA_RESTART;
-#ifdef SA_SIGINFO
- sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = mon_handler;
-#else
- sa.sa_handler = mon_handler;
-#endif
- if (sudo_sigaction(SIGHUP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGHUP);
- if (sudo_sigaction(SIGINT, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGINT);
- if (sudo_sigaction(SIGQUIT, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGQUIT);
- if (sudo_sigaction(SIGTERM, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGTERM);
- if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
- if (sudo_sigaction(SIGUSR1, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGUSR1);
- if (sudo_sigaction(SIGUSR2, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGUSR2);
-
/*
* Start a new session with the parent as the session leader
* and the slave pty as the controlling terminal.
goto bad;
}
- mon_pgrp = getpgrp(); /* save a copy of our process group */
-
- /* Start command and wait for it to stop or exit */
- if (pipe2(errpipe, O_CLOEXEC) == -1)
+ /*
+ * We use a pipe to get errno if execve(2) fails in the child.
+ */
+ if (pipe2(errpipe, O_CLOEXEC) != 0)
sudo_fatal(U_("unable to create pipe"));
- cmnd_pid = sudo_debug_fork();
- if (cmnd_pid == -1) {
+
+ mc.cmnd_pid = sudo_debug_fork();
+ switch (mc.cmnd_pid) {
+ case -1:
sudo_warn(U_("unable to fork"));
goto bad;
- }
- if (cmnd_pid == 0) {
- /* We pass errno back to our parent via pipe on exec failure. */
+ case 0:
+ /* child */
+ sigprocmask(SIG_SETMASK, oset, NULL);
close(backchannel);
- close(signal_pipe[0]);
- close(signal_pipe[1]);
close(errpipe[0]);
restore_signals();
/* Send the command's pid to main sudo process. */
cstat.type = CMD_PID;
- cstat.val = cmnd_pid;
- while (send(backchannel, &cstat, sizeof(cstat), 0) == -1) {
- if (errno != EINTR)
- break;
- }
+ cstat.val = mc.cmnd_pid;
+ send_status(backchannel, &cstat);
+
+ /*
+ * Create new event base and register read events for the
+ * signal pipe, error pipe, and backchannel.
+ */
+ fill_exec_closure_monitor(&mc, &cstat, errpipe[0], backchannel);
+
+ /* Restore signal mask now that signal handlers are setup. */
+ sigprocmask(SIG_SETMASK, oset, NULL);
/* If any of stdin/stdout/stderr are pipes, close them in parent. */
if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
close(io_fds[SFD_STDERR]);
/* Put command in its own process group. */
- cmnd_pgrp = cmnd_pid;
- setpgid(cmnd_pid, cmnd_pgrp);
+ mc.cmnd_pgrp = mc.cmnd_pid;
+ setpgid(mc.cmnd_pid, mc.cmnd_pgrp);
/* Make the command the foreground process for the pty slave. */
if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
+ int n;
+
do {
- n = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
+ n = tcsetpgrp(io_fds[SFD_SLAVE], mc.cmnd_pgrp);
} while (n == -1 && errno == EINTR);
}
- /*
- * Create new event base and register read events for the
- * signal pipe, error pipe, and backchannel.
- */
- fill_exec_closure_monitor(&mc, &cstat, errpipe[0], backchannel);
-
/*
* Wait for errno on pipe, signal on backchannel or for SIGCHLD.
* The event loop ends when the child is no longer running and
* the error pipe is closed.
*/
+ cstat.type = CMD_INVALID;
+ cstat.val = 0;
(void) sudo_ev_loop(mc.evbase, 0);
- if (cmnd_pid != -1) {
- /* XXX An error occurred, should send a message back. */
+ if (mc.cmnd_pid != -1) {
+ pid_t pid;
+
+ /* Command still running, did the parent die? */
sudo_debug_printf(SUDO_DEBUG_ERROR,
- "Command still running after event loop exit, sending SIGKILL");
- kill(cmnd_pid, SIGKILL);
- /* XXX - wait for cmnd_pid to exit */
- } else {
- /* Send parent status. */
- send_status(backchannel, &cstat);
+ "Command still running after event loop exit, terminating");
+ terminate_command(mc.cmnd_pid, true);
+ do {
+ pid = waitpid(mc.cmnd_pid, NULL, 0);
+ } while (pid == -1 && errno == EINTR);
+ /* XXX - update cstat with wait status? */
}
+ /* Send parent status. */
+ send_status(backchannel, &cstat);
#ifdef HAVE_SELINUX
if (ISSET(details->flags, CD_RBAC_ENABLED)) {
_exit(1);
bad:
- debug_return_int(errno);
+ debug_return_int(-1);
}
#include "sudo_plugin_int.h"
struct exec_closure_nopty {
- pid_t child;
+ pid_t cmnd_pid;
+ pid_t ppgrp;
struct command_status *cstat;
struct command_details *details;
struct sudo_event_base *evbase;
- struct sudo_event *signal_event;
struct sudo_event *errpipe_event;
+ struct sudo_event *sigint_event;
+ struct sudo_event *sigquit_event;
+ struct sudo_event *sigtstp_event;
+ struct sudo_event *sigterm_event;
+ struct sudo_event *sighup_event;
+ struct sudo_event *sigalrm_event;
+ struct sudo_event *sigpipe_event;
+ struct sudo_event *sigusr1_event;
+ struct sudo_event *sigusr2_event;
+ struct sudo_event *sigchld_event;
+ struct sudo_event *sigcont_event;
+ struct sudo_event *siginfo_event;
};
-static void signal_pipe_cb(int fd, int what, void *v);
-#ifdef SA_SIGINFO
-static void exec_handler_user_only(int s, siginfo_t *info, void *context);
-#endif
+static void handle_sigchld_nopty(struct exec_closure_nopty *ec);
/* Note: this is basically the same as mon_errpipe_cb() in exec_monitor.c */
static void
debug_return;
}
+/* Signal callback */
+static void
+signal_cb_nopty(int signo, int what, void *v)
+{
+ struct sudo_ev_siginfo_container *sc = v;
+ struct exec_closure_nopty *ec = sc->closure;
+ char signame[SIG2STR_MAX];
+ debug_decl(signal_cb_nopty, SUDO_DEBUG_EXEC)
+
+ if (ec->cmnd_pid == -1)
+ debug_return;
+
+ if (sig2str(signo, signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_DIAG,
+ "%s: evbase %p, command: %d, signo %s(%d), cstat %p",
+ __func__, ec->evbase, (int)ec->cmnd_pid, signame, signo, ec->cstat);
+
+ switch (signo) {
+ case SIGCHLD:
+ handle_sigchld_nopty(ec);
+ if (ec->cmnd_pid == -1) {
+ /* Command exited or was killed, exit event loop. */
+ sudo_ev_loopexit(ec->evbase);
+ }
+ debug_return;
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTSTP:
+ /*
+ * Only forward user-generated signals not sent by a process in
+ * the command's own process group. Signals sent by the kernel
+ * may include SIGTSTP when the user presses ^Z. Curses programs
+ * often trap ^Z and send SIGTSTP to their own pgrp, so we don't
+ * want to send an extra SIGTSTP.
+ */
+ if (!USER_SIGNALED(sc->siginfo))
+ debug_return;
+ if (sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == ec->ppgrp || si_pgrp == ec->cmnd_pid)
+ debug_return;
+ } else if (sc->siginfo->si_pid == ec->cmnd_pid) {
+ debug_return;
+ }
+ }
+ break;
+ default:
+ /*
+ * Do not forward signals sent by a process in the command's process
+ * group, as we don't want the command to indirectly kill itself.
+ * For example, this can happen with some versions of reboot that
+ * call kill(-1, SIGTERM) to kill all other processes.
+ */
+ if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == ec->ppgrp || si_pgrp == ec->cmnd_pid)
+ debug_return;
+ } else if (sc->siginfo->si_pid == ec->cmnd_pid) {
+ debug_return;
+ }
+ }
+ break;
+ }
+
+ /* Send signal to command. */
+ if (signo == SIGALRM) {
+ terminate_command(ec->cmnd_pid, false);
+ } else if (kill(ec->cmnd_pid, signo) != 0) {
+ sudo_warn("kill(%d, SIG%s)", (int)ec->cmnd_pid, signame);
+ }
+
+ debug_return;
+}
+
+
/*
* Fill in the exec closure and setup initial exec events.
* Allocates events for the signal pipe and error pipe.
debug_decl(fill_exec_closure_nopty, SUDO_DEBUG_EXEC)
/* Fill in the non-event part of the closure. */
- ec->child = cmnd_pid;
+ ec->ppgrp = getpgrp();
ec->cstat = cstat;
ec->details = details;
if (ec->evbase == NULL)
sudo_fatal(NULL);
- /* Event for local signals via signal_pipe. */
- ec->signal_event = sudo_ev_alloc(signal_pipe[0],
- SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, ec);
- if (ec->signal_event == NULL)
- sudo_fatal(NULL);
- if (sudo_ev_add(ec->evbase, ec->signal_event, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
-
/* Event for command status via errfd. */
ec->errpipe_event = sudo_ev_alloc(errfd,
SUDO_EV_READ|SUDO_EV_PERSIST, errpipe_cb, ec);
sudo_fatal(NULL);
if (sudo_ev_add(ec->evbase, ec->errpipe_event, NULL, false) == -1)
sudo_fatal(U_("unable to add event to queue"));
-
- sudo_debug_printf(SUDO_DEBUG_INFO, "signal pipe fd %d\n", signal_pipe[0]);
sudo_debug_printf(SUDO_DEBUG_INFO, "error pipe fd %d\n", errfd);
+ /* Events for local signals. */
+ ec->sigint_event = sudo_ev_alloc(SIGINT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigint_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigint_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigquit_event = sudo_ev_alloc(SIGQUIT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigquit_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigquit_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigtstp_event = sudo_ev_alloc(SIGTSTP,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigtstp_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigtstp_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigterm_event = sudo_ev_alloc(SIGTERM,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigterm_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigterm_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sighup_event = sudo_ev_alloc(SIGHUP,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sighup_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sighup_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigalrm_event = sudo_ev_alloc(SIGALRM,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigalrm_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigalrm_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigpipe_event = sudo_ev_alloc(SIGPIPE,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigpipe_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigpipe_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigusr1_event = sudo_ev_alloc(SIGUSR1,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigusr1_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigusr1_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigusr2_event = sudo_ev_alloc(SIGUSR2,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigusr2_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigusr2_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigchld_event = sudo_ev_alloc(SIGCHLD,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigchld_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigchld_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigcont_event = sudo_ev_alloc(SIGCONT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigcont_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigcont_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+#ifdef SIGINFO
+ ec->siginfo_event = sudo_ev_alloc(SIGINFO,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->siginfo_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->siginfo_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+#endif
+
+ debug_return;
+}
+
+/*
+ * Free the dynamically-allocated contents of the exec closure.
+ */
+static void
+free_exec_closure_nopty(struct exec_closure_nopty *ec)
+{
+ debug_decl(free_exec_closure_nopty, SUDO_DEBUG_EXEC)
+
+ sudo_ev_base_free(ec->evbase);
+ sudo_ev_free(ec->errpipe_event);
+ sudo_ev_free(ec->sigint_event);
+ sudo_ev_free(ec->sigquit_event);
+ sudo_ev_free(ec->sigtstp_event);
+ sudo_ev_free(ec->sigterm_event);
+ sudo_ev_free(ec->sighup_event);
+ sudo_ev_free(ec->sigalrm_event);
+ sudo_ev_free(ec->sigpipe_event);
+ sudo_ev_free(ec->sigusr1_event);
+ sudo_ev_free(ec->sigusr2_event);
+ sudo_ev_free(ec->sigchld_event);
+ sudo_ev_free(ec->sigcont_event);
+ sudo_ev_free(ec->siginfo_event);
+
debug_return;
}
int
exec_nopty(struct command_details *details, struct command_status *cstat)
{
- struct exec_closure_nopty ec;
- sigaction_t sa;
+ struct exec_closure_nopty ec = { 0 };
+ sigset_t set, oset;
int errpipe[2];
debug_decl(exec_nopty, SUDO_DEBUG_EXEC)
/*
- * We use a pipe to get errno if execve(2) fails in the child.
- */
- if (pipe2(errpipe, O_CLOEXEC) == -1)
- sudo_fatal(U_("unable to create pipe"));
-
- /*
- * Signals to pass to the child process (excluding SIGALRM).
- * We block all other signals while running the signal handler.
- * Note: HP-UX select() will not be interrupted if SA_RESTART set.
- *
- * We also need to handle suspend/restore of sudo and the command.
- * In most cases, the command will be in the same process group as
- * sudo and job control will "just work". However, if the command
- * changes its process group ID and does not change it back (or is
- * kill by SIGSTOP which is not catchable), we need to resume the
- * command manually. Also, if SIGTSTP is sent directly to sudo,
- * we need to suspend the command, and then suspend ourself, restoring
- * the default SIGTSTP handler temporarily.
- *
- * XXX - currently we send SIGCONT upon resume in some cases where
- * we don't need to (e.g. command pgrp == parent pgrp).
+ * The policy plugin's session init must be run before we fork
+ * or certain pam modules won't be able to track their state.
*/
-
- memset(&sa, 0, sizeof(sa));
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
-#ifdef SA_SIGINFO
- sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = exec_handler;
-#else
- sa.sa_handler = exec_handler;
-#endif
- if (sudo_sigaction(SIGTERM, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGTERM);
- if (sudo_sigaction(SIGHUP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGHUP);
- if (sudo_sigaction(SIGALRM, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGALRM);
- if (sudo_sigaction(SIGPIPE, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGPIPE);
- if (sudo_sigaction(SIGUSR1, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGUSR1);
- if (sudo_sigaction(SIGUSR2, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGUSR2);
- if (sudo_sigaction(SIGCHLD, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGCHLD);
- if (sudo_sigaction(SIGCONT, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGCONT);
-#ifdef SIGINFO
- if (sudo_sigaction(SIGINFO, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGINFO);
-#endif
+ if (policy_init_session(details) != true)
+ sudo_fatalx(U_("policy plugin failed session initialization"));
/*
- * When not running the command in a pty, we do not want to
- * forward signals generated by the kernel that the child will
- * already have received by virtue of being in the controlling
- * terminals's process group (SIGINT, SIGQUIT, SIGTSTP).
+ * We use a pipe to get errno if execve(2) fails in the child.
*/
-#ifdef SA_SIGINFO
- sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = exec_handler_user_only;
-#endif
- if (sudo_sigaction(SIGINT, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGINT);
- if (sudo_sigaction(SIGQUIT, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGQUIT);
- if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
+ if (pipe2(errpipe, O_CLOEXEC) != 0)
+ sudo_fatal(U_("unable to create pipe"));
/*
- * The policy plugin's session init must be run before we fork
- * or certain pam modules won't be able to track their state.
+ * Block signals until we have our handlers setup in the parent so
+ * we don't miss SIGCHLD if the command exits immediately.
*/
- if (policy_init_session(details) != true)
- sudo_fatalx(U_("policy plugin failed session initialization"));
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oset);
- ppgrp = getpgrp(); /* parent's process group */
+ /* Check for early termination or suspend signals before we fork. */
+ if (sudo_terminated(cstat)) {
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ debug_return_int(0);
+ }
- cmnd_pid = sudo_debug_fork();
- switch (cmnd_pid) {
+ ec.cmnd_pid = sudo_debug_fork();
+ switch (ec.cmnd_pid) {
case -1:
sudo_fatal(U_("unable to fork"));
break;
case 0:
/* child */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
close(errpipe[0]);
- close(signal_pipe[0]);
- close(signal_pipe[1]);
exec_cmnd(details, errpipe[1]);
while (write(errpipe[1], &errno, sizeof(int)) == -1) {
if (errno != EINTR)
_exit(1);
}
sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
- (int)cmnd_pid);
+ (int)ec.cmnd_pid);
close(errpipe[1]);
/* No longer need execfd. */
alarm(details->timeout);
/*
- * Fill in exec closure, allocate event base and two persistent events:
- * the signal pipe and the error pipe.
+ * Fill in exec closure, allocate event base, signal events and
+ * the error pipe event.
*/
fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]);
+ /* Restore signal mask now that signal handlers are setup. */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+
/*
* Non-pty event loop.
* Wait for command to exit, handles signals and the error pipe.
/* error from callback */
sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
/* kill command */
- terminate_command(ec.child, true);
+ terminate_command(ec.cmnd_pid, true);
}
#ifdef HAVE_SELINUX
#endif
/* Free things up. */
- sudo_ev_base_free(ec.evbase);
- sudo_ev_free(ec.signal_event);
- sudo_ev_free(ec.errpipe_event);
+ free_exec_closure_nopty(&ec);
debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);
}
/* Read command status. */
do {
- pid = waitpid(ec->child, &status, WUNTRACED|WNOHANG);
+ pid = waitpid(ec->cmnd_pid, &status, WUNTRACED|WNOHANG);
} while (pid == -1 && errno == EINTR);
switch (pid) {
case 0:
* Save the controlling terminal's process group so we can restore it
* after we resume, if needed. Most well-behaved shells change the
* pgrp back to its original value before suspending so we must
- * not try to restore in that case, lest we race with the child upon
+ * not try to restore in that case, lest we race with the command upon
* resume, potentially stopping sudo with SIGTTOU while the command
* continues to run.
*/
if (sig2str(signo, signame) == -1)
snprintf(signame, sizeof(signame), "%d", signo);
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s",
- __func__, (int)ec->child, signame);
+ __func__, (int)ec->cmnd_pid, signame);
fd = open(_PATH_TTY, O_RDWR);
if (fd != -1) {
}
if (saved_pgrp != -1) {
/*
- * Child was stopped trying to access the controlling terminal.
- * If the child has a different pgrp and we own the controlling
- * terminal, give it to the child's pgrp and let it continue.
+ * Command was stopped trying to access the controlling terminal.
+ * If the command has a different pgrp and we own the controlling
+ * terminal, give it to the command's pgrp and let it continue.
*/
if (signo == SIGTTOU || signo == SIGTTIN) {
- if (saved_pgrp == ppgrp) {
- pid_t child_pgrp = getpgid(ec->child);
- if (child_pgrp != ppgrp) {
- if (tcsetpgrp_nobg(fd, child_pgrp) == 0) {
- if (killpg(child_pgrp, SIGCONT) != 0) {
+ if (saved_pgrp == ec->ppgrp) {
+ pid_t cmnd_pgrp = getpgid(ec->cmnd_pid);
+ if (cmnd_pgrp != ec->ppgrp) {
+ if (tcsetpgrp_nobg(fd, cmnd_pgrp) == 0) {
+ if (killpg(cmnd_pgrp, SIGCONT) != 0) {
sudo_warn("kill(%d, SIGCONT)",
- (int)child_pgrp);
+ (int)cmnd_pgrp);
}
close(fd);
goto done;
* It is possible that we are no longer the foreground process so
* use tcsetpgrp_nobg() to prevent sudo from receiving SIGTTOU.
*/
- if (saved_pgrp != ppgrp)
+ if (saved_pgrp != ec->ppgrp)
tcsetpgrp_nobg(fd, saved_pgrp);
close(fd);
}
} else {
- /* Child has exited or been killed, we are done. */
+ /* Command has exited or been killed, we are done. */
if (WIFSIGNALED(status)) {
if (sig2str(WTERMSIG(status), signame) == -1)
snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
- __func__, (int)ec->child, signame);
+ __func__, (int)ec->cmnd_pid, signame);
} else {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
- __func__, (int)ec->child, WEXITSTATUS(status));
+ __func__, (int)ec->cmnd_pid, WEXITSTATUS(status));
}
- /* Don't overwrite execve() failure with child exit status. */
+ /* Don't overwrite execve() failure with command exit status. */
if (ec->cstat->type != CMD_ERRNO) {
ec->cstat->type = CMD_WSTATUS;
ec->cstat->val = status;
}
- ec->child = -1;
+ ec->cmnd_pid = -1;
}
done:
debug_return;
}
-
-/* Signal pipe callback */
-static void
-signal_pipe_cb(int fd, int what, void *v)
-{
- struct exec_closure_nopty *ec = v;
- char signame[SIG2STR_MAX];
- unsigned char signo;
- ssize_t nread;
- debug_decl(signal_pipe_cb, SUDO_DEBUG_EXEC)
-
- /* Process received signals until the child dies or the pipe is empty. */
- do {
- /* read signal pipe */
- nread = read(fd, &signo, sizeof(signo));
- if (nread <= 0) {
- /* It should not be possible to get EOF but just in case... */
- if (nread == 0)
- errno = ECONNRESET;
- /* Restart if interrupted by signal so the pipe doesn't fill. */
- if (errno == EINTR)
- continue;
- /* On error, store errno and break out of the event loop. */
- if (errno != EAGAIN) {
- ec->cstat->type = CMD_ERRNO;
- ec->cstat->val = errno;
- sudo_warn(U_("error reading from signal pipe"));
- sudo_ev_loopbreak(ec->evbase);
- }
- break;
- }
- if (sig2str(signo, signame) == -1)
- snprintf(signame, sizeof(signame), "%d", signo);
- sudo_debug_printf(SUDO_DEBUG_DIAG,
- "%s: evbase %p, child: %d, signo %s(%d), cstat %p",
- __func__, ec->evbase, (int)ec->child, signame, signo, ec->cstat);
-
- if (signo == SIGCHLD) {
- handle_sigchld_nopty(ec);
- if (ec->child == -1) {
- /* Command exited or was killed, exit event loop. */
- sudo_ev_del(ec->evbase, ec->signal_event);
- sudo_ev_loopexit(ec->evbase);
- }
- } else if (ec->child != -1) {
- /* Send signal to child. */
- if (signo == SIGALRM) {
- terminate_command(ec->child, false);
- } else if (kill(ec->child, signo) != 0) {
- sudo_warn("kill(%d, SIG%s)", (int)ec->child, signame);
- }
- }
- } while (ec->child != -1);
- debug_return;
-}
-
-#ifdef SA_SIGINFO
-/*
- * Generic handler for signals passed from parent -> child.
- * The other end of signal_pipe is checked in the main event loop.
- * This version is for the non-pty case and does not forward
- * signals that are generated by the kernel.
- */
-static void
-exec_handler_user_only(int s, siginfo_t *info, void *context)
-{
- unsigned char signo = (unsigned char)s;
-
- /*
- * Only forward user-generated signals not sent by a process in
- * the command's own process group. Signals sent by the kernel
- * may include SIGTSTP when the user presses ^Z. Curses programs
- * often trap ^Z and send SIGTSTP to their own pgrp, so we don't
- * want to send an extra SIGTSTP.
- */
- if (!USER_SIGNALED(info))
- return;
- if (info->si_pid != 0) {
- pid_t si_pgrp = getpgid(info->si_pid);
- if (si_pgrp != -1) {
- if (si_pgrp == ppgrp || si_pgrp == cmnd_pid)
- return;
- } else if (info->si_pid == cmnd_pid) {
- return;
- }
- }
-
- /*
- * The pipe is non-blocking, if we overflow the kernel's pipe
- * buffer we drop the signal. This is not a problem in practice.
- */
- while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
- if (errno != EINTR)
- break;
- }
-}
-#endif /* SA_SIGINFO */
#define TERM_COOKED 0
#define TERM_RAW 1
-/* We keep a tailq of signals to forward to child. */
+/* We keep a tailq of signals to forward to the monitor. */
struct sigforward {
TAILQ_ENTRY(sigforward) entries;
int signo;
TAILQ_HEAD(sigfwd_list, sigforward);
struct exec_closure_pty {
- pid_t child;
- sigset_t *omask;
+ pid_t monitor_pid;
+ pid_t cmnd_pid;
+ pid_t ppgrp;
struct command_status *cstat;
struct command_details *details;
struct sudo_event_base *evbase;
- struct sudo_event *signal_event;
- struct sudo_event *sigfwd_event;
struct sudo_event *backchannel_event;
+ struct sudo_event *sigint_event;
+ struct sudo_event *sigquit_event;
+ struct sudo_event *sigtstp_event;
+ struct sudo_event *sigterm_event;
+ struct sudo_event *sighup_event;
+ struct sudo_event *sigalrm_event;
+ struct sudo_event *sigusr1_event;
+ struct sudo_event *sigusr2_event;
+ struct sudo_event *sigchld_event;
+ struct sudo_event *sigwinch_event;
+ struct sudo_event *sigfwd_event;
struct sigfwd_list sigfwd_list;
};
typedef bool (*sudo_io_action_t)(const char *, unsigned int, struct io_buffer *);
struct io_buffer {
SLIST_ENTRY(io_buffer) entries;
+ struct exec_closure_pty *ec;
struct sudo_event *revent;
struct sudo_event *wevent;
sudo_io_action_t action;
SLIST_HEAD(io_buffer_list, io_buffer);
static char slavename[PATH_MAX];
-int io_fds[6] = { -1, -1, -1, -1, -1, -1}; /* XXX - sudo_exec.h? */
+int io_fds[6] = { -1, -1, -1, -1, -1, -1};
static bool foreground, pipeline;
static bool tty_initialized;
static int ttymode = TERM_COOKED;
static struct io_buffer_list iobufs;
static const char *utmp_user;
-static int fork_pty(struct command_details *details, int sv[], sigset_t *omask);
static void del_io_events(bool nonblocking);
-static void sigwinch(int s);
static void sync_ttysize(int src, int dst);
static int safe_close(int fd);
static void ev_free_by_fd(struct sudo_event_base *evbase, int fd);
-static void check_foreground(void);
+static void check_foreground(pid_t ppgrp);
static void add_io_events(struct sudo_event_base *evbase);
/*
* the pty slave as needed.
*/
static void
-check_foreground(void)
+check_foreground(pid_t ppgrp)
{
debug_decl(check_foreground, SUDO_DEBUG_EXEC);
* foreground or SIGCONT_BG if it is a background process.
*/
static int
-suspend_sudo(int signo)
+suspend_sudo(int signo, pid_t ppgrp)
{
char signame[SIG2STR_MAX];
sigaction_t sa, osa;
* in the foreground. If not, we'll suspend sudo and resume later.
*/
if (!foreground)
- check_foreground();
+ check_foreground(ppgrp);
if (foreground) {
if (ttymode != TERM_RAW) {
if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
sudo_warn("killpg(%d, SIG%s)", (int)ppgrp, signame);
/* Check foreground/background status on resume. */
- check_foreground();
+ check_foreground(ppgrp);
/*
* We always resume the command in the foreground if sudo itself
default:
sudo_debug_printf(SUDO_DEBUG_INFO,
"read %d bytes from fd %d", n, fd);
- if (!iob->action(iob->buf + iob->len, n, iob))
- terminate_command(cmnd_pid, true);
+ if (!iob->action(iob->buf + iob->len, n, iob)) {
+ terminate_command(iob->ec->cmnd_pid, true);
+ iob->ec->cmnd_pid = -1;
+ }
iob->len += n;
/* Enable writer if not /dev/tty or we are foreground pgrp. */
if (iob->wevent != NULL &&
/* not an error */
break;
default:
-#if 0 /* XXX -- how to set cstat? stash in iobufs instead? */
- if (cstat != NULL) {
- cstat->type = CMD_ERRNO;
- cstat->val = errno;
- }
-#endif /* XXX */
+ /* XXX - need a way to distinguish non-exec error. */
+ iob->ec->cstat->type = CMD_ERRNO;
+ iob->ec->cstat->val = errno;
sudo_debug_printf(SUDO_DEBUG_ERROR,
"error writing fd %d: %s", fd, strerror(errno));
sudo_ev_loopbreak(evbase);
static void
io_buf_new(int rfd, int wfd,
bool (*action)(const char *, unsigned int, struct io_buffer *),
- struct io_buffer_list *head)
+ struct exec_closure_pty *ec, struct io_buffer_list *head)
{
int n;
struct io_buffer *iob;
/* Allocate and add to head of list. */
if ((iob = malloc(sizeof(*iob))) == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ iob->ec = ec;
iob->revent = sudo_ev_alloc(rfd, SUDO_EV_READ, read_callback, iob);
iob->wevent = sudo_ev_alloc(wfd, SUDO_EV_WRITE, write_callback, iob);
iob->len = 0;
debug_return;
}
-/*
- * Fork a monitor process which runs the actual command as its own child
- * process with std{in,out,err} hooked up to the pty or pipes as appropriate.
- * Returns the child pid.
- */
-static int
-fork_pty(struct command_details *details, int sv[], sigset_t *omask)
-{
- struct plugin_container *plugin;
- struct command_status cstat;
- int io_pipe[3][2] = { { -1, -1 }, { -1, -1 }, { -1, -1 } };
- bool interpose[3] = { false, false, false };
- sigaction_t sa;
- sigset_t mask;
- pid_t child;
- debug_decl(fork_pty, SUDO_DEBUG_EXEC);
-
- ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
-
- memset(&sa, 0, sizeof(sa));
- sigemptyset(&sa.sa_mask);
-
- if (io_fds[SFD_USERTTY] != -1) {
- sa.sa_flags = SA_RESTART;
- sa.sa_handler = sigwinch;
- if (sudo_sigaction(SIGWINCH, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGWINCH);
- }
-
- /* So we can block tty-generated signals */
- sigemptyset(&ttyblock);
- sigaddset(&ttyblock, SIGINT);
- sigaddset(&ttyblock, SIGQUIT);
- sigaddset(&ttyblock, SIGTSTP);
- sigaddset(&ttyblock, SIGTTIN);
- sigaddset(&ttyblock, SIGTTOU);
-
- /* Determine whether any of std{in,out,err} should be logged. */
- TAILQ_FOREACH(plugin, &io_plugins, entries) {
- if (plugin->u.io->log_stdin)
- interpose[STDIN_FILENO] = true;
- if (plugin->u.io->log_stdout)
- interpose[STDOUT_FILENO] = true;
- if (plugin->u.io->log_stderr)
- interpose[STDERR_FILENO] = true;
- }
-
- /*
- * Setup stdin/stdout/stderr for child, to be duped after forking.
- * In background mode there is no stdin.
- */
- if (!ISSET(details->flags, CD_BACKGROUND))
- io_fds[SFD_STDIN] = io_fds[SFD_SLAVE];
- io_fds[SFD_STDOUT] = io_fds[SFD_SLAVE];
- io_fds[SFD_STDERR] = io_fds[SFD_SLAVE];
-
- if (io_fds[SFD_USERTTY] != -1) {
- /* Read from /dev/tty, write to pty master */
- if (!ISSET(details->flags, CD_BACKGROUND)) {
- io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER],
- log_ttyin, &iobufs);
- }
-
- /* Read from pty master, write to /dev/tty */
- io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY],
- log_ttyout, &iobufs);
-
- /* Are we the foreground process? */
- foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
- }
-
- /*
- * If stdin, stdout or stderr is not a tty and logging is enabled,
- * use a pipe to interpose ourselves instead of using the pty fd.
- */
- if (io_fds[SFD_STDIN] == -1 || !isatty(STDIN_FILENO)) {
- if (!interpose[STDIN_FILENO]) {
- /* Not logging stdin, do not interpose. */
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "stdin not a tty, not logging");
- io_fds[SFD_STDIN] = dup(STDIN_FILENO);
- if (io_fds[SFD_STDIN] == -1)
- sudo_fatal("dup");
- } else {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "stdin not a tty, creating a pipe");
- pipeline = true;
- if (pipe(io_pipe[STDIN_FILENO]) != 0)
- sudo_fatal(U_("unable to create pipe"));
- io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1],
- log_stdin, &iobufs);
- io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0];
- }
- }
- if (io_fds[SFD_STDOUT] == -1 || !isatty(STDOUT_FILENO)) {
- if (!interpose[STDOUT_FILENO]) {
- /* Not logging stdout, do not interpose. */
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "stdout not a tty, not logging");
- io_fds[SFD_STDOUT] = dup(STDOUT_FILENO);
- if (io_fds[SFD_STDOUT] == -1)
- sudo_fatal("dup");
- } else {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "stdout not a tty, creating a pipe");
- pipeline = true;
- if (pipe(io_pipe[STDOUT_FILENO]) != 0)
- sudo_fatal(U_("unable to create pipe"));
- io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO,
- log_stdout, &iobufs);
- io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1];
- }
- }
- if (io_fds[SFD_STDERR] == -1 || !isatty(STDERR_FILENO)) {
- if (!interpose[STDERR_FILENO]) {
- /* Not logging stderr, do not interpose. */
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "stderr not a tty, not logging");
- io_fds[SFD_STDERR] = dup(STDERR_FILENO);
- if (io_fds[SFD_STDERR] == -1)
- sudo_fatal("dup");
- } else {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "stderr not a tty, creating a pipe");
- if (pipe(io_pipe[STDERR_FILENO]) != 0)
- sudo_fatal(U_("unable to create pipe"));
- io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO,
- log_stderr, &iobufs);
- io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1];
- }
- }
-
- if (foreground) {
- /* Copy terminal attrs from user tty -> pty slave. */
- if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
- tty_initialized = true;
- sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
- }
-
- /* Start out in raw mode unless part of a pipeline or backgrounded. */
- if (!pipeline && !ISSET(details->flags, CD_EXEC_BG)) {
- if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
- ttymode = TERM_RAW;
- }
- }
-
- /*
- * Block some signals until cmnd_pid is set in the parent to avoid a
- * race between exec of the command and receipt of a fatal signal from it.
- */
- sigemptyset(&mask);
- sigaddset(&mask, SIGTERM);
- sigaddset(&mask, SIGHUP);
- sigaddset(&mask, SIGINT);
- sigaddset(&mask, SIGQUIT);
- sigprocmask(SIG_BLOCK, &mask, omask);
-
- child = sudo_debug_fork();
- switch (child) {
- case -1:
- sudo_fatal(U_("unable to fork"));
- break;
- case 0:
- /* child */
- close(sv[0]);
- close(signal_pipe[0]);
- close(signal_pipe[1]);
- (void)fcntl(sv[1], F_SETFD, FD_CLOEXEC);
- sigprocmask(SIG_SETMASK, omask, NULL);
- /* Close the other end of the stdin/stdout/stderr pipes and exec. */
- if (io_pipe[STDIN_FILENO][1] != -1)
- close(io_pipe[STDIN_FILENO][1]);
- if (io_pipe[STDOUT_FILENO][0] != -1)
- close(io_pipe[STDOUT_FILENO][0]);
- if (io_pipe[STDERR_FILENO][0] != -1)
- close(io_pipe[STDERR_FILENO][0]);
- /*
- * If stdin/stdout is not a tty, start command in the background
- * since it might be part of a pipeline that reads from /dev/tty.
- * In this case, we rely on the command receiving SIGTTOU or SIGTTIN
- * when it needs access to the controlling tty.
- */
- exec_monitor(details, foreground && !pipeline, sv[1]);
- cstat.type = CMD_ERRNO;
- cstat.val = errno;
- while (send(sv[1], &cstat, sizeof(cstat), 0) == -1) {
- if (errno != EINTR)
- break;
- }
- _exit(1);
- }
-
- /* Close the other end of the stdin/stdout/stderr pipes. */
- if (io_pipe[STDIN_FILENO][0] != -1)
- close(io_pipe[STDIN_FILENO][0]);
- if (io_pipe[STDOUT_FILENO][1] != -1)
- close(io_pipe[STDOUT_FILENO][1]);
- if (io_pipe[STDERR_FILENO][1] != -1)
- close(io_pipe[STDERR_FILENO][1]);
-
- debug_return_int(child);
-}
-
static void
pty_close(struct command_status *cstat)
{
strlcpy(signame, "CONT_BG", sizeof(signame));
else if (sig2str(signo, signame) == -1)
snprintf(signame, sizeof(signame), "%d", signo);
- sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for child", signame);
+ sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for command", signame);
if ((sigfwd = calloc(1, sizeof(*sigfwd))) == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
backchannel_cb(int fd, int what, void *v)
{
struct exec_closure_pty *ec = v;
- ssize_t n;
+ struct command_status cstat;
+ ssize_t nread;
debug_decl(backchannel_cb, SUDO_DEBUG_EXEC)
- /* read child status */
- n = recv(fd, ec->cstat, sizeof(struct command_status), MSG_WAITALL);
- if (n != sizeof(struct command_status)) {
- if (n == -1) {
+ /*
+ * Read command status from the monitor.
+ * Note that the backchannel is a *blocking* socket.
+ */
+ for (;;) {
+ nread = recv(fd, &cstat, sizeof(cstat), MSG_WAITALL);
+ switch (nread) {
+ case -1:
switch (errno) {
case EINTR:
- /* got a signal, restart loop to service it. */
- sudo_ev_loopcontinue(ec->evbase);
- break;
+ /* Should not happen now that we use SA_RESTART. */
+ continue;
case EAGAIN:
- /* not ready after all... */
+ /* Nothing ready. */
break;
default:
+ if (ec->cstat->val != CMD_WSTATUS) {
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: failed to read command status: %s",
+ __func__, strerror(errno));
+ sudo_ev_loopbreak(ec->evbase);
+ }
+ break;
+ }
+ break;
+ case 0:
+ /* EOF, monitor exited or was killed. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "EOF on backchannel, monitor dead?");
+ if (ec->cstat->type == CMD_INVALID) {
+ /* XXX - need new CMD_ type for monitor errors. */
ec->cstat->type = CMD_ERRNO;
- ec->cstat->val = errno;
- sudo_debug_printf(SUDO_DEBUG_ERROR,
- "failed to read child status: %s", strerror(errno));
+ ec->cstat->val = ECONNRESET;
+ }
+ sudo_ev_loopexit(ec->evbase);
+ break;
+ case sizeof(cstat):
+ /* Check command status. */
+ switch (cstat.type) {
+ case CMD_PID:
+ ec->cmnd_pid = ec->cstat->val;
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
+ ec->details->command, (int)ec->cmnd_pid);
+ break;
+ case CMD_WSTATUS:
+ if (WIFSTOPPED(cstat.val)) {
+ int signo;
+
+ /* Suspend parent and tell monitor how to resume on return. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "command stopped, suspending parent");
+ signo = suspend_sudo(WSTOPSIG(cstat.val), ec->ppgrp);
+ schedule_signal(ec, signo);
+ /* Re-enable I/O events and restart event loop to service signal. */
+ add_io_events(ec->evbase);
+ sudo_ev_loopcontinue(ec->evbase);
+ } else {
+ /* Command exited or was killed, either way we are done. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "command exited or was killed");
+ sudo_ev_loopexit(ec->evbase);
+ }
+ *ec->cstat = cstat;
+ break;
+ case CMD_ERRNO:
+ /* Monitor was unable to execute command or broken pipe. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "errno from monitor: %s",
+ strerror(cstat.val));
sudo_ev_loopbreak(ec->evbase);
+ *ec->cstat = cstat;
break;
}
- } else {
- /* Short read or EOF. */
- sudo_debug_printf(SUDO_DEBUG_ERROR,
- "failed to read child status: %s", n ? "short read" : "EOF");
- /* XXX - need new CMD_ type for monitor errors. */
- errno = n ? EIO : ECONNRESET;
- ec->cstat->type = CMD_ERRNO;
- ec->cstat->val = errno;
- sudo_ev_loopbreak(ec->evbase);
+ /* Keep reading command status messages until EAGAIN or EOF. */
+ break;
+ default:
+ /* Short read, should not happen. */
+ if (ec->cstat->val != CMD_WSTATUS) {
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = EIO;
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "%s: failed to read command status: short read", __func__);
+ sudo_ev_loopbreak(ec->evbase);
+ }
+ break;
}
debug_return;
}
- switch (ec->cstat->type) {
- case CMD_PID:
- /*
- * Once we know the command's pid we can unblock
- * signals which ere blocked in fork_pty(). This
- * avoids a race between exec of the command and
- * receipt of a fatal signal from it.
- */
- cmnd_pid = ec->cstat->val;
- sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
- ec->details->command, (int)cmnd_pid);
- sigprocmask(SIG_SETMASK, ec->omask, NULL);
- break;
- case CMD_WSTATUS:
- if (WIFSTOPPED(ec->cstat->val)) {
- /* Suspend parent and tell child how to resume on return. */
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "child stopped, suspending parent");
- n = suspend_sudo(WSTOPSIG(ec->cstat->val));
- schedule_signal(ec, n);
- /* Re-enable I/O events and restart event loop to service signal. */
- add_io_events(ec->evbase);
- sudo_ev_loopcontinue(ec->evbase);
- } else {
- /* Child exited or was killed, either way we are done. */
- sudo_debug_printf(SUDO_DEBUG_INFO, "child exited or was killed");
- sudo_ev_loopexit(ec->evbase);
- }
- break;
- case CMD_ERRNO:
- /* Child was unable to execute command or broken pipe. */
- sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s",
- strerror(ec->cstat->val));
- sudo_ev_loopbreak(ec->evbase);
- break;
- }
- debug_return;
}
/*
* Monitor process was signaled; wait for it as needed.
*/
do {
- pid = waitpid(ec->child, &status, WUNTRACED|WNOHANG);
+ pid = waitpid(ec->monitor_pid, &status, WUNTRACED|WNOHANG);
} while (pid == -1 && errno == EINTR);
- if (pid == ec->child) {
- /*
- * If the monitor dies we get notified via backchannel_cb().
- * If it was stopped, we should stop too (the command keeps
- * running in its pty) and continue it when we come back.
- */
- if (WIFSTOPPED(status)) {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "monitor stopped, suspending sudo");
- n = suspend_sudo(WSTOPSIG(status));
- kill(pid, SIGCONT);
- schedule_signal(ec, n);
- /* Re-enable I/O events and restart event loop. */
- add_io_events(ec->evbase);
- sudo_ev_loopcontinue(ec->evbase);
- } else if (WIFSIGNALED(status)) {
- char signame[SIG2STR_MAX];
- if (sig2str(WTERMSIG(status), signame) == -1)
- snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
- sudo_debug_printf(SUDO_DEBUG_INFO, "%s: monitor (%d) killed, SIG%s",
- __func__, (int)ec->child, signame);
- ec->child = -1;
- } else {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "%s: monitor exited, status %d", __func__, WEXITSTATUS(status));
- ec->child = -1;
- }
+ switch (pid) {
+ case 0:
+ errno = ECHILD;
+ /* FALLTHROUGH */
+ case -1:
+ sudo_warn(U_("%s: %s"), __func__, "waitpid");
+ debug_return;
+ }
+
+ /*
+ * If the monitor dies we get notified via backchannel_cb().
+ * If it was stopped, we should stop too (the command keeps
+ * running in its pty) and continue it when we come back.
+ */
+ if (WIFSTOPPED(status)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "monitor stopped, suspending sudo");
+ n = suspend_sudo(WSTOPSIG(status), ec->ppgrp);
+ kill(pid, SIGCONT);
+ schedule_signal(ec, n);
+ /* Re-enable I/O events and restart event loop. */
+ add_io_events(ec->evbase);
+ sudo_ev_loopcontinue(ec->evbase);
+ } else if (WIFSIGNALED(status)) {
+ char signame[SIG2STR_MAX];
+ if (sig2str(WTERMSIG(status), signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: monitor (%d) killed, SIG%s",
+ __func__, (int)ec->monitor_pid, signame);
+ ec->monitor_pid = -1;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: monitor exited, status %d", __func__, WEXITSTATUS(status));
+ ec->monitor_pid = -1;
}
debug_return;
}
-/* Signal pipe callback */
+/* Signal callback */
static void
-signal_pipe_cb(int fd, int what, void *v)
+signal_cb_pty(int signo, int what, void *v)
{
- struct exec_closure_pty *ec = v;
+ struct sudo_ev_siginfo_container *sc = v;
+ struct exec_closure_pty *ec = sc->closure;
char signame[SIG2STR_MAX];
- unsigned char signo;
- ssize_t nread;
- debug_decl(signal_pipe_cb, SUDO_DEBUG_EXEC)
+ debug_decl(signal_cb_pty, SUDO_DEBUG_EXEC)
- /* Process received signals until the child dies or the pipe is empty. */
- do {
- /* read signal pipe */
- nread = read(fd, &signo, sizeof(signo));
- if (nread <= 0) {
- /* It should not be possible to get EOF but just in case... */
- if (nread == 0)
- errno = ECONNRESET;
- /* Restart if interrupted by signal so the pipe doesn't fill. */
- if (errno == EINTR)
- continue;
- /* On error, store errno and break out of the event loop. */
- if (errno != EAGAIN) {
- ec->cstat->type = CMD_ERRNO;
- ec->cstat->val = errno;
- sudo_warn(U_("error reading from signal pipe"));
- sudo_ev_loopbreak(ec->evbase);
+ if (ec->monitor_pid == -1)
+ debug_return;
+
+ if (sig2str(signo, signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_DIAG,
+ "%s: evbase %p, monitor: %d, signo %s(%d), cstat %p", __func__,
+ ec->evbase, (int)ec->monitor_pid, signame, signo, ec->cstat);
+
+ switch (signo) {
+ case SIGCHLD:
+ handle_sigchld_pty(ec);
+ break;
+ case SIGWINCH:
+ sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+ break;
+ default:
+ /*
+ * Do not forward signals sent by a process in the command's process
+ * group, as we don't want the command to indirectly kill itself.
+ * For example, this can happen with some versions of reboot that
+ * call kill(-1, SIGTERM) to kill all other processes.
+ */
+ if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == ec->ppgrp || si_pgrp == ec->cmnd_pid)
+ debug_return;
+ } else if (sc->siginfo->si_pid == ec->cmnd_pid) {
+ debug_return;
}
- break;
}
- if (sig2str(signo, signame) == -1)
- snprintf(signame, sizeof(signame), "%d", signo);
- sudo_debug_printf(SUDO_DEBUG_DIAG,
- "%s: evbase %p, child: %d, signo %s(%d), cstat %p",
- __func__, ec->evbase, (int)ec->child, signame, signo, ec->cstat);
-
- if (signo == SIGCHLD) {
- handle_sigchld_pty(ec);
- } else if (ec->child != -1) {
- /* Schedule signo to be forwared to the child. */
- schedule_signal(ec, signo);
- /* Restart event loop to service signal immediately. */
- sudo_ev_loopcontinue(ec->evbase);
- }
- } while (ec->child != -1);
+ /* Schedule signo to be forwared to the command. */
+ schedule_signal(ec, signo);
+ /* Restart event loop to service signal immediately. */
+ sudo_ev_loopcontinue(ec->evbase);
+ break;
+ }
+
debug_return;
}
else if (sig2str(sigfwd->signo, signame) == -1)
snprintf(signame, sizeof(signame), "%d", sigfwd->signo);
sudo_debug_printf(SUDO_DEBUG_INFO,
- "sending SIG%s to child over backchannel", signame);
+ "sending SIG%s to monitor over backchannel", signame);
cstat.type = CMD_SIGNO;
cstat.val = sigfwd->signo;
do {
if (nsent != sizeof(cstat)) {
if (errno == EPIPE) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
- "broken pipe writing to child over backchannel");
+ "broken pipe writing to monitor over backchannel");
/* Other end of socket gone, empty out sigfwd_list. */
while ((sigfwd = TAILQ_FIRST(&ec->sigfwd_list)) != NULL) {
TAILQ_REMOVE(&ec->sigfwd_list, sigfwd, entries);
free(sigfwd);
}
- /* XXX - child (monitor) is dead, we should exit too? */
+ /* XXX - need new CMD_ type for monitor errors. */
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ sudo_ev_loopbreak(ec->evbase);
}
break;
}
*/
static void
fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat,
- struct command_details *details, pid_t child, sigset_t *omask,
- int backchannel)
+ struct command_details *details, pid_t ppgrp, int backchannel)
{
debug_decl(fill_exec_closure_pty, SUDO_DEBUG_EXEC)
/* Fill in the non-event part of the closure. */
- ec->child = child;
- ec->omask = omask;
+ ec->cmnd_pid = -1;
+ ec->ppgrp = ppgrp;
ec->cstat = cstat;
ec->details = details;
TAILQ_INIT(&ec->sigfwd_list);
if (ec->evbase == NULL)
sudo_fatal(NULL);
- /* Event for local signals via signal_pipe. */
- ec->signal_event = sudo_ev_alloc(signal_pipe[0],
- SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, ec);
- if (ec->signal_event == NULL)
- sudo_fatal(NULL);
- if (sudo_ev_add(ec->evbase, ec->signal_event, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
-
/* Event for command status via backchannel. */
ec->backchannel_event = sudo_ev_alloc(backchannel,
SUDO_EV_READ|SUDO_EV_PERSIST, backchannel_cb, ec);
sudo_fatal(NULL);
if (sudo_ev_add(ec->evbase, ec->backchannel_event, NULL, false) == -1)
sudo_fatal(U_("unable to add event to queue"));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "backchannel fd %d\n", backchannel);
+
+ /* Events for local signals. */
+ ec->sigint_event = sudo_ev_alloc(SIGINT,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigint_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigint_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigquit_event = sudo_ev_alloc(SIGQUIT,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigquit_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigquit_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigtstp_event = sudo_ev_alloc(SIGTSTP,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigtstp_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigtstp_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigterm_event = sudo_ev_alloc(SIGTERM,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigterm_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigterm_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sighup_event = sudo_ev_alloc(SIGHUP,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sighup_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sighup_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigalrm_event = sudo_ev_alloc(SIGALRM,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigalrm_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigalrm_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigusr1_event = sudo_ev_alloc(SIGUSR1,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigusr1_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigusr1_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigusr2_event = sudo_ev_alloc(SIGUSR2,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigusr2_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigusr2_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigchld_event = sudo_ev_alloc(SIGCHLD,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigchld_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigchld_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigwinch_event = sudo_ev_alloc(SIGWINCH,
+ SUDO_EV_SIGINFO, signal_cb_pty, ec);
+ if (ec->sigwinch_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->sigwinch_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
/* The signal forwarding event gets added on demand. */
ec->sigfwd_event = sudo_ev_alloc(backchannel,
SUDO_EV_WRITE, sigfwd_cb, ec);
if (ec->sigfwd_event == NULL)
sudo_fatal(NULL);
+}
- sudo_debug_printf(SUDO_DEBUG_INFO, "signal pipe fd %d\n", signal_pipe[0]);
- sudo_debug_printf(SUDO_DEBUG_INFO, "backchannel fd %d\n", backchannel);
+/*
+ * Free the dynamically-allocated contents of the exec closure.
+ */
+static void
+free_exec_closure_pty(struct exec_closure_pty *ec)
+{
+ debug_decl(free_exec_closure_pty, SUDO_DEBUG_EXEC)
+
+ sudo_ev_base_free(ec->evbase);
+ sudo_ev_free(ec->backchannel_event);
+ sudo_ev_free(ec->sigint_event);
+ sudo_ev_free(ec->sigquit_event);
+ sudo_ev_free(ec->sigtstp_event);
+ sudo_ev_free(ec->sigterm_event);
+ sudo_ev_free(ec->sighup_event);
+ sudo_ev_free(ec->sigalrm_event);
+ sudo_ev_free(ec->sigusr1_event);
+ sudo_ev_free(ec->sigusr2_event);
+ sudo_ev_free(ec->sigchld_event);
+ sudo_ev_free(ec->sigwinch_event);
+ sudo_ev_free(ec->sigfwd_event);
+
+ debug_return;
}
/*
int
exec_pty(struct command_details *details, struct command_status *cstat)
{
+ int io_pipe[3][2] = { { -1, -1 }, { -1, -1 }, { -1, -1 } };
+ bool interpose[3] = { false, false, false };
struct sigforward *sigfwd, *sigfwd_next;
- struct exec_closure_pty ec;
+ struct exec_closure_pty ec = { 0 };
+ struct plugin_container *plugin;
+ sigset_t set, oset;
sigaction_t sa;
- sigset_t omask;
- pid_t child;
+ pid_t ppgrp;
int sv[2];
debug_decl(exec_pty, SUDO_DEBUG_EXEC)
pty_setup(details->euid, user_details.tty);
/*
- * We communicate with the child over a bi-directional pair of sockets.
- * Parent sends signal info to child and child sends back wait status.
+ * We communicate with the monitor over a bi-directional pair of sockets.
+ * Parent sends signal info to monitor and monitor sends back wait status.
*/
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1)
sudo_fatal(U_("unable to create sockets"));
/*
- * Signals to forward to the child process (excluding SIGALRM).
- * We block all other signals while running the signal handler.
- * Note: HP-UX select() will not be interrupted if SA_RESTART set.
+ * We don't want to receive SIGTTIN/SIGTTOU.
+ * XXX - this affects tcsetattr() and tcsetpgrp() too.
*/
memset(&sa, 0, sizeof(sa));
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
-#ifdef SA_SIGINFO
- sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = exec_handler;
-#else
- sa.sa_handler = exec_handler;
-#endif
- if (sudo_sigaction(SIGTERM, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGTERM);
- if (sudo_sigaction(SIGHUP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGHUP);
- if (sudo_sigaction(SIGALRM, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGALRM);
- if (sudo_sigaction(SIGPIPE, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGPIPE);
- if (sudo_sigaction(SIGUSR1, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGUSR1);
- if (sudo_sigaction(SIGUSR2, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGUSR2);
- if (sudo_sigaction(SIGCHLD, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGCHLD);
-#ifdef SIGINFO
- if (sudo_sigaction(SIGINFO, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGINFO);
-#endif
-
- /*
- * Unlike the non-pty case, we can use our normal signal handler
- * for tty-generated signals triggered by the user.
- */
- if (sudo_sigaction(SIGINT, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGINT);
- if (sudo_sigaction(SIGQUIT, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGQUIT);
- if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
-
- /*
- * We don't want to receive SIGTTIN/SIGTTOU, getting EIO is preferable.
- */
+ sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN;
if (sudo_sigaction(SIGTTIN, &sa, NULL) != 0)
/*
* Child will run the command in the pty, parent will pass data
* to and from pty.
- * XXX - inline fork_pty or refactor differently?
*/
- child = fork_pty(details, sv, &omask);
+
+ /* So we can block tty-generated signals */
+ sigemptyset(&ttyblock);
+ sigaddset(&ttyblock, SIGINT);
+ sigaddset(&ttyblock, SIGQUIT);
+ sigaddset(&ttyblock, SIGTSTP);
+ sigaddset(&ttyblock, SIGTTIN);
+ sigaddset(&ttyblock, SIGTTOU);
+
+ ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
+
+ /* Determine whether any of std{in,out,err} should be logged. */
+ TAILQ_FOREACH(plugin, &io_plugins, entries) {
+ if (plugin->u.io->log_stdin)
+ interpose[STDIN_FILENO] = true;
+ if (plugin->u.io->log_stdout)
+ interpose[STDOUT_FILENO] = true;
+ if (plugin->u.io->log_stderr)
+ interpose[STDERR_FILENO] = true;
+ }
+
+ /*
+ * Setup stdin/stdout/stderr for command, to be duped after forking.
+ * In background mode there is no stdin.
+ */
+ if (!ISSET(details->flags, CD_BACKGROUND))
+ io_fds[SFD_STDIN] = io_fds[SFD_SLAVE];
+ io_fds[SFD_STDOUT] = io_fds[SFD_SLAVE];
+ io_fds[SFD_STDERR] = io_fds[SFD_SLAVE];
+
+ if (io_fds[SFD_USERTTY] != -1) {
+ /* Read from /dev/tty, write to pty master */
+ if (!ISSET(details->flags, CD_BACKGROUND)) {
+ io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER],
+ log_ttyin, &ec, &iobufs);
+ }
+
+ /* Read from pty master, write to /dev/tty */
+ io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY],
+ log_ttyout, &ec, &iobufs);
+
+ /* Are we the foreground process? */
+ foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp;
+ }
+
+ /*
+ * If stdin, stdout or stderr is not a tty and logging is enabled,
+ * use a pipe to interpose ourselves instead of using the pty fd.
+ */
+ if (io_fds[SFD_STDIN] == -1 || !isatty(STDIN_FILENO)) {
+ if (!interpose[STDIN_FILENO]) {
+ /* Not logging stdin, do not interpose. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stdin not a tty, not logging");
+ io_fds[SFD_STDIN] = dup(STDIN_FILENO);
+ if (io_fds[SFD_STDIN] == -1)
+ sudo_fatal("dup");
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stdin not a tty, creating a pipe");
+ pipeline = true;
+ if (pipe(io_pipe[STDIN_FILENO]) != 0)
+ sudo_fatal(U_("unable to create pipe"));
+ io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1],
+ log_stdin, &ec, &iobufs);
+ io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0];
+ }
+ }
+ if (io_fds[SFD_STDOUT] == -1 || !isatty(STDOUT_FILENO)) {
+ if (!interpose[STDOUT_FILENO]) {
+ /* Not logging stdout, do not interpose. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stdout not a tty, not logging");
+ io_fds[SFD_STDOUT] = dup(STDOUT_FILENO);
+ if (io_fds[SFD_STDOUT] == -1)
+ sudo_fatal("dup");
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stdout not a tty, creating a pipe");
+ pipeline = true;
+ if (pipe(io_pipe[STDOUT_FILENO]) != 0)
+ sudo_fatal(U_("unable to create pipe"));
+ io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO,
+ log_stdout, &ec, &iobufs);
+ io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1];
+ }
+ }
+ if (io_fds[SFD_STDERR] == -1 || !isatty(STDERR_FILENO)) {
+ if (!interpose[STDERR_FILENO]) {
+ /* Not logging stderr, do not interpose. */
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stderr not a tty, not logging");
+ io_fds[SFD_STDERR] = dup(STDERR_FILENO);
+ if (io_fds[SFD_STDERR] == -1)
+ sudo_fatal("dup");
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "stderr not a tty, creating a pipe");
+ if (pipe(io_pipe[STDERR_FILENO]) != 0)
+ sudo_fatal(U_("unable to create pipe"));
+ io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO,
+ log_stderr, &ec, &iobufs);
+ io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1];
+ }
+ }
+
+ if (foreground) {
+ /* Copy terminal attrs from user tty -> pty slave. */
+ if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
+ tty_initialized = true;
+ sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
+ }
+
+ /* Start out in raw mode unless part of a pipeline or backgrounded. */
+ if (!pipeline && !ISSET(details->flags, CD_EXEC_BG)) {
+ if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
+ ttymode = TERM_RAW;
+ }
+ }
+
+ /*
+ * Block signals until we have our handlers setup in the parent so
+ * we don't miss SIGCHLD if the command exits immediately.
+ */
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oset);
+
+ /* Check for early termination or suspend signals before we fork. */
+ if (sudo_terminated(cstat)) {
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ debug_return_int(0);
+ }
+
+ ec.monitor_pid = sudo_debug_fork();
+ switch (ec.monitor_pid) {
+ case -1:
+ sudo_fatal(U_("unable to fork"));
+ break;
+ case 0:
+ /* child */
+ close(sv[0]);
+ (void)fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+ /* Close the other end of the stdin/stdout/stderr pipes and exec. */
+ if (io_pipe[STDIN_FILENO][1] != -1)
+ close(io_pipe[STDIN_FILENO][1]);
+ if (io_pipe[STDOUT_FILENO][0] != -1)
+ close(io_pipe[STDOUT_FILENO][0]);
+ if (io_pipe[STDERR_FILENO][0] != -1)
+ close(io_pipe[STDERR_FILENO][0]);
+ /*
+ * If stdin/stdout is not a tty, start command in the background
+ * since it might be part of a pipeline that reads from /dev/tty.
+ * In this case, we rely on the command receiving SIGTTOU or SIGTTIN
+ * when it needs access to the controlling tty.
+ */
+ exec_monitor(details, &oset, foreground && !pipeline, sv[1]);
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ while (send(sv[1], cstat, sizeof(*cstat), 0) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+ _exit(1);
+ }
+
+ /* Close the other end of the stdin/stdout/stderr pipes and socketpair. */
+ if (io_pipe[STDIN_FILENO][0] != -1)
+ close(io_pipe[STDIN_FILENO][0]);
+ if (io_pipe[STDOUT_FILENO][1] != -1)
+ close(io_pipe[STDOUT_FILENO][1]);
+ if (io_pipe[STDERR_FILENO][1] != -1)
+ close(io_pipe[STDERR_FILENO][1]);
close(sv[1]);
/* No longer need execfd. */
alarm(details->timeout);
/*
- * I/O logging must be in the C locale for floating point numbers
- * to be logged consistently.
+ * Fill in exec closure, allocate event base, signal events and
+ * the backchannel event.
*/
- setlocale(LC_ALL, "C");
+ fill_exec_closure_pty(&ec, cstat, details, ppgrp, sv[0]);
+
+ /* Restore signal mask now that signal handlers are setup. */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
/*
- * Allocate event base and two persistent events:
- * the signal pipe and the child process's backchannel.
+ * I/O logging must be in the C locale for floating point numbers
+ * to be logged consistently.
*/
- fill_exec_closure_pty(&ec, cstat, details, child, &omask, sv[0]);
+ setlocale(LC_ALL, "C");
/*
* In the event loop we pass input from user tty to master
if (sudo_ev_loop(ec.evbase, 0) == -1)
sudo_warn(U_("error in event loop"));
if (sudo_ev_got_break(ec.evbase)) {
- /* error from callback */
+ /* error from callback or monitor died */
sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
+ /* XXX - may need to terminate command if cmnd_pid != -1 */
}
- /* Flush any remaining output and free pty-related memory. */
+ /* Flush any remaining output, free I/O bufs and events, do logout. */
pty_close(cstat);
/* Free things up. */
- sudo_ev_base_free(ec.evbase);
- sudo_ev_free(ec.sigfwd_event);
- sudo_ev_free(ec.signal_event);
- sudo_ev_free(ec.backchannel_event);
+ free_exec_closure_pty(&ec);
TAILQ_FOREACH_SAFE(sigfwd, &ec.sigfwd_list, entries, sigfwd_next) {
free(sigfwd);
}
static void
sync_ttysize(int src, int dst)
{
-#ifdef TIOCGWINSZ
struct winsize wsize;
pid_t pgrp;
debug_decl(sync_ttysize, SUDO_DEBUG_EXEC);
}
debug_return;
-#endif
-}
-
-/*
- * Handler for SIGWINCH in parent.
- */
-static void
-sigwinch(int s)
-{
- int serrno = errno;
-
- sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]);
- errno = serrno;
}
/*
#include "sudo.h"
#include "sudo_exec.h"
-int signal_pipe[2];
-
static struct signal_state {
int signo;
int restore;
{ -1 }
};
+static sig_atomic_t pending_signals[NSIG];
+
+static void
+sudo_handler(int signo)
+{
+ /* Mark signal as pending. */
+ pending_signals[signo] = 1;
+}
+
+bool
+signal_pending(int signo)
+{
+ return pending_signals[signo] == 1;
+}
+
/*
* Save signal handler state so it can be restored before exec.
*/
debug_return;
}
-static void
-sudo_handler(int s)
-{
- unsigned char signo = (unsigned char)s;
-
- /*
- * The pipe is non-blocking, if we overflow the kernel's pipe
- * buffer we drop the signal. This is not a problem in practice.
- */
- while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
- if (errno != EINTR)
- break;
- }
-}
-
/*
* Trap tty-generated (and other) signals so we can't be killed before
* calling the policy close function. The signal pipe will be drained
struct signal_state *ss;
debug_decl(init_signals, SUDO_DEBUG_MAIN)
- /*
- * We use a pipe to atomically handle signal notification within
- * the select() loop without races (we may not have pselect()).
- */
- if (pipe2(signal_pipe, O_NONBLOCK) != 0)
- sudo_fatal(U_("unable to create pipe"));
-
memset(&sa, 0, sizeof(sa));
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
/* signal.c */
struct sigaction;
-extern int signal_pipe[2];
int sudo_sigaction(int signo, struct sigaction *sa, struct sigaction *osa);
void init_signals(void);
void restore_signals(void);
void save_signals(void);
+bool signal_pending(int signo);
/* preload.c */
void preload_static_symbols(void);
/*
* Some older systems support siginfo but predate SI_USER.
*/
-#ifdef SA_SIGINFO
-# ifdef SI_USER
-# define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code == SI_USER)
-# else
-# define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code <= 0)
-# endif
+#ifdef SI_USER
+# define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code == SI_USER)
+#else
+# define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code <= 0)
#endif
/*
struct command_status;
/* exec.c */
-extern volatile pid_t cmnd_pid, ppgrp;
void exec_cmnd(struct command_details *details, int errfd);
void terminate_command(pid_t pid, bool use_pgrp);
-#ifdef SA_SIGINFO
-void exec_handler(int s, siginfo_t *info, void *context);
-#else
-void exec_handler(int s);
-#endif
+bool sudo_terminated(struct command_status *cstat);
/* exec_common.c */
int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec);
int exec_pty(struct command_details *details, struct command_status *cstat);
void pty_cleanup(void);
int pty_make_controlling(void);
+extern int io_fds[6];
/* exec_monitor.c */
-int exec_monitor(struct command_details *details, bool foreground, int backchannel);
+int exec_monitor(struct command_details *details, sigset_t *omask, bool foreground, int backchannel);
/* utmp.c */
bool utmp_login(const char *from_line, const char *to_line, int ttyfd,