]> granicus.if.org Git - sudo/commitdiff
Use SUDO_EV_SIGNAL and SUDO_EV_SIGINFO instead of managing the
authorTodd C. Miller <Todd.Miller@courtesan.com>
Fri, 12 May 2017 16:02:17 +0000 (10:02 -0600)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Fri, 12 May 2017 16:02:17 +0000 (10:02 -0600)
signal_pipe explicitly.

src/exec.c
src/exec_monitor.c
src/exec_nopty.c
src/exec_pty.c
src/signal.c
src/sudo.h
src/sudo_exec.h

index e7dcf539a11119679fce23f8c7e435fc0c588224..23fdbcbe78ff9fa599c53ec0bb6c6cc5f6d284c3 100644 (file)
 #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
@@ -138,60 +83,53 @@ exec_cmnd(struct command_details *details, int errfd)
 }
 
 /*
- * 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);
 }
 
 /*
@@ -205,11 +143,6 @@ sudo_execute(struct command_details *details, struct command_status *cstat)
 {
     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()) {
@@ -246,9 +179,11 @@ sudo_execute(struct command_details *details, struct command_status *cstat)
         * 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
index b0cb389a71d14d0c35ec426e07b3c2f9af585da7..3ae901cfbdb7dee40dcc56d21a6ba162cfc4a72a 100644 (file)
 #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
@@ -114,14 +67,14 @@ mon_handler(int s)
  * 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)
@@ -136,28 +89,28 @@ deliver_signal(pid_t pid, int signo, bool from_parent)
        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;
@@ -195,7 +148,7 @@ send_status(int fd, struct command_status *cstat)
  * 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;
@@ -204,7 +157,7 @@ mon_handle_sigchld(int backchannel, struct command_status *cstat)
 
     /* 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:
@@ -217,43 +170,43 @@ mon_handle_sigchld(int backchannel, struct command_status *cstat)
 
     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);
        }
     }
 
@@ -261,37 +214,39 @@ mon_handle_sigchld(int backchannel, struct command_status *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;
 }
@@ -352,7 +307,10 @@ mon_backchannel_cb(int fd, int what, void *v)
     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) {
@@ -362,10 +320,13 @@ mon_backchannel_cb(int fd, int what, void *v)
        } 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);
        }
@@ -437,24 +398,15 @@ fill_exec_closure_monitor(struct monitor_closure *mc,
     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);
@@ -470,6 +422,63 @@ fill_exec_closure_monitor(struct monitor_closure *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"));
 }
 
 /*
@@ -477,15 +486,17 @@ fill_exec_closure_monitor(struct monitor_closure *mc,
  * 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. */
@@ -494,67 +505,16 @@ exec_monitor(struct command_details *details, bool foreground, int backchannel)
     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.
@@ -569,21 +529,21 @@ exec_monitor(struct command_details *details, bool foreground, int backchannel)
        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();
 
@@ -605,11 +565,17 @@ exec_monitor(struct command_details *details, bool foreground, int backchannel)
 
     /* 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])
@@ -620,38 +586,40 @@ exec_monitor(struct command_details *details, bool foreground, int backchannel)
        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)) {
@@ -663,5 +631,5 @@ exec_monitor(struct command_details *details, bool foreground, int backchannel)
     _exit(1);
 
 bad:
-    debug_return_int(errno);
+    debug_return_int(-1);
 }
index dfb0f4d2336c23ec2a94a51c7a8b89bbb24d3df0..4daa4a4d60583e4b7f40893a26368e8a47efe27d 100644 (file)
 #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
@@ -99,6 +108,84 @@ errpipe_cb(int fd, int what, void *v)
     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.
@@ -110,7 +197,7 @@ fill_exec_closure_nopty(struct exec_closure_nopty *ec,
     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;
 
@@ -119,14 +206,6 @@ fill_exec_closure_nopty(struct exec_closure_nopty *ec,
     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);
@@ -134,10 +213,121 @@ fill_exec_closure_nopty(struct exec_closure_nopty *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;
 }
 
@@ -147,101 +337,46 @@ fill_exec_closure_nopty(struct exec_closure_nopty *ec,
 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)
@@ -251,7 +386,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
        _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. */
@@ -265,11 +400,14 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
        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.
@@ -280,7 +418,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
        /* 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
@@ -291,9 +429,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
 #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);
 }
 
@@ -313,7 +449,7 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
 
     /* 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:
@@ -329,7 +465,7 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
         * 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.
         */
@@ -340,7 +476,7 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
        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) {
@@ -352,18 +488,18 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
        }
        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;
@@ -398,125 +534,28 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
             * 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 */
