X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=exec.c;h=19dbad2e4dca52f116c0a32406a9b37e11505af6;hb=f3530d8198251b72d01da9a07b1fa518446ec0f0;hp=784f90ab40ff80020dc18f2ea12105076042b5c9;hpb=a91c5f78701b90c47a08daa15569ef64c6194504;p=debian%2Fsudo diff --git a/exec.c b/exec.c index 784f90a..19dbad2 100644 --- a/exec.c +++ b/exec.c @@ -18,6 +18,9 @@ #include #include +#ifdef HAVE_SYS_SYSMACROS_H +# include +#endif #include #include #include @@ -65,9 +68,24 @@ #include "sudo.h" #include "sudo_exec.h" -/* shared with exec_pty.c */ -sig_atomic_t recvsig[NSIG]; -void handler __P((int s)); +/* Shared with exec_pty.c for use with handler(). */ +int signal_pipe[2]; + +#ifdef _PATH_SUDO_IO_LOGDIR +/* We keep a tailq of signals to forward to child. */ +struct sigforward { + struct sigforward *prev, *next; + int signo; +}; +TQ_DECLARE(sigforward) +static struct sigforward_list sigfwd_list; +static void forward_signals __P((int fd)); +static void schedule_signal __P((int signo)); +static int log_io; +#endif /* _PATH_SUDO_IO_LOGDIR */ + +static int handle_signals __P((int fd, pid_t child, + struct command_status *cstat)); /* * Like execve(2) but falls back to running through /bin/sh @@ -102,7 +120,7 @@ static int fork_cmnd(path, argv, envp, sv, rbac_enabled) { struct command_status cstat; sigaction_t sa; - int pid; + pid_t child; zero_bytes(&sa, sizeof(sa)); sigemptyset(&sa.sa_mask); @@ -110,15 +128,18 @@ static int fork_cmnd(path, argv, envp, sv, rbac_enabled) sa.sa_handler = handler; sigaction(SIGCONT, &sa, NULL); - pid = fork(); - switch (pid) { + child = fork(); + switch (child) { case -1: error(1, "fork"); break; case 0: /* child */ close(sv[0]); + close(signal_pipe[0]); + close(signal_pipe[1]); fcntl(sv[1], F_SETFD, FD_CLOEXEC); + restore_signals(); if (exec_setup(rbac_enabled, user_ttypath, -1) == TRUE) { /* headed for execve() */ closefrom(def_closefrom); @@ -134,7 +155,51 @@ static int fork_cmnd(path, argv, envp, sv, rbac_enabled) send(sv[1], &cstat, sizeof(cstat), 0); _exit(1); } - return pid; + return child; +} + +static struct signal_state { + int signo; + sigaction_t sa; +} saved_signals[] = { + { SIGALRM }, + { SIGCHLD }, + { SIGCONT }, + { SIGHUP }, + { SIGINT }, + { SIGPIPE }, + { SIGQUIT }, + { SIGTERM }, + { SIGTSTP }, + { SIGTTIN }, + { SIGTTOU }, + { SIGUSR1 }, + { SIGUSR2 }, + { -1 } +}; + +/* + * Save signal handler state so it can be restored before exec. + */ +void +save_signals() +{ + struct signal_state *ss; + + for (ss = saved_signals; ss->signo != -1; ss++) + sigaction(ss->signo, NULL, &ss->sa); +} + +/* + * Restore signal handlers to initial state. + */ +void +restore_signals() +{ + struct signal_state *ss; + + for (ss = saved_signals; ss->signo != -1; ss++) + sigaction(ss->signo, &ss->sa, NULL); } /* @@ -152,11 +217,10 @@ sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode) int dowait; int bgmode; { - sigaction_t sa; - fd_set *fdsr, *fdsw; - int maxfd, n, nready, status, sv[2]; + int maxfd, n, nready, sv[2]; int rbac_enabled = 0; - int log_io; + fd_set *fdsr, *fdsw; + sigaction_t sa; pid_t child; /* If running in background mode, fork and exit. */ @@ -200,31 +264,44 @@ sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode) my_execve(path, argv, envp); cstat->type = CMD_ERRNO; cstat->val = errno; - return(127); + return 127; } /* * We communicate with the child over a bi-directional pair of sockets. * Parent sends signal info to child and child sends back wait status. */ - if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0) + if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) == -1) error(1, "cannot create sockets"); + /* + * We use a pipe to atomically handle signal notification within + * the select() loop. + */ + if (pipe_nonblock(signal_pipe) != 0) + error(1, "cannot create pipe"); + zero_bytes(&sa, sizeof(sa)); sigemptyset(&sa.sa_mask); - /* Note: HP-UX select() will not be interrupted if SA_RESTART set */ + /* + * Signals for forward to the child process (excluding SIGCHLD). + * Note: HP-UX select() will not be interrupted if SA_RESTART set. + */ sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */ sa.sa_handler = handler; + sigaction(SIGALRM, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); sigaction(SIGHUP, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGUSR2, &sa, NULL); /* Max fd we will be selecting on. */ - maxfd = sv[0]; + maxfd = MAX(sv[0], signal_pipe[0]); /* * Child will run the command in the pty, parent will pass data @@ -253,72 +330,48 @@ sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode) fdsr = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); fdsw = (fd_set *)emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); for (;;) { - if (recvsig[SIGCHLD]) { - pid_t pid; - - /* - * If logging I/O, child is the intermediate process, - * otherwise it is the command itself. - */ - recvsig[SIGCHLD] = FALSE; - do { -#ifdef sudo_waitpid - pid = sudo_waitpid(child, &status, WUNTRACED|WNOHANG); -#else - pid = wait(&status); -#endif - } while (pid == -1 && errno == EINTR); - if (pid == child) { - /* If not logging I/O and child has exited we are done. */ - if (!log_io) { - if (WIFSTOPPED(status)) { - /* Child may not have privs to suspend us itself. */ - kill(getpid(), WSTOPSIG(status)); - } else { - /* Child has exited, we are done. */ - cstat->type = CMD_WSTATUS; - cstat->val = status; - return 0; - } - } - /* Else we get ECONNRESET on sv[0] if child dies. */ - } - } - zero_bytes(fdsw, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); zero_bytes(fdsr, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); + FD_SET(signal_pipe[0], fdsr); FD_SET(sv[0], fdsr); #ifdef _PATH_SUDO_IO_LOGDIR + if (!tq_empty(&sigfwd_list)) + FD_SET(sv[0], fdsw); if (log_io) fd_set_iobs(fdsr, fdsw); /* XXX - better name */ #endif - for (n = 0; n < NSIG; n++) { - if (recvsig[n] && n != SIGCHLD) { - if (log_io) { - FD_SET(sv[0], fdsw); - break; - } else { - /* nothing listening on sv[0], send directly */ - kill(child, n); - } - } - } - - if (recvsig[SIGCHLD]) - continue; nready = select(maxfd + 1, fdsr, fdsw, NULL, NULL); if (nready == -1) { if (errno == EINTR) continue; error(1, "select failed"); } +#ifdef _PATH_SUDO_IO_LOGDIR + if (FD_ISSET(sv[0], fdsw)) { + forward_signals(sv[0]); + } +#endif /* _PATH_SUDO_IO_LOGDIR */ + if (FD_ISSET(signal_pipe[0], fdsr)) { + n = handle_signals(signal_pipe[0], child, cstat); + if (n == 0) { + /* Child has exited, cstat is set, we are done. */ + goto done; + } + 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 == -1) { if (errno == EINTR) continue; +#ifdef _PATH_SUDO_IO_LOGDIR /* * If not logging I/O we will receive ECONNRESET when * the command is executed. It is safe to ignore this. @@ -328,13 +381,14 @@ sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode) cstat->val = errno; break; } +#endif } #ifdef _PATH_SUDO_IO_LOGDIR /* XXX */ if (cstat->type == CMD_WSTATUS) { if (WIFSTOPPED(cstat->val)) { /* Suspend parent and tell child how to resume on return. */ n = suspend_parent(WSTOPSIG(cstat->val)); - recvsig[n] = TRUE; + schedule_signal(n); continue; } else { /* Child exited or was killed, either way we are done. */ @@ -349,25 +403,12 @@ sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode) } #ifdef _PATH_SUDO_IO_LOGDIR - /* XXX - move this too */ - if (FD_ISSET(sv[0], fdsw)) { - for (n = 0; n < NSIG; n++) { - if (!recvsig[n]) - continue; - recvsig[n] = FALSE; - cstat->type = CMD_SIGNO; - cstat->val = n; - do { - n = send(sv[0], cstat, sizeof(*cstat), 0); - } while (n == -1 && errno == EINTR); - if (n != sizeof(*cstat)) { - recvsig[n] = TRUE; - break; - } - } - } - if (perform_io(fdsr, fdsw, cstat) != 0) + if (perform_io(fdsr, fdsw, cstat) != 0) { + /* I/O error, kill child if still alive and finish. */ + schedule_signal(SIGKILL); + forward_signals(sv[0]); break; + } #endif /* _PATH_SUDO_IO_LOGDIR */ } @@ -386,19 +427,211 @@ sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode) } #endif +done: efree(fdsr); efree(fdsw); +#ifdef _PATH_SUDO_IO_LOGDIR + while (!tq_empty(&sigfwd_list)) { + struct sigforward *sigfwd = tq_first(&sigfwd_list); + tq_remove(&sigfwd_list, sigfwd); + efree(sigfwd); + } +#endif /* _PATH_SUDO_IO_LOGDIR */ return cstat->type == CMD_ERRNO ? -1 : 0; } +/* + * Read signals on fd written to by handler(). + * Returns -1 on error, 0 on child exit, else 1. + */ +static int +handle_signals(fd, child, cstat) + int fd; + pid_t child; + struct command_status *cstat; +{ + unsigned char signo; + ssize_t nread; + int status; + pid_t pid; + + for (;;) { + /* read signal pipe */ + 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) + break; + cstat->type = CMD_ERRNO; + cstat->val = errno; + return -1; + } + if (signo == SIGCHLD) { + /* + * If logging I/O, child is the intermediate process, + * otherwise it is the command itself. + */ + do { +#ifdef sudo_waitpid + pid = sudo_waitpid(child, &status, WUNTRACED|WNOHANG); +#else + pid = wait(&status); +#endif + } while (pid == -1 && errno == EINTR); + if (pid == child) { + /* If not logging I/O and child has exited we are done. */ +#ifdef _PATH_SUDO_IO_LOGDIR + if (!log_io) +#endif + { + if (WIFSTOPPED(status)) { + /* + * Save the controlling terminal's process group + * so we can restore it after we resume. + */ +#ifdef HAVE_TCSETPGRP + pid_t saved_pgrp = (pid_t)-1; + int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0); + if (fd != -1) + saved_pgrp = tcgetpgrp(fd); +#endif /* HAVE_TCSETPGRP */ + if (kill(getpid(), WSTOPSIG(status)) != 0) + warning("kill(%d, %d)", getpid(), WSTOPSIG(status)); +#ifdef HAVE_TCSETPGRP + if (fd != -1) { + if (saved_pgrp != (pid_t)-1) + (void)tcsetpgrp(fd, saved_pgrp); + close(fd); + } +#endif /* HAVE_TCSETPGRP */ + } else { + /* Child has exited, we are done. */ + cstat->type = CMD_WSTATUS; + cstat->val = status; + return 0; + } + } + /* Else we get ECONNRESET on sv[0] if child dies. */ + } + } else { +#ifdef _PATH_SUDO_IO_LOGDIR + if (log_io) { + /* Schedule signo to be forwared to the child. */ + schedule_signal(signo); + } else +#endif + { + /* Nothing listening on sv[0], send directly. */ + if (kill(child, signo) != 0) + warning("kill(%d, %d)", child, signo); + } + } + } + return 1; +} + +#ifdef _PATH_SUDO_IO_LOGDIR +/* + * Forward signals in sigfwd_list to child listening on fd. + */ +static void +forward_signals(sock) + int sock; +{ + struct sigforward *sigfwd; + struct command_status cstat; + ssize_t nsent; + + while (!tq_empty(&sigfwd_list)) { + sigfwd = tq_first(&sigfwd_list); + cstat.type = CMD_SIGNO; + cstat.val = sigfwd->signo; + do { + nsent = send(sock, &cstat, sizeof(cstat), 0); + } while (nsent == -1 && errno == EINTR); + tq_remove(&sigfwd_list, sigfwd); + efree(sigfwd); + if (nsent != sizeof(cstat)) { + if (errno == EPIPE) { + /* Other end of socket gone, empty out sigfwd_list. */ + while (!tq_empty(&sigfwd_list)) { + sigfwd = tq_first(&sigfwd_list); + tq_remove(&sigfwd_list, sigfwd); + efree(sigfwd); + } + } + break; + } + } +} + +/* + * Schedule a signal to be forwared. + */ +static void +schedule_signal(signo) + int signo; +{ + struct sigforward *sigfwd; + + sigfwd = emalloc(sizeof(*sigfwd)); + sigfwd->prev = sigfwd; + sigfwd->next = NULL; + sigfwd->signo = signo; + tq_append(&sigfwd_list, sigfwd); +} +#endif /* _PATH_SUDO_IO_LOGDIR */ + /* * Generic handler for signals passed from parent -> child. - * The recvsig[] array is checked in the main event loop. + * The other end of signal_pipe is checked in the main event loop. */ -void +RETSIGTYPE handler(s) int s; { - recvsig[s] = TRUE; + 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. + */ + if (write(signal_pipe[1], &signo, sizeof(signo)) == -1) + /* shut up glibc */; +} + +/* + * Open a pipe and make both ends non-blocking. + * Returns 0 on success and -1 on error. + */ +int +pipe_nonblock(fds) + int fds[2]; +{ + int flags, rval; + + rval = pipe(fds); + if (rval != -1) { + flags = fcntl(fds[0], F_GETFL, 0); + if (flags != -1 && !ISSET(flags, O_NONBLOCK)) + rval = fcntl(fds[0], F_SETFL, flags | O_NONBLOCK); + if (rval != -1) { + flags = fcntl(fds[1], F_GETFL, 0); + if (flags != -1 && !ISSET(flags, O_NONBLOCK)) + rval = fcntl(fds[1], F_SETFL, flags | O_NONBLOCK); + } + if (rval == -1) { + close(fds[0]); + close(fds[1]); + } + } + + return rval; }