]> granicus.if.org Git - sudo/commitdiff
If we receive a signal from the command we executed, do not forward
authorTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 6 Aug 2012 18:38:35 +0000 (14:38 -0400)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 6 Aug 2012 18:38:35 +0000 (14:38 -0400)
it back to the command.  This fixes a problem with BSD-derived
versions of the reboot command which send SIGTERM to all other
processes, including the sudo process.  Sudo would then deliver
SIGTERM to reboot which would die before calling the reboot() system
call, effectively leaving the system in single user mode.

src/exec.c
src/exec_pty.c
src/sudo.h
src/sudo_exec.h

index d93549813f03f1da859f0f3eb639b0baea8662db..a9fcf8393e698fdf83d81f375e80ecd746f7f6fb 100644 (file)
@@ -74,12 +74,14 @@ struct sigforward {
 TQ_DECLARE(sigforward)
 static struct sigforward_list sigfwd_list;
 
+volatile pid_t cmnd_pid = -1;
+
 static int handle_signals(int sv[2], pid_t child, int log_io,
     struct command_status *cstat);
 static void forward_signals(int fd);
 static void schedule_signal(int signo);
 #ifdef SA_SIGINFO
-static void handler_nofwd(int s, siginfo_t *info, void *context);
+static void handler_user_only(int s, siginfo_t *info, void *context);
 #endif
 
 /*
@@ -90,13 +92,17 @@ static int fork_cmnd(struct command_details *details, int sv[2])
 {
     struct command_status cstat;
     sigaction_t sa;
-    pid_t child;
     debug_decl(fork_cmnd, SUDO_DEBUG_EXEC)
 
     zero_bytes(&sa, sizeof(sa));
     sigemptyset(&sa.sa_mask);
     sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
+#ifdef SA_SIGINFO
+    sa.sa_flags |= SA_SIGINFO;
+    sa.sa_sigaction = handler;
+#else
     sa.sa_handler = handler;
+#endif
     sigaction(SIGCONT, &sa, NULL);
 
     /*
@@ -106,8 +112,8 @@ static int fork_cmnd(struct command_details *details, int sv[2])
     if (policy_init_session(details) != true)
        errorx(1, _("policy plugin failed session initialization"));
 
-    child = sudo_debug_fork();
-    switch (child) {
+    cmnd_pid = sudo_debug_fork();
+    switch (cmnd_pid) {
     case -1:
        error(1, _("unable to fork"));
        break;
@@ -150,7 +156,9 @@ static int fork_cmnd(struct command_details *details, int sv[2])
        sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
        _exit(1);
     }
-    debug_return_int(child);
+    sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
+       cmnd_pid);
+    debug_return_int(cmnd_pid);
 }
 
 static struct signal_state {
@@ -216,6 +224,7 @@ sudo_execute(struct command_details *details, struct command_status *cstat)
     bool log_io = false;
     fd_set *fdsr, *fdsw;
     sigaction_t sa;
+    sigset_t omask;
     pid_t child;
     debug_decl(sudo_execute, SUDO_DEBUG_EXEC)
 
@@ -273,7 +282,12 @@ sudo_execute(struct command_details *details, struct command_status *cstat)
      * Note: HP-UX select() will not be interrupted if SA_RESTART set.
      */
     sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
+#ifdef SA_SIGINFO
+    sa.sa_flags |= SA_SIGINFO;
+    sa.sa_sigaction = handler;
+#else
     sa.sa_handler = handler;
+#endif
     sigaction(SIGALRM, &sa, NULL);
     sigaction(SIGCHLD, &sa, NULL);
     sigaction(SIGPIPE, &sa, NULL);
@@ -291,7 +305,7 @@ sudo_execute(struct command_details *details, struct command_status *cstat)
 #ifdef SA_SIGINFO
     if (!log_io) {
        sa.sa_flags |= SA_SIGINFO;
-       sa.sa_sigaction = handler_nofwd;
+       sa.sa_sigaction = handler_user_only;
     }
 #endif
     sigaction(SIGHUP, &sa, NULL);
@@ -306,7 +320,7 @@ sudo_execute(struct command_details *details, struct command_status *cstat)
      * to and from pty.  Adjusts maxfd as needed.
      */
     if (log_io)
-       child = fork_pty(details, sv, &maxfd);
+       child = fork_pty(details, sv, &maxfd, &omask);
     else
        child = fork_cmnd(details, sv);
     close(sv[1]);
@@ -399,7 +413,19 @@ sudo_execute(struct command_details *details, struct command_status *cstat)
                    break;
                }
            }