index 7e30b66e2c532e3c75c0108021c1b2cfd982d331..3958b7d593e4c48cb4012cd4c5bfe34feae39c25 100644 (file)
@@ -45,7 +45,7 @@
 #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;
@@ -53,14 +53,24 @@ struct sigforward {
 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;
 };
 
@@ -73,6 +83,7 @@ struct io_buffer;
 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;
@@ -83,7 +94,7 @@ struct io_buffer {
 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;
@@ -91,13 +102,11 @@ static sigset_t ttyblock;
 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);
 
 /*
@@ -357,7 +366,7 @@ log_stderr(const char *buf, unsigned int n, struct io_buffer *iob)
  * the pty slave as needed.
  */
 static void
-check_foreground(void)
+check_foreground(pid_t ppgrp)
 {
     debug_decl(check_foreground, SUDO_DEBUG_EXEC);
 
@@ -380,7 +389,7 @@ check_foreground(void)
  * 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;
@@ -395,7 +404,7 @@ suspend_sudo(int signo)
         * 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))
@@ -431,7 +440,7 @@ suspend_sudo(int signo)
            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
@@ -506,8 +515,10 @@ read_callback(int fd, int what, void *v)
        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 &&
@@ -561,12 +572,9 @@ write_callback(int fd, int what, void *v)
            /* 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);
@@ -604,7 +612,7 @@ write_callback(int fd, int what, void *v)
 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;
@@ -621,6 +629,7 @@ io_buf_new(int rfd, int wfd,
     /* 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;
@@ -634,209 +643,6 @@ io_buf_new(int rfd, int wfd,
     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)
 {
@@ -890,7 +696,7 @@ schedule_signal(struct exec_closure_pty *ec, int signo)
        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"));
@@ -907,78 +713,98 @@ static void
 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;
 }
 
 /*
@@ -995,84 +821,94 @@ handle_sigchld_pty(struct exec_closure_pty *ec)
      * 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;
 }
 
@@ -1098,7 +934,7 @@ sigfwd_cb(int sock, int what, void *v)
        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 {
@@ -1109,13 +945,16 @@ sigfwd_cb(int sock, int what, void *v)
        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;
        }
@@ -1129,14 +968,13 @@ sigfwd_cb(int sock, int what, void *v)
  */
 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);
@@ -1146,14 +984,6 @@ fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat,
     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);
@@ -1161,15 +991,109 @@ fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat,
        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;
 }
 
 /*
@@ -1181,11 +1105,14 @@ fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat,
 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)
 
@@ -1198,59 +1125,18 @@ exec_pty(struct command_details *details, struct command_status *cstat)
     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)
@@ -1268,9 +1154,179 @@ exec_pty(struct command_details *details, struct command_status *cstat)
     /*
      * 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. */
@@ -1284,16 +1340,19 @@ exec_pty(struct command_details *details, struct command_status *cstat)
        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
@@ -1303,18 +1362,16 @@ exec_pty(struct command_details *details, struct command_status *cstat)
     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);
     }
@@ -1462,7 +1519,6 @@ del_io_events(bool nonblocking)
 static void
 sync_ttysize(int src, int dst)
 {
-#ifdef TIOCGWINSZ
     struct winsize wsize;
     pid_t pgrp;
     debug_decl(sync_ttysize, SUDO_DEBUG_EXEC);
@@ -1474,19 +1530,6 @@ sync_ttysize(int src, int dst)
     }
 
     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;
 }
 
 /*
index 53c402998dd056a52c6504ed5721794692510f90..7d18cf2bf9e6dad877837c038514fc42529d04c5 100644 (file)
@@ -33,8 +33,6 @@
 #include "sudo.h"
 #include "sudo_exec.h"
 
-int signal_pipe[2];
-
 static struct signal_state {
     int signo;
     int restore;
@@ -56,6 +54,21 @@ static struct signal_state {
     { -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.
  */
@@ -94,21 +107,6 @@ restore_signals(void)
     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
@@ -122,13 +120,6 @@ init_signals(void)
     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;
index cac16762d435f36827eebef33d3017e308337d6e..9fe00d23d3a37374d45b3f04fbed295a33c07d2c 100644 (file)
@@ -256,11 +256,11 @@ char *get_process_ttyname(char *name, size_t namelen);
 
 /* 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);
index 302bd42c155c7680ea4df51e6f14a9d3fe47b1ba..1e9cfc72a36a0c40d9bed27858c4417971bc5917 100644 (file)
 /*
  * 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
 
 /*
@@ -85,14 +83,9 @@ struct command_details;
 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);
@@ -105,9 +98,10 @@ int exec_nopty(struct command_details *details, struct command_status *cstat);
 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,