Diff for /embedaddon/sudo/src/exec.c between versions 1.1.1.5 and 1.1.1.6

version 1.1.1.5, 2013/10/14 07:56:35 version 1.1.1.6, 2014/06/15 16:12:55
Line 1 Line 1
 /*  /*
 * Copyright (c) 2009-2013 Todd C. Miller <Todd.Miller@courtesan.com> * Copyright (c) 2009-2014 Todd C. Miller <Todd.Miller@courtesan.com>
  *   *
  * Permission to use, copy, modify, and distribute this software for any   * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above   * purpose with or without fee is hereby granted, provided that the above
Line 17 Line 17
 #include <config.h>  #include <config.h>
   
 #include <sys/types.h>  #include <sys/types.h>
 #ifdef HAVE_SYS_SYSMACROS_H  
 # include <sys/sysmacros.h>  
 #endif  
 #include <sys/socket.h>  #include <sys/socket.h>
 #include <sys/stat.h>  #include <sys/stat.h>
 #include <sys/time.h>  #include <sys/time.h>
 #include <sys/wait.h>  #include <sys/wait.h>
 #include <sys/ioctl.h>  #include <sys/ioctl.h>
 #ifdef HAVE_SYS_SELECT_H  
 # include <sys/select.h>  
 #endif /* HAVE_SYS_SELECT_H */  
 #include <stdio.h>  #include <stdio.h>
 #ifdef STDC_HEADERS  #ifdef STDC_HEADERS
 # include <stdlib.h>  # include <stdlib.h>
Line 46 Line 40
 #ifdef HAVE_UNISTD_H  #ifdef HAVE_UNISTD_H
 # include <unistd.h>  # include <unistd.h>
 #endif /* HAVE_UNISTD_H */  #endif /* HAVE_UNISTD_H */
#if TIME_WITH_SYS_TIME#ifdef TIME_WITH_SYS_TIME
 # include <time.h>  # include <time.h>
 #endif  #endif
 #include <errno.h>  #include <errno.h>
Line 56 Line 50
   
 #include "sudo.h"  #include "sudo.h"
 #include "sudo_exec.h"  #include "sudo_exec.h"
   #include "sudo_event.h"
 #include "sudo_plugin.h"  #include "sudo_plugin.h"
 #include "sudo_plugin_int.h"  #include "sudo_plugin_int.h"
   
   struct exec_closure {
       pid_t child;
       bool log_io;
       sigset_t omask;
       struct command_status *cstat;
       struct command_details *details;
       struct sudo_event_base *evbase;
   };
   
 /* We keep a tailq of signals to forward to child. */  /* We keep a tailq of signals to forward to child. */
 struct sigforward {  struct sigforward {
    struct sigforward *prev, *next;    TAILQ_ENTRY(sigforward) entries;
     int signo;      int signo;
 };  };
TQ_DECLARE(sigforward)TAILQ_HEAD(sigfwd_list, sigforward);
static struct sigforward_list sigfwd_list;static struct sigfwd_list sigfwd_list = TAILQ_HEAD_INITIALIZER(sigfwd_list);
 static struct sudo_event *signal_event;
 static struct sudo_event *sigfwd_event;
 static struct sudo_event *backchannel_event;
 static pid_t ppgrp = -1;  static pid_t ppgrp = -1;
   
 volatile pid_t cmnd_pid = -1;  volatile pid_t cmnd_pid = -1;
   
static int dispatch_signals(int sv[2], pid_t child, int log_io,static void signal_pipe_cb(int fd, int what, void *v);
    struct command_status *cstat); 
 static int dispatch_pending_signals(struct command_status *cstat);  static int dispatch_pending_signals(struct command_status *cstat);
static void forward_signals(int fd);static void forward_signals(int fd, int what, void *v);
static void schedule_signal(int signo);static void schedule_signal(struct sudo_event_base *evbase, int signo);
 #ifdef SA_SIGINFO  #ifdef SA_SIGINFO
 static void handler_user_only(int s, siginfo_t *info, void *context);  static void handler_user_only(int s, siginfo_t *info, void *context);
 #endif  #endif