-           if (cstat->type == CMD_WSTATUS) {
+           if (cstat->type == 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 = cstat->val;
+               sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
+                   details->command, cmnd_pid);
+               if (log_io)
+                   sigprocmask(SIG_SETMASK, &omask, NULL);
+           } else if (cstat->type == CMD_WSTATUS) {
                if (WIFSTOPPED(cstat->val)) {
                    /* Suspend parent and tell child how to resume on return. */
                    sudo_debug_printf(SUDO_DEBUG_INFO,
@@ -538,7 +564,7 @@ handle_signals(int sv[2], pid_t child, int log_io, struct command_status *cstat)
            } else {
                /* Nothing listening on sv[0], send directly. */
                if (signo == SIGALRM)
-                   terminate_child(child, false);
+                   terminate_command(child, false);
                else if (kill(child, signo) != 0)
                    warning("kill(%d, %d)", (int)child, signo);
            }
@@ -611,6 +637,28 @@ schedule_signal(int signo)
  * Generic handler for signals passed from parent -> child.
  * The other end of signal_pipe is checked in the main event loop.
  */
+#ifdef SA_SIGINFO
+void
+handler(int s, siginfo_t *info, void *context)
+{
+    unsigned char signo = (unsigned char)s;
+
+    /*
+     * If the signal came from the command we ran, just ignore
+     * it since 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 (info != NULL && info->si_code == SI_USER && 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.
+     */
+    ignore_result(write(signal_pipe[1], &signo, sizeof(signo)));
+}
+#else
 void
 handler(int s)
 {
@@ -622,6 +670,7 @@ handler(int s)
      */
     ignore_result(write(signal_pipe[1], &signo, sizeof(signo)));
 }
+#endif
 
 #ifdef SA_SIGINFO
 /*
@@ -631,7 +680,7 @@ handler(int s)
  * signals that are generated by the kernel.
  */
 static void
-handler_nofwd(int s, siginfo_t *info, void *context)
+handler_user_only(int s, siginfo_t *info, void *context)
 {
     unsigned char signo = (unsigned char)s;
 
index f34df2b7c6bcb8f78bc85963bde9b84c93160df9..4f44d003107329588a077c5f048909f810e02e11 100644 (file)
@@ -93,7 +93,7 @@ static char slavename[PATH_MAX];
 static bool foreground, pipeline, tty_initialized;
 static int io_fds[6] = { -1, -1, -1, -1, -1, -1};
 static int ttymode = TERM_COOKED;
-static pid_t ppgrp, child, child_pgrp;
+static pid_t ppgrp, cmnd_pgrp;
 static sigset_t ttyblock;
 static struct io_buffer *iobufs;
 
@@ -127,6 +127,45 @@ cleanup(int gotsignal)
     debug_return;
 }
 
+/*
+ * 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
+void
+mon_handler(int s, siginfo_t *info, void *context)
+{
+    unsigned char signo = (unsigned char)s;
+
+    /*
+     * If the signal came from the command we ran, just ignore
+     * it since 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 (info != NULL && info->si_code == SI_USER && 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.
+     */
+    ignore_result(write(signal_pipe[1], &signo, sizeof(signo)));
+}
+#else
+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.
+     */
+    ignore_result(write(signal_pipe[1], &signo, sizeof(signo)));
+}
+#endif
+
 /*
  * Allocate a pty if /dev/tty is a tty.
  * Fills in io_fds[SFD_USERTTY], io_fds[SFD_MASTER], io_fds[SFD_SLAVE]
@@ -290,7 +329,7 @@ check_foreground(void)
 
 /*
  * Suspend sudo if the underlying command is suspended.
- * Returns SIGCONT_FG if the child should be resume in the
+ * Returns SIGCONT_FG if the command should be resumed in the
  * foreground or SIGCONT_BG if it is a background process.
  */
 int
@@ -304,7 +343,7 @@ suspend_parent(int signo)
     case SIGTTOU:
     case SIGTTIN:
        /*
-        * If we are the foreground process, just resume the child.
+        * If we are the foreground process, just resume the command.
         * Otherwise, re-send the signal with the handler disabled.
         */
        if (!foreground)
