#include <config.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sudo.h>
#include <sudo_exec.h>
#include <sudo_plugin.h>
#include <sudo_plugin_int.h>
struct monitor_closure {
const struct command_details *details;
struct sudo_event_base *evbase;
struct sudo_event *errsock_event;
struct sudo_event *backchannel_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 *sigusr1_event;
struct sudo_event *sigusr2_event;
struct sudo_event *sigchld_event;
struct command_status *cstat;
pid_t cmnd_pid;
pid_t cmnd_pgrp;
pid_t mon_pgrp;
int backchannel;
};
static void
deliver_signal(struct monitor_closure *mc, int signo, bool from_parent)
{
debug_decl(deliver_signal, SUDO_DEBUG_EXEC);
if (mc->cmnd_pid <= 0)
debug_return;
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
char signame[SIG2STR_MAX];
if (signo == SIGCONT_FG)
(void)strlcpy(signame, "CONT_FG", sizeof(signame));
else if (signo == SIGCONT_BG)
(void)strlcpy(signame, "CONT_BG", sizeof(signame));
else if (sig2str(signo, signame) == -1)
(void)snprintf(signame, sizeof(signame), "%d", signo);
sudo_debug_printf(SUDO_DEBUG_INFO, "received SIG%s%s",
signame, from_parent ? " from parent" : "");
}
switch (signo) {
case SIGALRM:
terminate_command(mc->cmnd_pid, true);
break;
case SIGCONT_FG:
if (tcsetpgrp(io_fds[SFD_FOLLOWER], mc->cmnd_pgrp) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to set foreground pgrp to %d (command)",
__func__, (int)mc->cmnd_pgrp);
}
killpg(mc->cmnd_pid, SIGCONT);
break;
case SIGCONT_BG:
if (tcsetpgrp(io_fds[SFD_FOLLOWER], mc->mon_pgrp) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to set foreground pgrp to %d (monitor)",
__func__, (int)mc->mon_pgrp);
}
killpg(mc->cmnd_pid, SIGCONT);
break;
case SIGKILL:
_exit(EXIT_FAILURE);
default:
sudo_debug_printf(SUDO_DEBUG_NOTICE, "%s: killpg(%d, %d)",
__func__, (int)mc->cmnd_pid, signo);
killpg(mc->cmnd_pid, signo);
break;
}
debug_return;
}
static ssize_t
send_status(int fd, struct command_status *cstat)
{
ssize_t n = -1;
debug_decl(send_status, SUDO_DEBUG_EXEC);
if (cstat->type != CMD_INVALID) {
sudo_debug_printf(SUDO_DEBUG_INFO,
"sending status message to parent: [%d, %d]",
cstat->type, cstat->val);
n = send(fd, cstat, sizeof(*cstat), 0);
if (n != ssizeof(*cstat)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to send status to parent", __func__);
}
cstat->type = CMD_INVALID;
}
debug_return_ssize_t(n);
}
static void
mon_handle_sigchld(struct monitor_closure *mc)
{
char signame[SIG2STR_MAX];
int status;
pid_t pid;
debug_decl(mon_handle_sigchld, SUDO_DEBUG_EXEC);
do {
pid = waitpid(mc->cmnd_pid, &status, WUNTRACED|WNOHANG);
} while (pid == -1 && errno == EINTR);
switch (pid) {
case -1:
if (errno != ECHILD) {
sudo_warn(U_("%s: %s"), __func__, "waitpid");
debug_return;
}
FALLTHROUGH;
case 0:
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: no process to wait for",
__func__);
debug_return;
}
if (WIFSTOPPED(status)) {
if (sig2str(WSTOPSIG(status), signame) == -1)
(void)snprintf(signame, sizeof(signame), "%d", WSTOPSIG(status));
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s",
__func__, (int)mc->cmnd_pid, signame);
} else if (WIFSIGNALED(status)) {
if (sig2str(WTERMSIG(status), signame) == -1)
(void)snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
__func__, (int)mc->cmnd_pid, signame);
mc->cmnd_pid = -1;
} else if (WIFEXITED(status)) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
__func__, (int)mc->cmnd_pid, WEXITSTATUS(status));
mc->cmnd_pid = -1;
} else {
sudo_debug_printf(SUDO_DEBUG_WARN,
"%s: unexpected wait status 0x%x for command (%d)",
__func__, status, (int)mc->cmnd_pid);
}
if (mc->cstat->type == CMD_INVALID) {
mc->cstat->type = CMD_WSTATUS;
mc->cstat->val = status;
if (WIFSTOPPED(status)) {
pid = tcgetpgrp(io_fds[SFD_FOLLOWER]);
if (pid != mc->mon_pgrp)
mc->cmnd_pgrp = pid;
send_status(mc->backchannel, mc->cstat);
}
} else {
sudo_debug_printf(SUDO_DEBUG_WARN,
"%s: not overwriting command status %d,%d with %d,%d",
__func__, mc->cstat->type, mc->cstat->val, CMD_WSTATUS, status);
}
debug_return;
}
static void
mon_signal_cb(int signo, int what, void *v)
{
struct sudo_ev_siginfo_container *sc = v;
struct monitor_closure *mc = sc->closure;
debug_decl(mon_signal_cb, SUDO_DEBUG_EXEC);
if (signo == SIGCHLD) {
mon_handle_sigchld(mc);
if (mc->cmnd_pid == -1) {
sudo_ev_loopexit(mc->evbase);
}
} else {
if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
pid_t si_pgrp;
if (sc->siginfo->si_pid == mc->cmnd_pid)
debug_return;
si_pgrp = getpgid(sc->siginfo->si_pid);
if (si_pgrp != -1) {
if (si_pgrp == mc->cmnd_pgrp)
debug_return;
}
}
deliver_signal(mc, signo, false);
}
debug_return;
}
static void
mon_errsock_cb(int fd, int what, void *v)
{
struct monitor_closure *mc = v;
ssize_t nread;
int errval;
debug_decl(mon_errsock_cb, SUDO_DEBUG_EXEC);
nread = read(fd, &errval, sizeof(errval));
if (nread < 0) {
if (errno != EAGAIN && errno != EINTR) {
if (mc->cstat->val == CMD_INVALID) {
mc->cstat->type = CMD_ERRNO;
mc->cstat->val = errno;
}
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: failed to read error socket", __func__);
sudo_ev_loopbreak(mc->evbase);
}
debug_return;
}
if (nread == 0) {
sudo_debug_printf(SUDO_DEBUG_INFO, "EOF on error socket");
} else {
sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s",
strerror(errval));
mc->cstat->type = CMD_ERRNO;
mc->cstat->val = errval;
}
sudo_ev_del(mc->evbase, mc->errsock_event);
close(fd);
debug_return;
}
static void
mon_backchannel_cb(int fd, int what, void *v)
{
struct monitor_closure *mc = v;
struct command_status cstmp;
ssize_t n;
debug_decl(mon_backchannel_cb, SUDO_DEBUG_EXEC);
n = recv(fd, &cstmp, sizeof(cstmp), MSG_WAITALL);
if (n != ssizeof(cstmp)) {
if (n == -1) {
if (errno == EINTR || errno == EAGAIN)
debug_return;
sudo_warn("%s", U_("error reading from socketpair"));
} else {
}
mc->cstat->type = CMD_ERRNO;
mc->cstat->val = n ? EIO : ECONNRESET;
sudo_ev_loopbreak(mc->evbase);
} else {
if (cstmp.type == CMD_SIGNO) {
deliver_signal(mc, cstmp.val, true);
} else {
sudo_warnx(U_("unexpected reply type on backchannel: %d"),
cstmp.type);
}
}
debug_return;
}
static void
exec_cmnd_pty(struct command_details *details, sigset_t *mask,
bool foreground, int intercept_fd, int errfd)
{
volatile pid_t self = getpid();
debug_decl(exec_cmnd_pty, SUDO_DEBUG_EXEC);
setpgid(0, self);
if (dup3(io_fds[SFD_STDIN], STDIN_FILENO, 0) == -1)
sudo_fatal("dup3");
if (io_fds[SFD_STDIN] != io_fds[SFD_FOLLOWER])
close(io_fds[SFD_STDIN]);
if (dup3(io_fds[SFD_STDOUT], STDOUT_FILENO, 0) == -1)
sudo_fatal("dup3");
if (io_fds[SFD_STDOUT] != io_fds[SFD_FOLLOWER])
close(io_fds[SFD_STDOUT]);
if (dup3(io_fds[SFD_STDERR], STDERR_FILENO, 0) == -1)
sudo_fatal("dup3");
if (io_fds[SFD_STDERR] != io_fds[SFD_FOLLOWER])
close(io_fds[SFD_STDERR]);
if (foreground) {
char ch;
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: waiting for controlling tty",
__func__);
if (recv(errfd, &ch, sizeof(ch), 0) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to receive message from parent", __func__);
debug_return;
}
if (tcgetpgrp(io_fds[SFD_FOLLOWER]) == self) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: got controlling tty",
__func__);
} else {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: unable to get controlling tty", __func__);
foreground = false;
}
}
if (io_fds[SFD_FOLLOWER] != -1)
close(io_fds[SFD_FOLLOWER]);
sudo_debug_printf(SUDO_DEBUG_INFO, "executing %s in the %s",
details->command, foreground ? "foreground" : "background");
exec_cmnd(details, mask, intercept_fd, errfd);
debug_return;
}
static void
init_exec_closure_monitor(struct monitor_closure *mc,
const struct command_details *details, struct command_status *cstat,
int backchannel)
{
debug_decl(init_exec_closure_monitor, SUDO_DEBUG_EXEC);
memset(mc, 0, sizeof(*mc));
mc->details = details;
mc->cstat = cstat;
mc->backchannel = backchannel;
mc->mon_pgrp = getpgrp();
debug_return;
}
static void
init_exec_events_monitor(struct monitor_closure *mc, int errfd)
{
debug_decl(init_exec_events_monitor, SUDO_DEBUG_EXEC);
mc->evbase = sudo_ev_base_alloc();
if (mc->evbase == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
mc->errsock_event = sudo_ev_alloc(errfd,
SUDO_EV_READ|SUDO_EV_PERSIST, mon_errsock_cb, mc);
if (mc->errsock_event == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->errsock_event, NULL, false) == -1)
sudo_fatal("%s", U_("unable to add event to queue"));
mc->backchannel_event = sudo_ev_alloc(mc->backchannel,
SUDO_EV_READ|SUDO_EV_PERSIST, mon_backchannel_cb, mc);
if (mc->backchannel_event == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->backchannel_event, NULL, false) == -1)
sudo_fatal("%s", U_("unable to add event to queue"));
mc->sigint_event = sudo_ev_alloc(SIGINT,
SUDO_EV_SIGINFO, mon_signal_cb, mc);
if (mc->sigint_event == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->sigint_event, NULL, false) == -1)
sudo_fatal("%s", 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_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->sigquit_event, NULL, false) == -1)
sudo_fatal("%s", 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_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->sigtstp_event, NULL, false) == -1)
sudo_fatal("%s", 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_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->sigterm_event, NULL, false) == -1)
sudo_fatal("%s", 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_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->sighup_event, NULL, false) == -1)
sudo_fatal("%s", 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_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->sigusr1_event, NULL, false) == -1)
sudo_fatal("%s", 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_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->sigusr2_event, NULL, false) == -1)
sudo_fatal("%s", 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_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (sudo_ev_add(mc->evbase, mc->sigchld_event, NULL, false) == -1)
sudo_fatal("%s", U_("unable to add event to queue"));
sudo_ev_base_setdef(NULL);
debug_return;
}
static bool
pty_make_controlling(const char *follower)
{
debug_decl(pty_make_controlling, SUDO_DEBUG_EXEC);
if (io_fds[SFD_FOLLOWER] != -1) {
#ifdef TIOCSCTTY
if (ioctl(io_fds[SFD_FOLLOWER], TIOCSCTTY, NULL) != 0)
debug_return_bool(false);
#else
int fd = open(follower, O_RDWR);
if (fd == -1)
debug_return_bool(false);
close(fd);
#endif
}
debug_return_bool(true);
}
int
exec_monitor(struct command_details *details, sigset_t *oset,
bool foreground, int backchannel, int intercept_fd)
{
struct monitor_closure mc;
struct command_status cstat;
struct sigaction sa;
int errsock[2];
debug_decl(exec_monitor, SUDO_DEBUG_EXEC);
if (io_fds[SFD_LEADER] != -1)
close(io_fds[SFD_LEADER]);
if (io_fds[SFD_USERTTY] != -1)
close(io_fds[SFD_USERTTY]);
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN;
if (sudo_sigaction(SIGTTIN, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTTIN);
if (sudo_sigaction(SIGTTOU, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTTOU);
if (setsid() == -1) {
sudo_warn("setsid");
goto bad;
}
if (!pty_make_controlling(details->tty)) {
sudo_warn("%s", U_("unable to set controlling tty"));
goto bad;
}
init_exec_closure_monitor(&mc, details, &cstat, backchannel);
if (socketpair(PF_UNIX, SOCK_STREAM, 0, errsock) == -1 ||
fcntl(errsock[0], F_SETFD, FD_CLOEXEC) == -1 ||
fcntl(errsock[1], F_SETFD, FD_CLOEXEC) == -1) {
sudo_warn("%s", U_("unable to create sockets"));
goto bad;
}
if (recv(backchannel, &cstat, sizeof(cstat), MSG_WAITALL) == -1) {
sudo_warn("%s", U_("unable to receive message from parent"));
goto bad;
}
#ifdef HAVE_SELINUX
if (ISSET(details->flags, CD_RBAC_ENABLED)) {
if (selinux_relabel_tty(details->tty, io_fds[SFD_FOLLOWER]) == -1)
goto bad;
selinux_audit_role_change();
}
#endif
mc.cmnd_pid = sudo_debug_fork();
switch (mc.cmnd_pid) {
case -1:
sudo_warn("%s", U_("unable to fork"));
#ifdef HAVE_SELINUX
if (ISSET(details->flags, CD_RBAC_ENABLED)) {
if (selinux_restore_tty() != 0)
sudo_warnx("%s", U_("unable to restore tty label"));
}
#endif
goto bad;
case 0:
close(backchannel);
close(errsock[0]);
exec_cmnd_pty(details, oset, foreground, intercept_fd, errsock[1]);
if (send(errsock[1], &errno, sizeof(int), 0) == -1)
sudo_warn(U_("unable to execute %s"), details->command);
_exit(EXIT_FAILURE);
}
close(errsock[1]);
if (intercept_fd != -1)
close(intercept_fd);
if (details->execfd != -1) {
close(details->execfd);
details->execfd = -1;
}
cstat.type = CMD_PID;
cstat.val = mc.cmnd_pid;
send_status(backchannel, &cstat);
init_exec_events_monitor(&mc, errsock[0]);
sigprocmask(SIG_SETMASK, oset, NULL);
if (io_fds[SFD_STDIN] != io_fds[SFD_FOLLOWER])
close(io_fds[SFD_STDIN]);
if (io_fds[SFD_STDOUT] != io_fds[SFD_FOLLOWER])
close(io_fds[SFD_STDOUT]);
if (io_fds[SFD_STDERR] != io_fds[SFD_FOLLOWER])
close(io_fds[SFD_STDERR]);
mc.cmnd_pgrp = mc.cmnd_pid;
setpgid(mc.cmnd_pid, mc.cmnd_pgrp);
if (foreground) {
if (tcsetpgrp(io_fds[SFD_FOLLOWER], mc.cmnd_pgrp) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to set foreground pgrp to %d (command)",
__func__, (int)mc.cmnd_pgrp);
}
if (send(errsock[0], "", 1, 0) == -1) {
sudo_warn(U_("unable to execute %s"), details->command);
terminate_command(mc.cmnd_pid, true);
}
}
cstat.type = CMD_INVALID;
cstat.val = 0;
(void) sudo_ev_dispatch(mc.evbase);
if (mc.cmnd_pid != -1) {
pid_t pid;
sudo_debug_printf(SUDO_DEBUG_ERROR,
"Command still running after event loop exit, terminating");
terminate_command(mc.cmnd_pid, true);
do {
pid = waitpid(mc.cmnd_pid, NULL, 0);
} while (pid == -1 && errno == EINTR);
}
if (tcsetpgrp(io_fds[SFD_FOLLOWER], mc.mon_pgrp) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to set foreground pgrp to %d (monitor)",
__func__, (int)mc.mon_pgrp);
}
send_status(backchannel, &cstat);
#ifdef HAVE_SELINUX
if (ISSET(details->flags, CD_RBAC_ENABLED)) {
if (selinux_restore_tty() != 0)
sudo_warnx("%s", U_("unable to restore tty label"));
}
#endif
sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
_exit(EXIT_FAILURE);
bad:
debug_return_int(-1);
}