Use SUDO_EV_SIGNAL and SUDO_EV_SIGINFO instead of managing the

signal_pipe explicitly.
This commit is contained in:
Todd C. Miller
2017-05-12 10:02:17 -06:00
parent 9d4a92b9b4
commit 1186f39842
7 changed files with 980 additions and 1010 deletions

View File

@@ -36,61 +36,6 @@
#include "sudo_plugin.h" #include "sudo_plugin.h"
#include "sudo_plugin_int.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. * Setup the execution environment and execute the command.
* If SELinux is enabled, run the command via sesh, otherwise * 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(). * Check for caught signals sent to sudo before command execution.
* Handles the case where the signal was sent to us before * Also suspends the process if SIGTSTP was caught.
* we have executed the command. * Returns true if we should terminate, else false.
* Returns 1 if we should terminate, else 0.
*/ */
static int bool
dispatch_pending_signals(struct command_status *cstat) sudo_terminated(struct command_status *cstat)
{ {
ssize_t nread; int signo;
struct sigaction sa; bool sigtstp = false;
unsigned char signo = 0; debug_decl(sudo_terminated, SUDO_DEBUG_EXEC)
int ret = 0;
debug_decl(dispatch_pending_signals, SUDO_DEBUG_EXEC)
for (;;) { for (signo = 0; signo < NSIG; signo++) {
nread = read(signal_pipe[0], &signo, sizeof(signo)); if (signal_pending(signo)) {
if (nread <= 0) { switch (signo) {
/* It should not be possible to get EOF but just in case. */ case SIGTSTP:
if (nread == 0) /* Suspend below if not terminated. */
errno = ECONNRESET; sigtstp = true;
/* 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; break;
sudo_debug_printf(SUDO_DEBUG_ERROR, "error reading signal pipe %s", default:
strerror(errno)); /* Terminal signal, do not exec command. */
cstat->type = CMD_ERRNO; cstat->type = CMD_WSTATUS;
cstat->val = errno; cstat->val = signo + 128;
ret = 1; debug_return_bool(true);
break; break;
} }
/* Take the first terminal signal. */
if (signo == SIGINT || signo == SIGQUIT) {
cstat->type = CMD_WSTATUS;
cstat->val = signo + 128;
ret = 1;
break;
} }
} }
/* Only stop if we haven't already been terminated. */ if (sigtstp) {
if (signo == SIGTSTP) { struct sigaction sa;
sigset_t set, oset;
/* Send SIGTSTP to ourselves, unblocking it if needed. */
memset(&sa, 0, sizeof(sa)); memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_DFL; sa.sa_handler = SIG_DFL;
if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0) if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP); 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) if (kill(getpid(), SIGTSTP) != 0)
sudo_warn("kill(%d, SIGTSTP)", (int)getpid()); 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) 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 running in background mode, fork and exit. */
if (ISSET(details->flags, CD_BACKGROUND)) { if (ISSET(details->flags, CD_BACKGROUND)) {
switch (sudo_debug_fork()) { 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 * as sudoedit, there is no command timeout and there is no close
* function, just exec directly. Only returns on error. * function, just exec directly. Only returns on error.
*/ */
exec_cmnd(details, -1); if (!sudo_terminated(cstat)) {
cstat->type = CMD_ERRNO; exec_cmnd(details, -1);
cstat->val = errno; cstat->type = CMD_ERRNO;
cstat->val = errno;
}
} else { } else {
/* /*
* No pty but we need to wait for the command to finish to * No pty but we need to wait for the command to finish to

View File

@@ -40,71 +40,24 @@
#include "sudo_plugin_int.h" #include "sudo_plugin_int.h"
struct monitor_closure { 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_base *evbase;
struct sudo_event *errpipe_event; struct sudo_event *errpipe_event;
struct sudo_event *backchannel_event; struct sudo_event *backchannel_event;
struct sudo_event *signal_pipe_event; struct sudo_event *sigint_event;
struct command_status *cstat; struct sudo_event *sigquit_event;
int backchannel; 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. * Deliver a signal to the running command.
* The signal was either forwarded to us by the parent sudo process * 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. * also specify whether the command should have the controlling tty.
*/ */
static void 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]; char signame[SIG2STR_MAX];
int status; int status;
debug_decl(deliver_signal, SUDO_DEBUG_EXEC); debug_decl(deliver_signal, SUDO_DEBUG_EXEC);
/* Avoid killing more than a single process or process group. */ /* Avoid killing more than a single process or process group. */
if (pid <= 0) if (mc->cmnd_pid <= 0)
debug_return; debug_return;
if (signo == SIGCONT_FG) if (signo == SIGCONT_FG)
@@ -136,28 +89,28 @@ deliver_signal(pid_t pid, int signo, bool from_parent)
signame, from_parent ? " from parent" : ""); signame, from_parent ? " from parent" : "");
switch (signo) { switch (signo) {
case SIGALRM: case SIGALRM:
terminate_command(pid, true); terminate_command(mc->cmnd_pid, true);
break; break;
case SIGCONT_FG: case SIGCONT_FG:
/* Continue in foreground, grant it controlling tty. */ /* Continue in foreground, grant it controlling tty. */
do { do {
status = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp); status = tcsetpgrp(io_fds[SFD_SLAVE], mc->cmnd_pgrp);
} while (status == -1 && errno == EINTR); } while (status == -1 && errno == EINTR);
killpg(pid, SIGCONT); killpg(mc->cmnd_pid, SIGCONT);
break; break;
case SIGCONT_BG: case SIGCONT_BG:
/* Continue in background, I take controlling tty. */ /* Continue in background, I take controlling tty. */
do { do {
status = tcsetpgrp(io_fds[SFD_SLAVE], mon_pgrp); status = tcsetpgrp(io_fds[SFD_SLAVE], mc->mon_pgrp);
} while (status == -1 && errno == EINTR); } while (status == -1 && errno == EINTR);
killpg(pid, SIGCONT); killpg(mc->cmnd_pid, SIGCONT);
break; break;
case SIGKILL: case SIGKILL:
_exit(1); /* XXX */ _exit(1); /* XXX */
/* NOTREACHED */ /* NOTREACHED */
default: default:
/* Relay signal to command. */ /* Relay signal to command. */
killpg(pid, signo); killpg(mc->cmnd_pid, signo);
break; break;
} }
debug_return; debug_return;
@@ -195,7 +148,7 @@ send_status(int fd, struct command_status *cstat)
* Otherwise, cstat is filled in but not sent. * Otherwise, cstat is filled in but not sent.
*/ */
static void static void
mon_handle_sigchld(int backchannel, struct command_status *cstat) mon_handle_sigchld(struct monitor_closure *mc)
{ {
char signame[SIG2STR_MAX]; char signame[SIG2STR_MAX];
int status; int status;
@@ -204,7 +157,7 @@ mon_handle_sigchld(int backchannel, struct command_status *cstat)
/* Read command status. */ /* Read command status. */
do { do {
pid = waitpid(cmnd_pid, &status, WUNTRACED|WCONTINUED|WNOHANG); pid = waitpid(mc->cmnd_pid, &status, WUNTRACED|WCONTINUED|WNOHANG);
} while (pid == -1 && errno == EINTR); } while (pid == -1 && errno == EINTR);
switch (pid) { switch (pid) {
case 0: case 0:
@@ -217,43 +170,43 @@ mon_handle_sigchld(int backchannel, struct command_status *cstat)
if (WIFCONTINUED(status)) { if (WIFCONTINUED(status)) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) resumed", sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) resumed",
__func__, (int)cmnd_pid); __func__, (int)mc->cmnd_pid);
} else if (WIFSTOPPED(status)) { } else if (WIFSTOPPED(status)) {
if (sig2str(WSTOPSIG(status), signame) == -1) if (sig2str(WSTOPSIG(status), signame) == -1)
snprintf(signame, sizeof(signame), "%d", WSTOPSIG(status)); snprintf(signame, sizeof(signame), "%d", WSTOPSIG(status));
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s", 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)) { } else if (WIFSIGNALED(status)) {
if (sig2str(WTERMSIG(status), signame) == -1) if (sig2str(WTERMSIG(status), signame) == -1)
snprintf(signame, sizeof(signame), "%d", WTERMSIG(status)); snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s", sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
__func__, (int)cmnd_pid, signame); __func__, (int)mc->cmnd_pid, signame);
cmnd_pid = -1; mc->cmnd_pid = -1;
} else if (WIFEXITED(status)) { } else if (WIFEXITED(status)) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d", sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
__func__, (int)cmnd_pid, WEXITSTATUS(status)); __func__, (int)mc->cmnd_pid, WEXITSTATUS(status));
cmnd_pid = -1; mc->cmnd_pid = -1;
} else { } else {
sudo_debug_printf(SUDO_DEBUG_WARN, sudo_debug_printf(SUDO_DEBUG_WARN,
"%s: unexpected wait status %d for command (%d)", "%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. */ /* 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. * Store wait status in cstat and forward to parent if stopped.
*/ */
cstat->type = CMD_WSTATUS; mc->cstat->type = CMD_WSTATUS;
cstat->val = status; mc->cstat->val = status;
if (WIFSTOPPED(status)) { if (WIFSTOPPED(status)) {
/* Save the foreground pgid so we can restore it later. */ /* Save the foreground pgid so we can restore it later. */
do { do {
pid = tcgetpgrp(io_fds[SFD_SLAVE]); pid = tcgetpgrp(io_fds[SFD_SLAVE]);
} while (pid == -1 && errno == EINTR); } while (pid == -1 && errno == EINTR);
if (pid != mon_pgrp) if (pid != mc->mon_pgrp)
cmnd_pgrp = pid; mc->cmnd_pgrp = pid;
send_status(backchannel, cstat); send_status(mc->backchannel, mc->cstat);
} }
} }
@@ -261,37 +214,39 @@ mon_handle_sigchld(int backchannel, struct command_status *cstat)
} }
static void 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; struct sudo_ev_siginfo_container *sc = v;
unsigned char signo; struct monitor_closure *mc = sc->closure;
ssize_t nread; debug_decl(mon_signal_cb, SUDO_DEBUG_EXEC);
debug_decl(mon_signal_pipe_cb, SUDO_DEBUG_EXEC);
nread = read(fd, &signo, sizeof(signo)); /*
if (nread <= 0) { * Handle SIGCHLD specially and deliver other signals
/* It should not be possible to get EOF but just in case. */ * directly to the command.
if (nread == 0) */
errno = ECONNRESET; if (signo == SIGCHLD) {
if (errno != EINTR && errno != EAGAIN) { mon_handle_sigchld(mc);
sudo_warn(U_("error reading from signal pipe")); if (mc->cmnd_pid == -1) {
sudo_ev_loopbreak(mc->evbase); /* Command exited or was killed, exit event loop. */
sudo_ev_loopexit(mc->evbase);
} }
} else { } else {
/* /*
* Handle SIGCHLD specially and deliver other signals * If the signal came from the process group of the command we ran,
* directly to the command. * 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) { if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
mon_handle_sigchld(mc->backchannel, mc->cstat); pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
if (cmnd_pid == -1) { if (si_pgrp != -1) {
/* Remove all but the errpipe event. */ if (si_pgrp == mc->cmnd_pgrp)
sudo_ev_del(mc->evbase, mc->backchannel_event); debug_return;
sudo_ev_del(mc->evbase, mc->signal_pipe_event); } 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; debug_return;
} }
@@ -352,7 +307,10 @@ mon_backchannel_cb(int fd, int what, void *v)
ssize_t n; ssize_t n;
debug_decl(mon_backchannel_cb, SUDO_DEBUG_EXEC); 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); n = recv(fd, &cstmp, sizeof(cstmp), MSG_WAITALL);
if (n != sizeof(cstmp)) { if (n != sizeof(cstmp)) {
if (n == -1) { if (n == -1) {
@@ -362,10 +320,13 @@ mon_backchannel_cb(int fd, int what, void *v)
} else { } else {
/* short read or EOF, parent process died? */ /* 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); sudo_ev_loopbreak(mc->evbase);
} else { } else {
if (cstmp.type == CMD_SIGNO) { if (cstmp.type == CMD_SIGNO) {
deliver_signal(cmnd_pid, cstmp.val, true); deliver_signal(mc, cstmp.val, true);
} else { } else {
sudo_warnx(U_("unexpected reply type on backchannel: %d"), cstmp.type); 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); debug_decl(fill_exec_closure_monitor, SUDO_DEBUG_EXEC);
/* Fill in the non-event part of the closure. */ /* Fill in the non-event part of the closure. */
cstat->type = CMD_INVALID;
cstat->val = 0;
mc->cstat = cstat; mc->cstat = cstat;
mc->backchannel = backchannel; mc->backchannel = backchannel;
mc->mon_pgrp = getpgrp();
/* Setup event base and events. */ /* Setup event base and events. */
mc->evbase = sudo_ev_base_alloc(); mc->evbase = sudo_ev_base_alloc();
if (mc->evbase == NULL) if (mc->evbase == NULL)
sudo_fatal(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. */ /* Event for command status via errfd. */
mc->errpipe_event = sudo_ev_alloc(errfd, mc->errpipe_event = sudo_ev_alloc(errfd,
SUDO_EV_READ|SUDO_EV_PERSIST, mon_errpipe_cb, mc); 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); sudo_fatal(NULL);
if (sudo_ev_add(mc->evbase, mc->backchannel_event, NULL, false) == -1) if (sudo_ev_add(mc->evbase, mc->backchannel_event, NULL, false) == -1)
sudo_fatal(U_("unable to add event to queue")); 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(). * resets signal handlers and forks a child to call exec_cmnd_pty().
* Waits for status changes from the command and relays them to the * Waits for status changes from the command and relays them to the
* parent and relays signals from the parent to the command. * 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). * Returns an error if fork(2) fails, else calls _exit(2).
*/ */
int 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 command_status cstat;
struct monitor_closure mc;
sigaction_t sa; sigaction_t sa;
int errpipe[2], n; int errpipe[2];
debug_decl(exec_monitor, SUDO_DEBUG_EXEC); debug_decl(exec_monitor, SUDO_DEBUG_EXEC);
/* Close unused fds. */ /* Close unused fds. */
@@ -494,67 +505,16 @@ exec_monitor(struct command_details *details, bool foreground, int backchannel)
if (io_fds[SFD_USERTTY] != -1) if (io_fds[SFD_USERTTY] != -1)
close(io_fds[SFD_USERTTY]); close(io_fds[SFD_USERTTY]);
/* /* Ignore any SIGTTIN or SIGTTOU we receive (shouldn't be possible). */
* 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. */
memset(&sa, 0, sizeof(sa)); memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; 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; sa.sa_handler = SIG_IGN;
if (sudo_sigaction(SIGTTIN, &sa, NULL) != 0) if (sudo_sigaction(SIGTTIN, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTTIN); sudo_warn(U_("unable to set handler for signal %d"), SIGTTIN);
if (sudo_sigaction(SIGTTOU, &sa, NULL) != 0) if (sudo_sigaction(SIGTTOU, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTTOU); 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 * Start a new session with the parent as the session leader
* and the slave pty as the controlling terminal. * and the slave pty as the controlling terminal.
@@ -569,21 +529,21 @@ exec_monitor(struct command_details *details, bool foreground, int backchannel)
goto bad; goto bad;
} }
mon_pgrp = getpgrp(); /* save a copy of our process group */ /*
* We use a pipe to get errno if execve(2) fails in the child.
/* Start command and wait for it to stop or exit */ */
if (pipe2(errpipe, O_CLOEXEC) == -1) if (pipe2(errpipe, O_CLOEXEC) != 0)
sudo_fatal(U_("unable to create pipe")); 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")); sudo_warn(U_("unable to fork"));
goto bad; goto bad;
} case 0:
if (cmnd_pid == 0) { /* child */
/* We pass errno back to our parent via pipe on exec failure. */ sigprocmask(SIG_SETMASK, oset, NULL);
close(backchannel); close(backchannel);
close(signal_pipe[0]);
close(signal_pipe[1]);
close(errpipe[0]); close(errpipe[0]);
restore_signals(); 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. */ /* Send the command's pid to main sudo process. */
cstat.type = CMD_PID; cstat.type = CMD_PID;
cstat.val = cmnd_pid; cstat.val = mc.cmnd_pid;
while (send(backchannel, &cstat, sizeof(cstat), 0) == -1) { send_status(backchannel, &cstat);
if (errno != EINTR)
break; /*
} * 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 any of stdin/stdout/stderr are pipes, close them in parent. */
if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE]) 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]); close(io_fds[SFD_STDERR]);
/* Put command in its own process group. */ /* Put command in its own process group. */
cmnd_pgrp = cmnd_pid; mc.cmnd_pgrp = mc.cmnd_pid;
setpgid(cmnd_pid, cmnd_pgrp); setpgid(mc.cmnd_pid, mc.cmnd_pgrp);
/* Make the command the foreground process for the pty slave. */ /* Make the command the foreground process for the pty slave. */
if (foreground && !ISSET(details->flags, CD_EXEC_BG)) { if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
int n;
do { do {
n = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp); n = tcsetpgrp(io_fds[SFD_SLAVE], mc.cmnd_pgrp);
} while (n == -1 && errno == EINTR); } 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. * Wait for errno on pipe, signal on backchannel or for SIGCHLD.
* The event loop ends when the child is no longer running and * The event loop ends when the child is no longer running and
* the error pipe is closed. * the error pipe is closed.
*/ */
cstat.type = CMD_INVALID;
cstat.val = 0;
(void) sudo_ev_loop(mc.evbase, 0); (void) sudo_ev_loop(mc.evbase, 0);
if (cmnd_pid != -1) { if (mc.cmnd_pid != -1) {
/* XXX An error occurred, should send a message back. */ pid_t pid;
/* Command still running, did the parent die? */
sudo_debug_printf(SUDO_DEBUG_ERROR, sudo_debug_printf(SUDO_DEBUG_ERROR,
"Command still running after event loop exit, sending SIGKILL"); "Command still running after event loop exit, terminating");
kill(cmnd_pid, SIGKILL); terminate_command(mc.cmnd_pid, true);
/* XXX - wait for cmnd_pid to exit */ do {
} else { pid = waitpid(mc.cmnd_pid, NULL, 0);
/* Send parent status. */ } while (pid == -1 && errno == EINTR);
send_status(backchannel, &cstat); /* XXX - update cstat with wait status? */
} }
/* Send parent status. */
send_status(backchannel, &cstat);
#ifdef HAVE_SELINUX #ifdef HAVE_SELINUX
if (ISSET(details->flags, CD_RBAC_ENABLED)) { if (ISSET(details->flags, CD_RBAC_ENABLED)) {
@@ -663,5 +631,5 @@ exec_monitor(struct command_details *details, bool foreground, int backchannel)
_exit(1); _exit(1);
bad: bad:
debug_return_int(errno); debug_return_int(-1);
} }

View File

@@ -38,18 +38,27 @@
#include "sudo_plugin_int.h" #include "sudo_plugin_int.h"
struct exec_closure_nopty { struct exec_closure_nopty {
pid_t child; pid_t cmnd_pid;
pid_t ppgrp;
struct command_status *cstat; struct command_status *cstat;
struct command_details *details; struct command_details *details;
struct sudo_event_base *evbase; struct sudo_event_base *evbase;
struct sudo_event *signal_event;
struct sudo_event *errpipe_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); static void handle_sigchld_nopty(struct exec_closure_nopty *ec);
#ifdef SA_SIGINFO
static void exec_handler_user_only(int s, siginfo_t *info, void *context);
#endif
/* Note: this is basically the same as mon_errpipe_cb() in exec_monitor.c */ /* Note: this is basically the same as mon_errpipe_cb() in exec_monitor.c */
static void static void
@@ -99,6 +108,84 @@ errpipe_cb(int fd, int what, void *v)
debug_return; 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. * Fill in the exec closure and setup initial exec events.
* Allocates events for the signal pipe and error pipe. * 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) debug_decl(fill_exec_closure_nopty, SUDO_DEBUG_EXEC)
/* Fill in the non-event part of the closure. */ /* Fill in the non-event part of the closure. */
ec->child = cmnd_pid; ec->ppgrp = getpgrp();
ec->cstat = cstat; ec->cstat = cstat;
ec->details = details; ec->details = details;
@@ -119,14 +206,6 @@ fill_exec_closure_nopty(struct exec_closure_nopty *ec,
if (ec->evbase == NULL) if (ec->evbase == NULL)
sudo_fatal(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. */ /* Event for command status via errfd. */
ec->errpipe_event = sudo_ev_alloc(errfd, ec->errpipe_event = sudo_ev_alloc(errfd,
SUDO_EV_READ|SUDO_EV_PERSIST, errpipe_cb, ec); 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); sudo_fatal(NULL);
if (sudo_ev_add(ec->evbase, ec->errpipe_event, NULL, false) == -1) if (sudo_ev_add(ec->evbase, ec->errpipe_event, NULL, false) == -1)
sudo_fatal(U_("unable to add event to queue")); 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); 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; debug_return;
} }
@@ -147,82 +337,11 @@ fill_exec_closure_nopty(struct exec_closure_nopty *ec,
int int
exec_nopty(struct command_details *details, struct command_status *cstat) exec_nopty(struct command_details *details, struct command_status *cstat)
{ {
struct exec_closure_nopty ec; struct exec_closure_nopty ec = { 0 };
sigaction_t sa; sigset_t set, oset;
int errpipe[2]; int errpipe[2];
debug_decl(exec_nopty, SUDO_DEBUG_EXEC) 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).
*/
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
/*
* 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).
*/
#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);
/* /*
* The policy plugin's session init must be run before we fork * The policy plugin's session init must be run before we fork
* or certain pam modules won't be able to track their state. * or certain pam modules won't be able to track their state.
@@ -230,18 +349,34 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
if (policy_init_session(details) != true) if (policy_init_session(details) != true)
sudo_fatalx(U_("policy plugin failed session initialization")); sudo_fatalx(U_("policy plugin failed session initialization"));
ppgrp = getpgrp(); /* parent's process group */ /*
* 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(); /*
switch (cmnd_pid) { * 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.cmnd_pid = sudo_debug_fork();
switch (ec.cmnd_pid) {
case -1: case -1:
sudo_fatal(U_("unable to fork")); sudo_fatal(U_("unable to fork"));
break; break;
case 0: case 0:
/* child */ /* child */
sigprocmask(SIG_SETMASK, &oset, NULL);
close(errpipe[0]); close(errpipe[0]);
close(signal_pipe[0]);
close(signal_pipe[1]);
exec_cmnd(details, errpipe[1]); exec_cmnd(details, errpipe[1]);
while (write(errpipe[1], &errno, sizeof(int)) == -1) { while (write(errpipe[1], &errno, sizeof(int)) == -1) {
if (errno != EINTR) if (errno != EINTR)
@@ -251,7 +386,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
_exit(1); _exit(1);
} }
sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command, sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
(int)cmnd_pid); (int)ec.cmnd_pid);
close(errpipe[1]); close(errpipe[1]);
/* No longer need execfd. */ /* No longer need execfd. */
@@ -265,11 +400,14 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
alarm(details->timeout); alarm(details->timeout);
/* /*
* Fill in exec closure, allocate event base and two persistent events: * Fill in exec closure, allocate event base, signal events and
* the signal pipe and the error pipe. * the error pipe event.
*/ */
fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]); 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. * Non-pty event loop.
* Wait for command to exit, handles signals and the error pipe. * 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 */ /* error from callback */
sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely"); sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
/* kill command */ /* kill command */
terminate_command(ec.child, true); terminate_command(ec.cmnd_pid, true);
} }
#ifdef HAVE_SELINUX #ifdef HAVE_SELINUX
@@ -291,9 +429,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
#endif #endif
/* Free things up. */ /* Free things up. */
sudo_ev_base_free(ec.evbase); free_exec_closure_nopty(&ec);
sudo_ev_free(ec.signal_event);
sudo_ev_free(ec.errpipe_event);
debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0); debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);
} }
@@ -313,7 +449,7 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
/* Read command status. */ /* Read command status. */
do { do {
pid = waitpid(ec->child, &status, WUNTRACED|WNOHANG); pid = waitpid(ec->cmnd_pid, &status, WUNTRACED|WNOHANG);
} while (pid == -1 && errno == EINTR); } while (pid == -1 && errno == EINTR);
switch (pid) { switch (pid) {
case 0: 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 * Save the controlling terminal's process group so we can restore it
* after we resume, if needed. Most well-behaved shells change the * after we resume, if needed. Most well-behaved shells change the
* pgrp back to its original value before suspending so we must * 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 * resume, potentially stopping sudo with SIGTTOU while the command
* continues to run. * continues to run.
*/ */
@@ -340,7 +476,7 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
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_INFO, "%s: command (%d) stopped, SIG%s", 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); fd = open(_PATH_TTY, O_RDWR);
if (fd != -1) { if (fd != -1) {
@@ -352,18 +488,18 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
} }
if (saved_pgrp != -1) { if (saved_pgrp != -1) {
/* /*
* Child was stopped trying to access the controlling terminal. * Command was stopped trying to access the controlling terminal.
* If the child has a different pgrp and we own the controlling * If the command has a different pgrp and we own the controlling
* terminal, give it to the child's pgrp and let it continue. * terminal, give it to the command's pgrp and let it continue.
*/ */
if (signo == SIGTTOU || signo == SIGTTIN) { if (signo == SIGTTOU || signo == SIGTTIN) {
if (saved_pgrp == ppgrp) { if (saved_pgrp == ec->ppgrp) {
pid_t child_pgrp = getpgid(ec->child); pid_t cmnd_pgrp = getpgid(ec->cmnd_pid);
if (child_pgrp != ppgrp) { if (cmnd_pgrp != ec->ppgrp) {
if (tcsetpgrp_nobg(fd, child_pgrp) == 0) { if (tcsetpgrp_nobg(fd, cmnd_pgrp) == 0) {
if (killpg(child_pgrp, SIGCONT) != 0) { if (killpg(cmnd_pgrp, SIGCONT) != 0) {
sudo_warn("kill(%d, SIGCONT)", sudo_warn("kill(%d, SIGCONT)",
(int)child_pgrp); (int)cmnd_pgrp);
} }
close(fd); close(fd);
goto done; 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 * It is possible that we are no longer the foreground process so
* use tcsetpgrp_nobg() to prevent sudo from receiving SIGTTOU. * use tcsetpgrp_nobg() to prevent sudo from receiving SIGTTOU.
*/ */
if (saved_pgrp != ppgrp) if (saved_pgrp != ec->ppgrp)
tcsetpgrp_nobg(fd, saved_pgrp); tcsetpgrp_nobg(fd, saved_pgrp);
close(fd); close(fd);
} }
} else { } else {
/* Child has exited or been killed, we are done. */ /* Command has exited or been killed, we are done. */
if (WIFSIGNALED(status)) { if (WIFSIGNALED(status)) {
if (sig2str(WTERMSIG(status), signame) == -1) if (sig2str(WTERMSIG(status), signame) == -1)
snprintf(signame, sizeof(signame), "%d", WTERMSIG(status)); snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s", sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
__func__, (int)ec->child, signame); __func__, (int)ec->cmnd_pid, signame);
} else { } else {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d", 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) { if (ec->cstat->type != CMD_ERRNO) {
ec->cstat->type = CMD_WSTATUS; ec->cstat->type = CMD_WSTATUS;
ec->cstat->val = status; ec->cstat->val = status;
} }
ec->child = -1; ec->cmnd_pid = -1;
} }
done: done:
debug_return; 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 */

File diff suppressed because it is too large Load Diff

View File

@@ -33,8 +33,6 @@
#include "sudo.h" #include "sudo.h"
#include "sudo_exec.h" #include "sudo_exec.h"
int signal_pipe[2];
static struct signal_state { static struct signal_state {
int signo; int signo;
int restore; int restore;
@@ -56,6 +54,21 @@ static struct signal_state {
{ -1 } { -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. * Save signal handler state so it can be restored before exec.
*/ */
@@ -94,21 +107,6 @@ restore_signals(void)
debug_return; 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 * Trap tty-generated (and other) signals so we can't be killed before
* calling the policy close function. The signal pipe will be drained * calling the policy close function. The signal pipe will be drained
@@ -122,13 +120,6 @@ init_signals(void)
struct signal_state *ss; struct signal_state *ss;
debug_decl(init_signals, SUDO_DEBUG_MAIN) 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)); memset(&sa, 0, sizeof(sa));
sigfillset(&sa.sa_mask); sigfillset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; sa.sa_flags = SA_RESTART;

View File

@@ -256,11 +256,11 @@ char *get_process_ttyname(char *name, size_t namelen);
/* signal.c */ /* signal.c */
struct sigaction; struct sigaction;
extern int signal_pipe[2];
int sudo_sigaction(int signo, struct sigaction *sa, struct sigaction *osa); int sudo_sigaction(int signo, struct sigaction *sa, struct sigaction *osa);
void init_signals(void); void init_signals(void);
void restore_signals(void); void restore_signals(void);
void save_signals(void); void save_signals(void);
bool signal_pending(int signo);
/* preload.c */ /* preload.c */
void preload_static_symbols(void); void preload_static_symbols(void);

View File

@@ -27,12 +27,10 @@
/* /*
* Some older systems support siginfo but predate SI_USER. * Some older systems support siginfo but predate SI_USER.
*/ */
#ifdef SA_SIGINFO #ifdef SI_USER
# ifdef SI_USER # define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code == SI_USER)
# define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code == SI_USER) #else
# else # define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code <= 0)
# define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code <= 0)
# endif
#endif #endif
/* /*
@@ -85,14 +83,9 @@ struct command_details;
struct command_status; struct command_status;
/* exec.c */ /* exec.c */
extern volatile pid_t cmnd_pid, ppgrp;
void exec_cmnd(struct command_details *details, int errfd); void exec_cmnd(struct command_details *details, int errfd);
void terminate_command(pid_t pid, bool use_pgrp); void terminate_command(pid_t pid, bool use_pgrp);
#ifdef SA_SIGINFO bool sudo_terminated(struct command_status *cstat);
void exec_handler(int s, siginfo_t *info, void *context);
#else
void exec_handler(int s);
#endif
/* exec_common.c */ /* exec_common.c */
int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec); 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); int exec_pty(struct command_details *details, struct command_status *cstat);
void pty_cleanup(void); void pty_cleanup(void);
int pty_make_controlling(void); int pty_make_controlling(void);
extern int io_fds[6];
/* exec_monitor.c */ /* 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 */ /* utmp.c */
bool utmp_login(const char *from_line, const char *to_line, int ttyfd, bool utmp_login(const char *from_line, const char *to_line, int ttyfd,