@@ -316,7 +355,7 @@ suspend_parent(int signo)
                } while (!n && errno == EINTR);
                ttymode = TERM_RAW;
            }
-           rval = SIGCONT_FG; /* resume child in foreground */
+           rval = SIGCONT_FG; /* resume command in foreground */
            break;
        }
        ttymode = TERM_RAW;
@@ -333,7 +372,7 @@ suspend_parent(int signo)
            } while (!n && errno == EINTR);
        }
 
-       /* Suspend self and continue child when we resume. */
+       /* Suspend self and continue command when we resume. */
        zero_bytes(&sa, sizeof(sa));
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
@@ -348,7 +387,7 @@ suspend_parent(int signo)
 
        /*
         * Only modify term if we are foreground process and either
-        * the old tty mode was not cooked or child got SIGTT{IN,OU}
+        * the old tty mode was not cooked or command got SIGTT{IN,OU}
         */
        sudo_debug_printf(SUDO_DEBUG_INFO, "parent is in %s, ttymode %d -> %d",
            foreground ? "foreground" : "background", oldmode, ttymode);
@@ -374,12 +413,12 @@ suspend_parent(int signo)
 }
 
 /*
- * Kill child with increasing urgency.
+ * Kill command with increasing urgency.
  */
 void
-terminate_child(pid_t pid, bool use_pgrp)
+terminate_command(pid_t pid, bool use_pgrp)
 {
-    debug_decl(terminate_child, SUDO_DEBUG_EXEC);
+    debug_decl(terminate_command, SUDO_DEBUG_EXEC);
 
     /*
      * Note that SIGCHLD will interrupt the sleep()
@@ -460,7 +499,7 @@ perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat)
                    sudo_debug_printf(SUDO_DEBUG_INFO,
                        "read %d bytes from fd %d", n, iob->rfd);
                    if (!iob->action(iob->buf + iob->len, n))
-                       terminate_child(child, true);
+                       terminate_command(cmnd_pid, true);
                    iob->len += n;
                    break;
            }
@@ -509,12 +548,14 @@ perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat)
  * Returns the child pid.
  */
 int
-fork_pty(struct command_details *details, int sv[], int *maxfd)
+fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask)
 {
     struct command_status cstat;
     struct io_buffer *iob;
     int io_pipe[3][2], n;
     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 */
@@ -594,7 +635,12 @@ fork_pty(struct command_details *details, int sv[], int *maxfd)
 
     /* Job control signals to relay from parent to child. */
     sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
+#ifdef SA_SIGINFO
+    sa.sa_flags |= SA_SIGINFO;
+    sa.sa_sigaction = handler;
+#else
     sa.sa_handler = handler;
+#endif
     sigaction(SIGTSTP, &sa, NULL);
 
     /* We don't want to receive SIGTTIN/SIGTTOU, getting EIO is preferable. */
@@ -627,6 +673,17 @@ fork_pty(struct command_details *details, int sv[], int *maxfd)
     if (policy_init_session(details) != true)
        errorx(1, _("policy plugin failed session initialization"));
 
+    /*
+     * 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:
@@ -638,6 +695,7 @@ fork_pty(struct command_details *details, int sv[], int *maxfd)
        close(signal_pipe[0]);
        close(signal_pipe[1]);
        fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+       sigprocmask(SIG_SETMASK, omask, NULL);
        if (exec_setup(details, slavename, io_fds[SFD_SLAVE]) == true) {
            /* Close the other end of the stdin/stdout/stderr pipes and exec. */
            if (io_pipe[STDIN_FILENO][1])
@@ -650,7 +708,7 @@ fork_pty(struct command_details *details, int sv[], int *maxfd)
        }
        cstat.type = CMD_ERRNO;
        cstat.val = errno;
-       send(sv[1], &cstat, sizeof(cstat), 0);
+       ignore_result(send(sv[1], &cstat, sizeof(cstat), 0));
        _exit(1);
     }
 
