--- embedaddon/sudo/src/exec.c 2012/05/29 12:26:49 1.1.1.2 +++ embedaddon/sudo/src/exec.c 2012/10/09 09:29:52 1.1.1.3 @@ -73,13 +73,16 @@ struct sigforward { }; TQ_DECLARE(sigforward) static struct sigforward_list sigfwd_list; +static pid_t ppgrp = -1; +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,14 +93,37 @@ static int fork_cmnd(struct command_details *details, { struct command_status cstat; sigaction_t sa; - pid_t child; debug_decl(fork_cmnd, SUDO_DEBUG_EXEC) + ppgrp = getpgrp(); /* parent's process group */ + + /* + * 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). + */ 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); +#ifdef SA_SIGINFO + sa.sa_sigaction = handler_user_only; +#endif + sigaction(SIGTSTP, &sa, NULL); /* * The policy plugin's session init must be run before we fork @@ -106,8 +132,8 @@ static int fork_cmnd(struct command_details *details, 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 +176,9 @@ static int fork_cmnd(struct command_details *details, 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, + (int)cmnd_pid); + debug_return_int(cmnd_pid); } static struct signal_state { @@ -216,6 +244,7 @@ sudo_execute(struct command_details *details, struct c 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 +302,12 @@ sudo_execute(struct command_details *details, struct c * 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 +325,7 @@ sudo_execute(struct command_details *details, struct c #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 +340,7 @@ sudo_execute(struct command_details *details, struct c * 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 +433,19 @@ sudo_execute(struct command_details *details, struct c 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, (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, @@ -460,6 +506,7 @@ do_tty_io: static int handle_signals(int sv[2], pid_t child, int log_io, struct command_status *cstat) { + char signame[SIG2STR_MAX]; unsigned char signo; ssize_t nread; int status; @@ -485,7 +532,9 @@ handle_signals(int sv[2], pid_t child, int log_io, str cstat->val = errno; debug_return_int(-1); } - sudo_debug_printf(SUDO_DEBUG_DIAG, "received signal %d", signo); + if (sig2str(signo, signame) == -1) + snprintf(signame, sizeof(signame), "%d", signo); + sudo_debug_printf(SUDO_DEBUG_DIAG, "received SIG%s", signame); if (signo == SIGCHLD) { /* * If logging I/O, child is the intermediate process, @@ -508,17 +557,36 @@ handle_signals(int sv[2], pid_t child, int log_io, str if (WIFSTOPPED(status)) { /* * Save the controlling terminal's process group - * so we can restore it after we resume. + * 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); - if (kill(getpid(), WSTOPSIG(status)) != 0) { - warning("kill(%d, %d)", (int)getpid(), - WSTOPSIG(status)); + if (fd != -1) { + if ((saved_pgrp = tcgetpgrp(fd)) == ppgrp) + saved_pgrp = -1; } + if (signo == SIGTSTP) { + zero_bytes(&sa, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sigaction(SIGTSTP, &sa, &osa); + } + if (kill(getpid(), signo) != 0) + warning("kill(%d, SIG%s)", (int)getpid(), signame); + if (signo == SIGTSTP) + sigaction(SIGTSTP, &osa, NULL); if (fd != -1) { + /* + * Restore command's process group if different. + * Otherwise, we cannot resume some shells. + */ if (saved_pgrp != (pid_t)-1) (void)tcsetpgrp(fd, saved_pgrp); close(fd); @@ -538,9 +606,9 @@ handle_signals(int sv[2], pid_t child, int log_io, str } 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); + warning("kill(%d, SIG%s)", (int)child, signame); } } } @@ -553,6 +621,7 @@ handle_signals(int sv[2], pid_t child, int log_io, str static void forward_signals(int sock) { + char signame[SIG2STR_MAX]; struct sigforward *sigfwd; struct command_status cstat; ssize_t nsent; @@ -560,8 +629,10 @@ forward_signals(int sock) while (!tq_empty(&sigfwd_list)) { sigfwd = tq_first(&sigfwd_list); + if (sig2str(sigfwd->signo, signame) == -1) + snprintf(signame, sizeof(signame), "%d", sigfwd->signo); sudo_debug_printf(SUDO_DEBUG_INFO, - "sending signal %d to child over backchannel", sigfwd->signo); + "sending SIG%s to child over backchannel", signame); cstat.type = CMD_SIGNO; cstat.val = sigfwd->signo; do { @@ -594,9 +665,12 @@ static void schedule_signal(int signo) { struct sigforward *sigfwd; + char signame[SIG2STR_MAX]; debug_decl(schedule_signal, SUDO_DEBUG_EXEC) - sudo_debug_printf(SUDO_DEBUG_DIAG, "forwarding signal %d to child", signo); + if (sig2str(signo, signame) == -1) + snprintf(signame, sizeof(signame), "%d", signo); + sudo_debug_printf(SUDO_DEBUG_DIAG, "forwarding SIG%s to child", signame); sigfwd = ecalloc(1, sizeof(*sigfwd)); sigfwd->prev = sigfwd; @@ -611,7 +685,29 @@ 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) { unsigned char signo = (unsigned char)s; @@ -622,6 +718,7 @@ handler(int s) */ ignore_result(write(signal_pipe[1], &signo, sizeof(signo))); } +#endif #ifdef SA_SIGINFO /* @@ -631,12 +728,12 @@ 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; /* Only forward user-generated signals. */ - if (info == NULL || info->si_code <= 0) { + if (info != NULL && info->si_code == SI_USER) { /* * The pipe is non-blocking, if we overflow the kernel's pipe * buffer we drop the signal. This is not a problem in practice.