Line 124  static int fork_cmnd(struct command_details *details,  Line 130  static int fork_cmnd(struct command_details *details, 
      * or certain pam modules won't be able to track their state.       * or certain pam modules won't be able to track their state.
      */       */
     if (policy_init_session(details) != true)      if (policy_init_session(details) != true)
        fatalx(_("policy plugin failed session initialization"));        fatalx(U_("policy plugin failed session initialization"));
   
     cmnd_pid = sudo_debug_fork();      cmnd_pid = sudo_debug_fork();
     switch (cmnd_pid) {      switch (cmnd_pid) {
     case -1:      case -1:
        fatal(_("unable to fork"));        fatal(U_("unable to fork"));
         break;          break;
     case 0:      case 0:
         /* child */          /* child */
Line 137  static int fork_cmnd(struct command_details *details,  Line 143  static int fork_cmnd(struct command_details *details, 
         close(signal_pipe[0]);          close(signal_pipe[0]);
         close(signal_pipe[1]);          close(signal_pipe[1]);
         fcntl(sv[1], F_SETFD, FD_CLOEXEC);          fcntl(sv[1], F_SETFD, FD_CLOEXEC);
        exec_cmnd(details, &cstat, &sv[1]);        exec_cmnd(details, &cstat, sv[1]);
         send(sv[1], &cstat, sizeof(cstat), 0);          send(sv[1], &cstat, sizeof(cstat), 0);
         sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);          sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
         _exit(1);          _exit(1);
Line 155  static int fork_cmnd(struct command_details *details,  Line 161  static int fork_cmnd(struct command_details *details, 
  */   */
 void  void
 exec_cmnd(struct command_details *details, struct command_status *cstat,  exec_cmnd(struct command_details *details, struct command_status *cstat,
    int *errfd)    int errfd)
 {  {
     debug_decl(exec_cmnd, SUDO_DEBUG_EXEC)      debug_decl(exec_cmnd, SUDO_DEBUG_EXEC)
   
Line 165  exec_cmnd(struct command_details *details, struct comm Line 171  exec_cmnd(struct command_details *details, struct comm
         sudo_debug_execve(SUDO_DEBUG_INFO, details->command,          sudo_debug_execve(SUDO_DEBUG_INFO, details->command,
             details->argv, details->envp);              details->argv, details->envp);
         if (details->closefrom >= 0) {          if (details->closefrom >= 0) {
            int maxfd = details->closefrom;            /* Preserve debug fd and error pipe as needed. */
            /* Preserve back channel if present. */            int debug_fd = sudo_debug_fd_get();
            if (errfd != NULL) {            if (debug_fd != -1)
                dup2(*errfd, maxfd);                add_preserved_fd(&details->preserved_fds, debug_fd);
                (void)fcntl(maxfd, F_SETFD, FD_CLOEXEC);            if (errfd != -1)
                *errfd = maxfd++;                add_preserved_fd(&details->preserved_fds, errfd);
            }
            if (sudo_debug_fd_set(maxfd) != -1)            /* Close all fds except those explicitly preserved. */
                maxfd++;            closefrom_except(details->closefrom, &details->preserved_fds);
            closefrom(maxfd); 
         }          }
 #ifdef HAVE_SELINUX  #ifdef HAVE_SELINUX
         if (ISSET(details->flags, CD_RBAC_ENABLED)) {          if (ISSET(details->flags, CD_RBAC_ENABLED)) {
Line 194  exec_cmnd(struct command_details *details, struct comm Line 199  exec_cmnd(struct command_details *details, struct comm
     debug_return;      debug_return;
 }  }
   
   static void
   backchannel_cb(int fd, int what, void *v)
   {
       struct exec_closure *ec = v;
       ssize_t n;
       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) {
               switch (errno) {
               case EINTR:
                   /* got a signal, restart loop to service it. */
                   sudo_ev_loopcontinue(ec->evbase);
                   break;
               case EAGAIN:
                   /* not ready after all... */
                   break;
               default:
                   ec->cstat->type = CMD_ERRNO;
                   ec->cstat->val = errno;
                   sudo_debug_printf(SUDO_DEBUG_ERROR,
                       "failed to read child status: %s", strerror(errno));
                   sudo_ev_loopbreak(ec->evbase);
                   break;
               }
           } else {
               /* Short read or EOF. */
               sudo_debug_printf(SUDO_DEBUG_ERROR,
                   "failed to read child status: %s", n ? "short read" : "EOF");
               if (!ec->log_io && n == 0) {
                   /*
                    * If not logging I/O we may get EOF when the command is
                    * executed and the other end of the backchannel is closed.
                    * Just remove the event in this case.
                    */
                   (void)sudo_ev_del(ec->evbase, backchannel_event);
               } else {
                   /* 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);
               }
           }
           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);
           if (ec->log_io)
               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_parent(WSTOPSIG(ec->cstat->val));
               schedule_signal(ec->evbase, 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;
   }
   
 /*  /*
    * Setup initial exec events.
    * Allocates events for the signal pipe and backchannel.
    * Forwarded signals on the backchannel are enabled on demand.
    */
   static struct sudo_event_base *
   exec_event_setup(int backchannel, struct exec_closure *ec)
   {
       struct sudo_event_base *evbase;
       debug_decl(exec_event_setup, SUDO_DEBUG_EXEC)
   
       evbase = sudo_ev_base_alloc();
       if (evbase == NULL)
           fatal(NULL);
   
       /* Event for incoming signals via signal_pipe. */
       signal_event = sudo_ev_alloc(signal_pipe[0],
           SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, ec);
       if (signal_event == NULL)
           fatal(NULL);
       if (sudo_ev_add(evbase, signal_event, NULL, false) == -1)
           fatal(U_("unable to add event to queue"));
   
       /* Event for command status via backchannel. */
       backchannel_event = sudo_ev_alloc(backchannel,
           SUDO_EV_READ|SUDO_EV_PERSIST, backchannel_cb, ec);
       if (backchannel_event == NULL)
           fatal(NULL);
       if (sudo_ev_add(evbase, backchannel_event, NULL, false) == -1)
           fatal(U_("unable to add event to queue"));
   
       /* The signal forwarding event gets added on demand. */
       sigfwd_event = sudo_ev_alloc(backchannel,
           SUDO_EV_WRITE, forward_signals, NULL);
       if (sigfwd_event == NULL)
           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);
   
       debug_return_ptr(evbase);
   }
   
   /*
  * Execute a command, potentially in a pty with I/O loggging, and   * Execute a command, potentially in a pty with I/O loggging, and
  * wait for it to finish.   * wait for it to finish.
  * This is a little bit tricky due to how POSIX job control works and   * This is a little bit tricky due to how POSIX job control works and
Line 203  exec_cmnd(struct command_details *details, struct comm Line 339  exec_cmnd(struct command_details *details, struct comm
 int  int
 sudo_execute(struct command_details *details, struct command_status *cstat)  sudo_execute(struct command_details *details, struct command_status *cstat)
 {  {
    int maxfd, n, nready, sv[2];    struct sigforward *sigfwd, *sigfwd_next;
     const char *utmp_user = NULL;      const char *utmp_user = NULL;
       struct sudo_event_base *evbase;
       struct exec_closure ec;
     bool log_io = false;      bool log_io = false;
     fd_set *fdsr, *fdsw;  
     sigaction_t sa;      sigaction_t sa;
     sigset_t omask;  
     pid_t child;      pid_t child;
       int sv[2];
     debug_decl(sudo_execute, SUDO_DEBUG_EXEC)      debug_decl(sudo_execute, SUDO_DEBUG_EXEC)
   
     dispatch_pending_signals(cstat);      dispatch_pending_signals(cstat);
Line 238  sudo_execute(struct command_details *details, struct c Line 375  sudo_execute(struct command_details *details, struct c
      * need to allocate a pty.  It is OK to set log_io in the pty-only case       * need to allocate a pty.  It is OK to set log_io in the pty-only case
      * as the io plugin tailqueue will be empty and no I/O logging will occur.       * as the io plugin tailqueue will be empty and no I/O logging will occur.
      */       */
    if (!tq_empty(&io_plugins) || ISSET(details->flags, CD_USE_PTY)) {    if (!TAILQ_EMPTY(&io_plugins) || ISSET(details->flags, CD_USE_PTY)) {
         log_io = true;          log_io = true;
         if (ISSET(details->flags, CD_SET_UTMP))          if (ISSET(details->flags, CD_SET_UTMP))
             utmp_user = details->utmp_user ? details->utmp_user : user_details.username;              utmp_user = details->utmp_user ? details->utmp_user : user_details.username;
         sudo_debug_printf(SUDO_DEBUG_INFO, "allocate pty for I/O logging");          sudo_debug_printf(SUDO_DEBUG_INFO, "allocate pty for I/O logging");
         pty_setup(details->euid, user_details.tty, utmp_user);          pty_setup(details->euid, user_details.tty, utmp_user);
    } else if (!ISSET(details->flags, CD_SET_TIMEOUT) &&    } else if (!ISSET(details->flags, CD_SET_TIMEOUT|CD_SUDOEDIT) &&
         policy_plugin.u.policy->close == NULL) {          policy_plugin.u.policy->close == NULL) {
        /* If no I/O logging, timeout or policy close we can exec directly. */        /*
        exec_cmnd(details, cstat, NULL);         * If there is no policy close function, no I/O logging or pty,
          * and we were not invoked as sudoedit, just exec directly.
          */
         exec_cmnd(details, cstat, -1);
         goto done;          goto done;
     }      }
   
Line 255  sudo_execute(struct command_details *details, struct c Line 395  sudo_execute(struct command_details *details, struct c
      * We communicate with the child over a bi-directional pair of sockets.       * We communicate with the child over a bi-directional pair of sockets.
      * Parent sends signal info to child and child sends back wait status.       * Parent sends signal info to child and child sends back wait status.
      */       */
    if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) == -1)    if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1)
        fatal(_("unable to create sockets"));        fatal(U_("unable to create sockets"));
   
     /*      /*
      * Signals to forward to the child process (excluding SIGALRM and SIGCHLD).       * Signals to forward to the child process (excluding SIGALRM and SIGCHLD).
Line 296  sudo_execute(struct command_details *details, struct c Line 436  sudo_execute(struct command_details *details, struct c
     sudo_sigaction(SIGINT, &sa, NULL);      sudo_sigaction(SIGINT, &sa, NULL);
     sudo_sigaction(SIGQUIT, &sa, NULL);      sudo_sigaction(SIGQUIT, &sa, NULL);
   
     /* Max fd we will be selecting on. */  
     maxfd = MAX(sv[0], signal_pipe[0]);  
   
     /*      /*
      * Child will run the command in the pty, parent will pass data       * Child will run the command in the pty, parent will pass data
     * to and from pty.  Adjusts maxfd as needed.     * to and from pty.
      */       */
     if (log_io)      if (log_io)
        child = fork_pty(details, sv, &maxfd, &omask);        child = fork_pty(details, sv, &ec.omask);
     else      else
         child = fork_cmnd(details, sv);          child = fork_cmnd(details, sv);
     close(sv[1]);      close(sv[1]);
Line 320  sudo_execute(struct command_details *details, struct c Line 457  sudo_execute(struct command_details *details, struct c
     setlocale(LC_ALL, "C");      setlocale(LC_ALL, "C");
   
     /*      /*
        * Allocate event base and two persistent events:
        *  the signal pipe and the child process's backchannel.
        */
       evbase = exec_event_setup(sv[0], &ec);
   
       /*
        * Generic exec closure used for signal_pipe and backchannel callbacks.
        * Note ec.omask is set earlier.
        */
       ec.child = child;
       ec.log_io = log_io;
       ec.cstat = cstat;
       ec.evbase = evbase;
       ec.details = details;
   
       /*
      * In the event loop we pass input from user tty to master       * In the event loop we pass input from user tty to master
      * and pass output from master to stdout and IO plugin.       * and pass output from master to stdout and IO plugin.
      */       */
    fdsr = emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));    if (log_io)
    fdsw = emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask));        add_io_events(evbase);
    for (;;) {    if (sudo_ev_loop(evbase, 0) == -1)
        memset(fdsw, 0, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));        warning(U_("error in event loop"));
        memset(fdsr, 0, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask));    if (sudo_ev_got_break(evbase)) {
        /* error from callback */
        FD_SET(signal_pipe[0], fdsr);        sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
        FD_SET(sv[0], fdsr);        /* kill command if still running and not I/O logging */
        if (!tq_empty(&sigfwd_list))        if (!log_io && kill(child, 0) == 0)
            FD_SET(sv[0], fdsw);            terminate_command(child, true);
        if (log_io) 
            fd_set_iobs(fdsr, fdsw); /* XXX - better name */ 
        nready = select(maxfd + 1, fdsr, fdsw, NULL, NULL); 
        sudo_debug_printf(SUDO_DEBUG_DEBUG, "select returns %d", nready); 
        if (nready == -1) { 
            if (errno == EINTR || errno == ENOMEM) 
                continue; 
            if (errno == EBADF || errno == EIO) { 
                /* One of the ttys must have gone away. */ 
                goto do_tty_io; 
            } 
            warning(_("select failed")); 
            sudo_debug_printf(SUDO_DEBUG_ERROR, 
                "select failure, terminating child"); 
            schedule_signal(SIGKILL); 
            forward_signals(sv[0]); 
            break; 
        } 
        if (FD_ISSET(sv[0], fdsw)) { 
            forward_signals(sv[0]); 
        } 
        if (FD_ISSET(signal_pipe[0], fdsr)) { 
            n = dispatch_signals(sv, child, log_io, cstat); 
            if (n == 0) { 
                /* Child has exited, cstat is set, we are done. */ 
                break; 
            } 
            if (n == -1) { 
                /* Error reading signal_pipe[0], should not happen. */ 
                break; 
            } 
            /* Restart event loop so signals get sent to child immediately. */ 
            continue; 
        } 
        if (FD_ISSET(sv[0], fdsr)) { 
            /* read child status */ 
            n = recv(sv[0], cstat, sizeof(*cstat), 0); 
            if (n != sizeof(*cstat)) { 
                if (n == -1) { 
                    if (errno == EINTR) 
                        continue; 
                    /* 
                     * If not logging I/O we may receive ECONNRESET when 
                     * the command is executed and sv is closed. 
                     * It is safe to ignore this. 
                     */ 
                    if (log_io && errno != EAGAIN) { 
                        cstat->type = CMD_ERRNO; 
                        cstat->val = errno; 
                        break; 
                    } 
                    sudo_debug_printf(SUDO_DEBUG_ERROR, 
                        "failed to read child status: %s", strerror(errno)); 
                } else { 
                    /* Short read or EOF. */ 
                    sudo_debug_printf(SUDO_DEBUG_ERROR, 
                        "failed to read child status: %s", 
                        n ? "short read" : "EOF"); 
                    /* XXX - should set cstat */ 
                    break; 
                } 
            } 
            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, (int)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, 
                        "child stopped, suspending parent"); 
                    n = suspend_parent(WSTOPSIG(cstat->val)); 
                    schedule_signal(n); 
                    continue; 
                } else { 
                    /* Child exited or was killed, either way we are done. */ 
                    sudo_debug_printf(SUDO_DEBUG_INFO, "child exited or was killed"); 
                    break; 
                } 
            } else if (cstat->type == CMD_ERRNO) { 
                /* Child was unable to execute command or broken pipe. */ 
                sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s", 
                    strerror(cstat->val)); 
                break; 
            } 
        } 
do_tty_io: 
        if (perform_io(fdsr, fdsw, cstat) != 0) { 
            /* I/O error, kill child if still alive and finish. */ 
            sudo_debug_printf(SUDO_DEBUG_ERROR, "I/O error, terminating child"); 
            schedule_signal(SIGKILL); 
            forward_signals(sv[0]); 
            break; 
        } 
     }      }
   
     if (log_io) {      if (log_io) {
Line 446  do_tty_io: Line 497  do_tty_io:
     if (ISSET(details->flags, CD_RBAC_ENABLED)) {      if (ISSET(details->flags, CD_RBAC_ENABLED)) {
         /* This is probably not needed in log_io mode. */          /* This is probably not needed in log_io mode. */
         if (selinux_restore_tty() != 0)          if (selinux_restore_tty() != 0)
            warningx(_("unable to restore tty label"));            warningx(U_("unable to restore tty label"));
     }      }
 #endif  #endif
   
    efree(fdsr);    /* Free things up. */
    efree(fdsw);    sudo_ev_base_free(evbase);
    while (!tq_empty(&sigfwd_list)) {    sudo_ev_free(sigfwd_event);
        struct sigforward *sigfwd = tq_first(&sigfwd_list);    sudo_ev_free(signal_event);
        tq_remove(&sigfwd_list, sigfwd);    sudo_ev_free(backchannel_event);
     TAILQ_FOREACH_SAFE(sigfwd, &sigfwd_list, entries, sigfwd_next) {
         efree(sigfwd);          efree(sigfwd);
     }      }
       TAILQ_INIT(&sigfwd_list);
 done:  done:
     debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);      debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);
 }  }
   
 /*  /*
 * Read signals on signal_pipe written by handler(). * Forward a signal to the command (non-pty version).
 * Returns -1 on error, 0 on child exit, else 1. 
  */   */
 static int  static int
dispatch_signals(int sv[2], pid_t child, int log_io, struct command_status *cstat)dispatch_signal(struct sudo_event_base *evbase, pid_t child,
     int signo, char *signame, struct command_status *cstat)
 {  {
       int rc = 1;
       debug_decl(dispatch_signal, SUDO_DEBUG_EXEC)
   
       sudo_debug_printf(SUDO_DEBUG_INFO,
           "%s: evbase %p, child: %d, signo %s(%d), cstat %p",
           __func__, evbase, (int)child, signame, signo, cstat);
   
       if (signo == SIGCHLD) {
           pid_t pid;
           int status;
           /*
            * The command stopped or exited.
            */
           do {
               pid = waitpid(child, &status, WUNTRACED|WNOHANG);
           } while (pid == -1 && errno == EINTR);
           if (pid == child) {
               if (WIFSTOPPED(status)) {
                   /*
                    * 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 resume, potentially stopping sudo
                    * with SIGTTOU while the command continues to run.
                    */
                   sigaction_t sa, osa;
                   pid_t saved_pgrp = (pid_t)-1;
                   int signo = WSTOPSIG(status);
                   int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
                   if (fd != -1) {
                       saved_pgrp = tcgetpgrp(fd);
                       /*
                        * Child was stopped trying to access 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.
                        */
                       if (signo == SIGTTOU || signo == SIGTTIN) {
                           if (saved_pgrp == ppgrp) {
                               pid_t child_pgrp = getpgid(child);
                               if (child_pgrp != ppgrp) {
                                   if (tcsetpgrp(fd, child_pgrp) == 0) {
                                       if (killpg(child_pgrp, SIGCONT) != 0) {
                                           warning("kill(%d, SIGCONT)",
                                               (int)child_pgrp);
                                       }
                                       close(fd);
                                       goto done;
                                   }
                               }
                           }
                       }
                   }
                   if (signo == SIGTSTP) {
                       memset(&sa, 0, sizeof(sa));
                       sigemptyset(&sa.sa_mask);
                       sa.sa_flags = SA_RESTART;
                       sa.sa_handler = SIG_DFL;
                       sudo_sigaction(SIGTSTP, &sa, &osa);
                   }
                   if (kill(getpid(), signo) != 0)
                       warning("kill(%d, SIG%s)", (int)getpid(), signame);
                   if (signo == SIGTSTP)
                       sudo_sigaction(SIGTSTP, &osa, NULL);
                   if (fd != -1) {
                       /*
                        * Restore command's process group if different.
                        * Otherwise, we cannot resume some shells.
                        */
                       if (saved_pgrp != ppgrp)
                           (void)tcsetpgrp(fd, saved_pgrp);
                       close(fd);
                   }
               } else {
                   /* Child has exited or been killed, we are done. */
                   cstat->type = CMD_WSTATUS;
                   cstat->val = status;
                   sudo_ev_loopexit(evbase);
                   goto done;
               }
           }
       } else {
           /* Send signal to child. */
           if (signo == SIGALRM) {
               terminate_command(child, false);
           } else if (kill(child, signo) != 0) {
               warning("kill(%d, SIG%s)", (int)child, signame);
           }
       }
       rc = 0;
   done:
       debug_return_int(rc);
   }
   
   /*
    * Forward a signal to the monitory (pty version).
    */
   static int
   dispatch_signal_pty(struct sudo_event_base *evbase, pid_t child,
       int signo, char *signame, struct command_status *cstat)
   {
       int rc = 1;
       debug_decl(dispatch_signal_pty, SUDO_DEBUG_EXEC)
   
       sudo_debug_printf(SUDO_DEBUG_INFO,
           "%s: evbase %p, child: %d, signo %s(%d), cstat %p",
           __func__, evbase, (int)child, signame, signo, cstat);
   
       if (signo == SIGCHLD) {
           int n, status;
           pid_t pid;
           /*
            * Monitor process was signaled; wait for it as needed.
            */
           do {
               pid = waitpid(child, &status, WUNTRACED|WNOHANG);
           } while (pid == -1 && errno == EINTR);
           if (pid == 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 parent");
                   n = suspend_parent(WSTOPSIG(status));
                   kill(pid, SIGCONT);
                   schedule_signal(evbase, n);
                   /* Re-enable I/O events and restart event loop. */
                   add_io_events(evbase);
                   sudo_ev_loopcontinue(evbase);
                   goto done;
               } else if (WIFSIGNALED(status)) {
                   sudo_debug_printf(SUDO_DEBUG_INFO,
                       "monitor killed, signal %d", WTERMSIG(status));
               } else {
                   sudo_debug_printf(SUDO_DEBUG_INFO,
                       "monitor exited, status %d", WEXITSTATUS(status));
               }
           }
       } else {
           /* Schedule signo to be forwared to the child. */
           schedule_signal(evbase, signo);
           /* Restart event loop to service signal immediately. */
           sudo_ev_loopcontinue(evbase);
       }
       rc = 0;
   done:
       debug_return_int(rc);
   }
   
   /* Signal pipe callback */
   static void
   signal_pipe_cb(int fd, int what, void *v)
   {
       struct exec_closure *ec = v;
     char signame[SIG2STR_MAX];      char signame[SIG2STR_MAX];
     unsigned char signo;      unsigned char signo;
     ssize_t nread;      ssize_t nread;
    int status;    int rc = 0;
    pid_t pid;    debug_decl(signal_pipe_cb, SUDO_DEBUG_EXEC)
    debug_decl(dispatch_signals, SUDO_DEBUG_EXEC) 
   
    for (;;) {    do {
         /* read signal pipe */          /* read signal pipe */
        nread = read(signal_pipe[0], &signo, sizeof(signo));        nread = read(fd, &signo, sizeof(signo));
         if (nread <= 0) {          if (nread <= 0) {
            /* It should not be possible to get EOF but just in case. */            /* It should not be possible to get EOF but just in case... */
             if (nread == 0)              if (nread == 0)
                 errno = ECONNRESET;                  errno = ECONNRESET;
             /* Restart if interrupted by signal so the pipe doesn't fill. */              /* Restart if interrupted by signal so the pipe doesn't fill. */
             if (errno == EINTR)              if (errno == EINTR)
                 continue;                  continue;
            /* If pipe is empty, we are done. */            /* On error, store errno and break out of the event loop. */
            if (errno == EAGAIN)            if (errno != EAGAIN) {
                break;                sudo_debug_printf(SUDO_DEBUG_ERROR,
            sudo_debug_printf(SUDO_DEBUG_ERROR, "error reading signal pipe %s",                    "error reading signal pipe %s", strerror(errno));
                strerror(errno));                ec->cstat->type = CMD_ERRNO;
            cstat->type = CMD_ERRNO;                ec->cstat->val = errno;
            cstat->val = errno;                sudo_ev_loopbreak(ec->evbase);
            debug_return_int(-1);            }
             break;
         }          }
         if (sig2str(signo, signame) == -1)          if (sig2str(signo, signame) == -1)
             snprintf(signame, sizeof(signame), "%d", signo);              snprintf(signame, sizeof(signame), "%d", signo);
         sudo_debug_printf(SUDO_DEBUG_DIAG, "received SIG%s", signame);          sudo_debug_printf(SUDO_DEBUG_DIAG, "received SIG%s", signame);
        if (signo == SIGCHLD) {        if (ec->log_io) {
            /*            rc = dispatch_signal_pty(ec->evbase, ec->child, signo, signame,
             * If logging I/O, child is the intermediate process,                ec->cstat);
             * otherwise it is the command itself. 
             */ 
            do { 
                pid = waitpid(child, &status, WUNTRACED|WNOHANG); 
            } while (pid == -1 && errno == EINTR); 
            if (pid == child) { 
                if (log_io) { 
                    /* 
                     * On BSD we get ECONNRESET on sv[0] if monitor dies 
                     * and select() will return with sv[0] readable. 
                     * On Linux that doesn't appear to happen so if the 
                     * monitor dies, shut down the socketpair to force a 
                     * select() notification. 
                     */ 
                    (void) shutdown(sv[0], SHUT_WR); 
                } else if (WIFSTOPPED(status)) { 
                    /* 
                     * 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 resume, potentially stopping sudo 
                     * with SIGTTOU while the command continues to run. 
                     */ 
                    sigaction_t sa, osa; 
                    pid_t saved_pgrp = (pid_t)-1; 
                    int signo = WSTOPSIG(status); 
                    int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0); 
                    if (fd != -1) { 
                        saved_pgrp = tcgetpgrp(fd); 
                        /* 
                         * Child was stopped trying to access 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. 
                         */ 
                        if (signo == SIGTTOU || signo == SIGTTIN) { 
                            if (saved_pgrp == ppgrp) { 
                                pid_t child_pgrp = getpgid(child); 
                                if (child_pgrp != ppgrp) { 
                                    if (tcsetpgrp(fd, child_pgrp) == 0) { 
                                        if (killpg(child_pgrp, SIGCONT) != 0) { 
                                            warning("kill(%d, SIGCONT)", 
                                                (int)child_pgrp); 
                                        } 
                                        close(fd); 
                                        debug_return_int(1); 
                                    } 
                                } 
                            } 
                        } 
                    } 
                    if (signo == SIGTSTP) { 
                        memset(&sa, 0, sizeof(sa)); 
                        sigemptyset(&sa.sa_mask); 
                        sa.sa_flags = SA_RESTART; 
                        sa.sa_handler = SIG_DFL; 
                        sudo_sigaction(SIGTSTP, &sa, &osa); 
                    } 
                    if (kill(getpid(), signo) != 0) 
                        warning("kill(%d, SIG%s)", (int)getpid(), signame); 
                    if (signo == SIGTSTP) 
                        sudo_sigaction(SIGTSTP, &osa, NULL); 
                    if (fd != -1) { 
                        /* 
                         * Restore command's process group if different. 
                         * Otherwise, we cannot resume some shells. 
                         */ 
                        if (saved_pgrp != ppgrp) 
                            (void)tcsetpgrp(fd, saved_pgrp); 
                        close(fd); 
                    } 
                } else { 
                    /* Child has exited or been killed, we are done. */ 
                    cstat->type = CMD_WSTATUS; 
                    cstat->val = status; 
                    debug_return_int(0); 
                } 
            } 
         } else {          } else {
            if (log_io) {            rc = dispatch_signal(ec->evbase, ec->child, signo, signame,
                /* Schedule signo to be forwared to the child. */                ec->cstat);
                schedule_signal(signo); 
            } else { 
                /* Nothing listening on sv[0], send directly. */ 
                if (signo == SIGALRM) 
                    terminate_command(child, false); 
                else if (kill(child, signo) != 0) 
                    warning("kill(%d, SIG%s)", (int)child, signame); 
            } 
         }          }
    }    } while (rc == 0);
    debug_return_int(1);    debug_return;
 }  }
   
 /*  /*
Line 657  dispatch_pending_signals(struct command_status *cstat) Line 781  dispatch_pending_signals(struct command_status *cstat)
  * Forward signals in sigfwd_list to child listening on fd.   * Forward signals in sigfwd_list to child listening on fd.
  */   */
 static void  static void
forward_signals(int sock)forward_signals(int sock, int what, void *v)
 {  {
     char signame[SIG2STR_MAX];      char signame[SIG2STR_MAX];
     struct sigforward *sigfwd;      struct sigforward *sigfwd;
Line 665  forward_signals(int sock) Line 789  forward_signals(int sock)
     ssize_t nsent;      ssize_t nsent;
     debug_decl(forward_signals, SUDO_DEBUG_EXEC)      debug_decl(forward_signals, SUDO_DEBUG_EXEC)
   
    while (!tq_empty(&sigfwd_list)) {    while (!TAILQ_EMPTY(&sigfwd_list)) {
        sigfwd = tq_first(&sigfwd_list);        sigfwd = TAILQ_FIRST(&sigfwd_list);
         if (sigfwd->signo == SIGCONT_FG)          if (sigfwd->signo == SIGCONT_FG)
             strlcpy(signame, "CONT_FG", sizeof(signame));              strlcpy(signame, "CONT_FG", sizeof(signame));
         else if (sigfwd->signo == SIGCONT_BG)          else if (sigfwd->signo == SIGCONT_BG)
Line 680  forward_signals(int sock) Line 804  forward_signals(int sock)
         do {          do {
             nsent = send(sock, &cstat, sizeof(cstat), 0);              nsent = send(sock, &cstat, sizeof(cstat), 0);
         } while (nsent == -1 && errno == EINTR);          } while (nsent == -1 && errno == EINTR);
        tq_remove(&sigfwd_list, sigfwd);        TAILQ_REMOVE(&sigfwd_list, sigfwd, entries);
         efree(sigfwd);          efree(sigfwd);
         if (nsent != sizeof(cstat)) {          if (nsent != sizeof(cstat)) {
             if (errno == EPIPE) {              if (errno == EPIPE) {
                   struct sigforward *sigfwd_next;
                 sudo_debug_printf(SUDO_DEBUG_ERROR,                  sudo_debug_printf(SUDO_DEBUG_ERROR,
                     "broken pipe writing to child over backchannel");                      "broken pipe writing to child over backchannel");
                 /* Other end of socket gone, empty out sigfwd_list. */                  /* Other end of socket gone, empty out sigfwd_list. */
                while (!tq_empty(&sigfwd_list)) {                TAILQ_FOREACH_SAFE(sigfwd, &sigfwd_list, entries, sigfwd_next) {
                    sigfwd = tq_first(&sigfwd_list); 
                    tq_remove(&sigfwd_list, sigfwd); 
                     efree(sigfwd);                      efree(sigfwd);
                 }                  }
                   TAILQ_INIT(&sigfwd_list);
                 /* XXX - child (monitor) is dead, we should exit too? */                  /* XXX - child (monitor) is dead, we should exit too? */
             }              }
             break;              break;
         }          }
     }      }
     debug_return;  
 }  }
   
 /*  /*
 * Schedule a signal to be forwared. * Schedule a signal to be forwarded.
  */   */
 static void  static void
schedule_signal(int signo)schedule_signal(struct sudo_event_base *evbase, int signo)
 {  {
     struct sigforward *sigfwd;      struct sigforward *sigfwd;
     char signame[SIG2STR_MAX];      char signame[SIG2STR_MAX];
Line 719  schedule_signal(int signo) Line 842  schedule_signal(int signo)
     sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for child", signame);      sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for child", signame);
   
     sigfwd = ecalloc(1, sizeof(*sigfwd));      sigfwd = ecalloc(1, sizeof(*sigfwd));
     sigfwd->prev = sigfwd;  
     /* sigfwd->next = NULL; */  
     sigfwd->signo = signo;      sigfwd->signo = signo;
    tq_append(&sigfwd_list, sigfwd);    TAILQ_INSERT_TAIL(&sigfwd_list, sigfwd, entries);
 
     if (sudo_ev_add(evbase, sigfwd_event, NULL, true) == -1)
         fatal(U_("unable to add event to queue"));
   
     debug_return;      debug_return;
 }  }

Removed from v.1.1.1.5  
changed lines
  Added in v.1.1.1.6


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>