@@ -772,12 +830,12 @@ deliver_signal(pid_t pid, int signo, bool from_parent)
        from_parent ? " from parent" : "");
     switch (signo) {
     case SIGALRM:
-       terminate_child(pid, true);
+       terminate_command(pid, true);
        break;
     case SIGCONT_FG:
        /* Continue in foreground, grant it controlling tty. */
        do {
-           status = tcsetpgrp(io_fds[SFD_SLAVE], child_pgrp);
+           status = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
        } while (status == -1 && errno == EINTR);
        killpg(pid, SIGCONT);
        break;
@@ -792,7 +850,7 @@ deliver_signal(pid_t pid, int signo, bool from_parent)
        _exit(1); /* XXX */
        /* NOTREACHED */
     default:
-       /* Relay signal to child. */
+       /* Relay signal to command. */
        killpg(pid, signo);
        break;
     }
@@ -826,10 +884,10 @@ send_status(int fd, struct command_status *cstat)
 }
 
 /*
- * Wait for child status after receiving SIGCHLD.
- * If the child was stopped, the status is send back to the parent.
+ * Wait for command status after receiving SIGCHLD.
+ * If the command was stopped, the status is send back to the parent.
  * Otherwise, cstat is filled in but not sent.
- * Returns true if child is still alive, else false.
+ * Returns true if command is still alive, else false.
  */
 static bool
 handle_sigchld(int backchannel, struct command_status *cstat)
@@ -839,11 +897,11 @@ handle_sigchld(int backchannel, struct command_status *cstat)
     pid_t pid;
     debug_decl(handle_sigchld, SUDO_DEBUG_EXEC);
 
-    /* read child status */
+    /* read command status */
     do {
-       pid = waitpid(child, &status, WUNTRACED|WNOHANG);
+       pid = waitpid(cmnd_pid, &status, WUNTRACED|WNOHANG);
     } while (pid == -1 && errno == EINTR);
