Use SUDO_EV_SIGNAL and SUDO_EV_SIGINFO instead of managing the
signal_pipe explicitly.
This commit is contained in:
127
src/exec.c
127
src/exec.c
@@ -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->val = errno;
|
|
||||||
ret = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* Take the first terminal signal. */
|
|
||||||
if (signo == SIGINT || signo == SIGQUIT) {
|
|
||||||
cstat->type = CMD_WSTATUS;
|
cstat->type = CMD_WSTATUS;
|
||||||
cstat->val = signo + 128;
|
cstat->val = signo + 128;
|
||||||
ret = 1;
|
debug_return_bool(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Only stop if we haven't already been terminated. */
|
}
|
||||||
if (signo == SIGTSTP) {
|
if (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.
|
||||||
*/
|
*/
|
||||||
|
if (!sudo_terminated(cstat)) {
|
||||||
exec_cmnd(details, -1);
|
exec_cmnd(details, -1);
|
||||||
cstat->type = CMD_ERRNO;
|
cstat->type = CMD_ERRNO;
|
||||||
cstat->val = 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
|
||||||
|
@@ -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,38 +214,40 @@ 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) {
|
|
||||||
/* It should not be possible to get EOF but just in case. */
|
|
||||||
if (nread == 0)
|
|
||||||
errno = ECONNRESET;
|
|
||||||
if (errno != EINTR && errno != EAGAIN) {
|
|
||||||
sudo_warn(U_("error reading from signal pipe"));
|
|
||||||
sudo_ev_loopbreak(mc->evbase);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/*
|
/*
|
||||||
* Handle SIGCHLD specially and deliver other signals
|
* Handle SIGCHLD specially and deliver other signals
|
||||||
* directly to the command.
|
* directly to the command.
|
||||||
*/
|
*/
|
||||||
if (signo == SIGCHLD) {
|
if (signo == SIGCHLD) {
|
||||||
mon_handle_sigchld(mc->backchannel, mc->cstat);
|
mon_handle_sigchld(mc);
|
||||||
if (cmnd_pid == -1) {
|
if (mc->cmnd_pid == -1) {
|
||||||
/* Remove all but the errpipe event. */
|
/* Command exited or was killed, exit event loop. */
|
||||||
sudo_ev_del(mc->evbase, mc->backchannel_event);
|
sudo_ev_loopexit(mc->evbase);
|
||||||
sudo_ev_del(mc->evbase, mc->signal_pipe_event);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deliver_signal(cmnd_pid, signo, false);
|
/*
|
||||||
|
* 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 (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 == mc->cmnd_pgrp)
|
||||||
|
debug_return;
|
||||||
|
} else if (sc->siginfo->si_pid == mc->cmnd_pid) {
|
||||||
|
debug_return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
} while (pid == -1 && errno == EINTR);
|
||||||
|
/* XXX - update cstat with wait status? */
|
||||||
|
}
|
||||||
/* Send parent status. */
|
/* Send parent status. */
|
||||||
send_status(backchannel, &cstat);
|
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);
|
||||||
}
|
}
|
||||||
|
473
src/exec_nopty.c
473
src/exec_nopty.c
@@ -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 */
|
|
||||||
|
839
src/exec_pty.c
839
src/exec_pty.c
File diff suppressed because it is too large
Load Diff
39
src/signal.c
39
src/signal.c
@@ -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;
|
||||||
|
@@ -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);
|
||||||
|
@@ -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,
|
||||||
|
Reference in New Issue
Block a user