-    if (pid == child) {
+    if (pid == cmnd_pid) {
        if (cstat->type != CMD_ERRNO) {
            cstat->type = CMD_WSTATUS;
            cstat->val = status;
@@ -851,8 +909,8 @@ handle_sigchld(int backchannel, struct command_status *cstat)
                sudo_debug_printf(SUDO_DEBUG_INFO, "command stopped, signal %d",
                    WSTOPSIG(status));
                do {
-                   child_pgrp = tcgetpgrp(io_fds[SFD_SLAVE]);
-               } while (child_pgrp == -1 && errno == EINTR);
+                   cmnd_pgrp = tcgetpgrp(io_fds[SFD_SLAVE]);
+               } while (cmnd_pgrp == -1 && errno == EINTR);
                if (send_status(backchannel, cstat) == -1)
                    return alive; /* XXX */
            } else if (WIFSIGNALED(status)) {
@@ -916,12 +974,22 @@ exec_monitor(struct command_details *details, int backchannel)
 
     /* Note: HP-UX select() will not be interrupted if SA_RESTART set */
     sa.sa_flags = SA_INTERRUPT;
-    sa.sa_handler = handler;
+#ifdef SA_SIGINFO
+    sa.sa_flags |= SA_SIGINFO;
+    sa.sa_sigaction = mon_handler;
+#else
+    sa.sa_handler = mon_handler;
+#endif
     sigaction(SIGCHLD, &sa, NULL);
 
     /* Catch common signals so we can cleanup properly. */
     sa.sa_flags = SA_RESTART;
-    sa.sa_handler = handler;
+#ifdef SA_SIGINFO
+    sa.sa_flags |= SA_SIGINFO;
+    sa.sa_sigaction = mon_handler;
+#else
+    sa.sa_handler = mon_handler;
+#endif
     sigaction(SIGHUP, &sa, NULL);
     sigaction(SIGINT, &sa, NULL);
     sigaction(SIGQUIT, &sa, NULL);
@@ -933,7 +1001,7 @@ exec_monitor(struct command_details *details, int backchannel)
     /*
      * Start a new session with the parent as the session leader
      * and the slave pty as the controlling terminal.
-     * This allows us to be notified when the child has been suspended.
+     * This allows us to be notified when the command has been suspended.
      */
     if (setsid() == -1) {
        warning("setsid");
@@ -962,12 +1030,12 @@ exec_monitor(struct command_details *details, int backchannel)
     /* Start command and wait for it to stop or exit */
     if (pipe(errpipe) == -1)
        error(1, _("unable to create pipe"));
-    child = sudo_debug_fork();
-    if (child == -1) {
+    cmnd_pid = sudo_debug_fork();
+    if (cmnd_pid == -1) {
        warning(_("unable to fork"));
        goto bad;
     }
-    if (child == 0) {
+    if (cmnd_pid == 0) {
        /* We pass errno back to our parent via pipe on exec failure. */
        close(backchannel);
        close(signal_pipe[0]);
@@ -985,6 +1053,11 @@ exec_monitor(struct command_details *details, int backchannel)
     }
     close(errpipe[1]);
 
+    /* Send the command's pid to main sudo process. */
+    cstat.type = CMD_PID;
+    cstat.val = cmnd_pid;
+    ignore_result(send(backchannel, &cstat, sizeof(cstat), 0));
+
     /* If any of stdin/stdout/stderr are pipes, close them in parent. */
     if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
        close(io_fds[SFD_STDIN]);
@@ -994,14 +1067,14 @@ exec_monitor(struct command_details *details, int backchannel)
        close(io_fds[SFD_STDERR]);
 
     /*
-     * Put child in its own process group.  If we are starting the command
+     * Put command in its own process group.  If we are starting the command
      * in the foreground, assign its pgrp to the tty.
      */
-    child_pgrp = child;
-    setpgid(child, child_pgrp);
+    cmnd_pgrp = cmnd_pid;
+    setpgid(cmnd_pid, cmnd_pgrp);
     if (foreground) {
        do {
-           status = tcsetpgrp(io_fds[SFD_SLAVE], child_pgrp);
+           status = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
        } while (status == -1 && errno == EINTR);
     }
 
@@ -1040,13 +1113,13 @@ exec_monitor(struct command_details *details, int backchannel)
            }
            /*
             * Handle SIGCHLD specially and deliver other signals
-            * directly to the child.
+            * directly to the command.
             */
            if (signo == SIGCHLD) {
                if (!handle_sigchld(backchannel, &cstat))
                    alive = false;
            } else {
-               deliver_signal(child, signo, false);
+               deliver_signal(cmnd_pid, signo, false);
            }
            continue;
        }
@@ -1080,14 +1153,14 @@ exec_monitor(struct command_details *details, int backchannel)
                    cstmp.type);
                continue;
            }
-           deliver_signal(child, cstmp.val, true);
+           deliver_signal(cmnd_pid, cstmp.val, true);
        }
     }
 
 done:
     if (alive) {
        /* XXX An error occurred, should send an error back. */
-       kill(child, SIGKILL);
+       kill(cmnd_pid, SIGKILL);
     } else {
        /* Send parent status. */
        send_status(backchannel, &cstat);
@@ -1184,7 +1257,7 @@ exec_pty(struct command_details *details, int *errfd)
     pid_t self = getpid();
     debug_decl(exec_pty, SUDO_DEBUG_EXEC);
 
-    /* Set child process group here too to avoid a race. */
+    /* Set command process group here too to avoid a race. */
     setpgid(0, self);
 
     /* Wire up standard fds, note that stdout/stderr may be pipes. */
index 178062f4df5bdd1fd05913294dd0d05a909cfeea..87accee150d1c0e169f0fe6c4464274f0bad3dcc 100644 (file)
@@ -165,6 +165,7 @@ struct command_status {
 #define CMD_ERRNO 1
 #define CMD_WSTATUS 2
 #define CMD_SIGNO 3
+#define CMD_PID 4
     int type;
     int val;
 };
index d9ce2e85d4f62dfaff223eb1e349903512c8a2d8..f22976be429d492e54f708070edfb4ef99d5f1e3 100644 (file)
 /* exec.c */
 int sudo_execve(const char *path, char *const argv[], char *const envp[], int noexec);
 int pipe_nonblock(int fds[2]);
+extern volatile pid_t cmnd_pid;
 
 /* exec_pty.c */
 struct command_details;
 struct command_status;
-int fork_pty(struct command_details *details, int sv[], int *maxfd);
+int fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *oset);
 int perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat);
 int suspend_parent(int signo);
 void fd_set_iobs(fd_set *fdsr, fd_set *fdsw);
+#ifdef SA_SIGINFO
+void handler(int s, siginfo_t *info, void *context);
+#else
 void handler(int s);
+#endif
 void pty_close(struct command_status *cstat);
 void pty_setup(uid_t uid, const char *tty, const char *utmp_user);
-void terminate_child(pid_t pid, bool use_pgrp);
+void terminate_command(pid_t pid, bool use_pgrp);
 extern int signal_pipe[2];
 
 /* utmp.c */