src/env_hooks.c
src/exec.c
src/exec_common.c
+src/exec_monitor.c
+src/exec_nopty.c
src/exec_pty.c
src/get_pty.c
src/hooks.c
#
-# Copyright (c) 2010-2015 Todd C. Miller <Todd.Miller@courtesan.com>
+# Copyright (c) 2010-2017 Todd C. Miller <Todd.Miller@courtesan.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
PROGS = @PROGS@
-OBJS = conversation.o env_hooks.o exec.o exec_common.o exec_pty.o \
- get_pty.o hooks.o net_ifs.o load_plugins.o parse_args.o \
- preserve_fds.o signal.o sudo.o sudo_edit.o tgetpass.o ttyname.o \
- utmp.o @SUDO_OBJS@
+OBJS = conversation.o env_hooks.o exec.o exec_common.o exec_monitor.o \
+ exec_nopty.o exec_pty.o get_pty.o hooks.o net_ifs.o load_plugins.o \
+ parse_args.o preserve_fds.o signal.o sudo.o sudo_edit.o tgetpass.o \
+ ttyname.o utmp.o @SUDO_OBJS@
SESH_OBJS = sesh.o exec_common.o
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/exec_common.c
+exec_monitor.o: $(srcdir)/exec_monitor.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/exec_monitor.c
+exec_nopty.o: $(srcdir)/exec_nopty.c $(incdir)/compat/stdbool.h \
+ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
+ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
+ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
+ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
+ $(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
+ $(srcdir)/sudo_plugin_int.h $(top_builddir)/config.h \
+ $(top_builddir)/pathnames.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/exec_nopty.c
exec_pty.o: $(srcdir)/exec_pty.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
#include <config.h>
#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/wait.h>
-#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <unistd.h>
-#ifdef TIME_WITH_SYS_TIME
-# include <time.h>
-#endif
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
-#include <termios.h>
#include "sudo.h"
#include "sudo_exec.h"
#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
-struct exec_closure {
- pid_t child;
- bool log_io;
- sigset_t omask;
- struct command_status *cstat;
- struct command_details *details;
- struct sudo_event_base *evbase;
-};
-
-/* We keep a tailq of signals to forward to child. */
-struct sigforward {
- TAILQ_ENTRY(sigforward) entries;
- int signo;
-};
-TAILQ_HEAD(sigfwd_list, sigforward);
-static struct sigfwd_list sigfwd_list = TAILQ_HEAD_INITIALIZER(sigfwd_list);
-static struct sudo_event *signal_event;
-static struct sudo_event *sigfwd_event;
-static struct sudo_event *backchannel_event;
-static pid_t ppgrp = -1;
-
volatile pid_t cmnd_pid = -1;
-
-static void signal_pipe_cb(int fd, int what, void *v);
-static int dispatch_pending_signals(struct command_status *cstat);
-static void forward_signals(int fd, int what, void *v);
-static void schedule_signal(struct sudo_event_base *evbase, int signo);
-#ifdef SA_SIGINFO
-static void handler_user_only(int s, siginfo_t *info, void *context);
-#endif
+volatile pid_t ppgrp = -1;
/*
- * Fork and execute a command, returns the child's pid.
- * Sends errno back on sv[1] if execve() fails.
+ * 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.
*/
-static int
-fork_cmnd(struct command_details *details, int sv[2])
+#ifdef SA_SIGINFO
+void
+exec_handler(int s, siginfo_t *info, void *context)
{
- struct command_status cstat;
- sigaction_t sa;
- debug_decl(fork_cmnd, SUDO_DEBUG_EXEC)
+ unsigned char signo = (unsigned char)s;
- ppgrp = getpgrp(); /* parent's process group */
+ /*
+ * 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;
+ }
+ }
/*
- * 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).
+ * The pipe is non-blocking, if we overflow the kernel's pipe
+ * buffer we drop the signal. This is not a problem in practice.
*/
- 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 = handler;
+ while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+}
#else
- sa.sa_handler = handler;
-#endif
- 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 SA_SIGINFO
- sa.sa_sigaction = handler_user_only;
-#endif
- if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
+void
+exec_handler(int s)
+{
+ unsigned char signo = (unsigned char)s;
- cmnd_pid = sudo_debug_fork();
- switch (cmnd_pid) {
- case -1:
- sudo_fatal(U_("unable to fork"));
- break;
- case 0:
- /* child */
- close(sv[0]);
- close(signal_pipe[0]);
- close(signal_pipe[1]);
- (void)fcntl(sv[1], F_SETFD, FD_CLOEXEC);
- exec_cmnd(details, &cstat, sv[1]);
- ignore_result(send(sv[1], &cstat, sizeof(cstat), 0));
- sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
- _exit(1);
+ /*
+ * 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;
}
- sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
- (int)cmnd_pid);
- debug_return_int(cmnd_pid);
}
+#endif
/*
* Setup the execution environment and execute the command.
* If the exec fails, cstat is filled in with the value of errno.
*/
void
-exec_cmnd(struct command_details *details, struct command_status *cstat,
- int errfd)
+exec_cmnd(struct command_details *details, int errfd)
{
debug_decl(exec_cmnd, SUDO_DEBUG_EXEC)
details->envp, ISSET(details->flags, CD_NOEXEC));
}
}
- cstat->type = CMD_ERRNO;
- cstat->val = errno;
sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to exec %s: %s",
details->command, strerror(errno));
debug_return;
}
-static void
-backchannel_cb(int fd, int what, void *v)
-{
- struct exec_closure *ec = v;
- ssize_t n;
- debug_decl(backchannel_cb, SUDO_DEBUG_EXEC)
-
- /* read child status */
- n = recv(fd, ec->cstat, sizeof(struct command_status), MSG_WAITALL);
- if (n != sizeof(struct command_status)) {
- if (n == -1) {
- switch (errno) {
- case EINTR:
- /* got a signal, restart loop to service it. */
- sudo_ev_loopcontinue(ec->evbase);
- break;
- case EAGAIN:
- /* not ready after all... */
- break;
- default:
- ec->cstat->type = CMD_ERRNO;
- ec->cstat->val = errno;
- sudo_debug_printf(SUDO_DEBUG_ERROR,
- "failed to read child status: %s", strerror(errno));
- sudo_ev_loopbreak(ec->evbase);
- break;
- }
- } else {
- /* Short read or EOF. */
- sudo_debug_printf(SUDO_DEBUG_ERROR,
- "failed to read child status: %s", n ? "short read" : "EOF");
- if (!ec->log_io && n == 0) {
- /*
- * If not logging I/O we may get EOF when the command is
- * executed and the other end of the backchannel is closed.
- * Just remove the event in this case.
- */
- sudo_ev_del(ec->evbase, backchannel_event);
- } else {
- /* XXX - need new CMD_ type for monitor errors. */
- errno = n ? EIO : ECONNRESET;
- ec->cstat->type = CMD_ERRNO;
- ec->cstat->val = errno;
- sudo_ev_loopbreak(ec->evbase);
- }
- }
- debug_return;
- }
- switch (ec->cstat->type) {
- case CMD_PID:
- /*
- * Once we know the command's pid we can unblock
- * signals which were blocked in fork_pty(). This
- * avoids a race between exec of the command and
- * receipt of a fatal signal from it.
- */
- cmnd_pid = ec->cstat->val;
- sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
- ec->details->command, (int)cmnd_pid);
- if (ec->log_io)
- sigprocmask(SIG_SETMASK, &ec->omask, NULL);
- break;
- case CMD_WSTATUS:
- if (WIFSTOPPED(ec->cstat->val)) {
- /* Suspend parent and tell child how to resume on return. */
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "child stopped, suspending parent");
- n = suspend_parent(WSTOPSIG(ec->cstat->val));
- schedule_signal(ec->evbase, n);
- /* Re-enable I/O events and restart event loop to service signal. */
- add_io_events(ec->evbase);
- sudo_ev_loopcontinue(ec->evbase);
- } else {
- /* Child exited or was killed, either way we are done. */
- sudo_debug_printf(SUDO_DEBUG_INFO, "child exited or was killed");
- sudo_ev_loopexit(ec->evbase);
- }
- break;
- case CMD_ERRNO:
- /* Child was unable to execute command or broken pipe. */
- sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s",
- strerror(ec->cstat->val));
- sudo_ev_loopbreak(ec->evbase);
- break;
- }
- debug_return;
-}
-
-/*
- * Setup initial exec events.
- * Allocates events for the signal pipe and backchannel.
- * Forwarded signals on the backchannel are enabled on demand.
- */
-static struct sudo_event_base *
-exec_event_setup(int backchannel, struct exec_closure *ec)
-{
- struct sudo_event_base *evbase;
- debug_decl(exec_event_setup, SUDO_DEBUG_EXEC)
-
- evbase = sudo_ev_base_alloc();
- if (evbase == NULL)
- sudo_fatal(NULL);
-
- /* Event for incoming signals via signal_pipe. */
- signal_event = sudo_ev_alloc(signal_pipe[0],
- SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, ec);
- if (signal_event == NULL)
- sudo_fatal(NULL);
- if (sudo_ev_add(evbase, signal_event, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
-
- /* Event for command status via backchannel. */
- backchannel_event = sudo_ev_alloc(backchannel,
- SUDO_EV_READ|SUDO_EV_PERSIST, backchannel_cb, ec);
- if (backchannel_event == NULL)
- sudo_fatal(NULL);
- if (sudo_ev_add(evbase, backchannel_event, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
-
- /* The signal forwarding event gets added on demand. */
- sigfwd_event = sudo_ev_alloc(backchannel,
- SUDO_EV_WRITE, forward_signals, NULL);
- if (sigfwd_event == NULL)
- sudo_fatal(NULL);
-
- sudo_debug_printf(SUDO_DEBUG_INFO, "signal pipe fd %d\n", signal_pipe[0]);
- sudo_debug_printf(SUDO_DEBUG_INFO, "backchannel fd %d\n", backchannel);
-
- debug_return_ptr(evbase);
-}
-
-/*
- * Execute a command, potentially in a pty with I/O loggging, and
- * wait for it to finish.
- * This is a little bit tricky due to how POSIX job control works and
- * we fact that we have two different controlling terminals to deal with.
- */
-int
-sudo_execute(struct command_details *details, struct command_status *cstat)
-{
- struct sigforward *sigfwd, *sigfwd_next;
- const char *utmp_user = NULL;
- struct sudo_event_base *evbase;
- struct exec_closure ec;
- bool log_io = false;
- sigaction_t sa;
- pid_t child;
- int sv[2];
- debug_decl(sudo_execute, SUDO_DEBUG_EXEC)
-
- dispatch_pending_signals(cstat);
-
- /* If running in background mode, fork and exit. */
- if (ISSET(details->flags, CD_BACKGROUND)) {
- switch (sudo_debug_fork()) {
- case -1:
- cstat->type = CMD_ERRNO;
- cstat->val = errno;
- debug_return_int(-1);
- case 0:
- /* child continues without controlling terminal */
- (void)setpgid(0, 0);
- break;
- default:
- /* parent exits (but does not flush buffers) */
- sudo_debug_exit_int(__func__, __FILE__, __LINE__,
- sudo_debug_subsys, 0);
- _exit(0);
- }
- }
-
- /*
- * If we have an I/O plugin or the policy plugin has requested one, we
- * need to allocate a pty. It is OK to set log_io in the pty-only case
- * as the io plugin tailqueue will be empty and no I/O logging will occur.
- */
- if (!TAILQ_EMPTY(&io_plugins) || ISSET(details->flags, CD_USE_PTY)) {
- log_io = true;
- if (ISSET(details->flags, CD_SET_UTMP))
- utmp_user = details->utmp_user ? details->utmp_user : user_details.username;
- sudo_debug_printf(SUDO_DEBUG_INFO, "allocate pty for I/O logging");
- pty_setup(details->euid, user_details.tty, utmp_user);
- } else if (!ISSET(details->flags, CD_SET_TIMEOUT|CD_SUDOEDIT) &&
- policy_plugin.u.policy->close == NULL) {
- /*
- * If there is no policy close function, no I/O logging or pty,
- * and we were not invoked as sudoedit, just exec directly.
- */
- exec_cmnd(details, cstat, -1);
- goto done;
- }
-
- /*
- * We communicate with the child over a bi-directional pair of sockets.
- * Parent sends signal info to child and child sends back wait status.
- */
- if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1)
- sudo_fatal(U_("unable to create sockets"));
-
- /*
- * Signals to forward 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.
- */
- 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 = handler;
-#else
- sa.sa_handler = 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);
-#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 either by virtue of being in the
- * controlling tty's process group (SIGINT, SIGQUIT).
- */
-#ifdef SA_SIGINFO
- if (!log_io) {
- sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = 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);
-
- /*
- * The policy plugin's session init must be run before we fork
- * or certain pam modules won't be able to track their state.
- */
- if (policy_init_session(details) != true)
- sudo_fatalx(U_("policy plugin failed session initialization"));
-
- /*
- * Child will run the command in the pty, parent will pass data
- * to and from pty.
- */
- if (log_io)
- child = fork_pty(details, sv, &ec.omask);
- else
- child = fork_cmnd(details, sv);
- close(sv[1]);
-
- /* No longer need execfd. */
- if (details->execfd != -1) {
- close(details->execfd);
- details->execfd = -1;
- }
-
- /* Set command timeout if specified. */
- if (ISSET(details->flags, CD_SET_TIMEOUT))
- alarm(details->timeout);
-
- /*
- * I/O logging must be in the C locale for floating point numbers
- * to be logged consistently.
- */
- setlocale(LC_ALL, "C");
-
- /*
- * Allocate event base and two persistent events:
- * the signal pipe and the child process's backchannel.
- */
- evbase = exec_event_setup(sv[0], &ec);
-
- /*
- * Generic exec closure used for signal_pipe and backchannel callbacks.
- * Note ec.omask is set earlier.
- */
- ec.child = child;
- ec.log_io = log_io;
- ec.cstat = cstat;
- ec.evbase = evbase;
- ec.details = details;
-
- /*
- * In the event loop we pass input from user tty to master
- * and pass output from master to stdout and IO plugin.
- */
- if (log_io)
- add_io_events(evbase);
- if (sudo_ev_loop(evbase, 0) == -1)
- sudo_warn(U_("error in event loop"));
- if (sudo_ev_got_break(evbase)) {
- /* error from callback */
- sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
- /* kill command if not I/O logging */
- if (!log_io)
- terminate_command(ec.child, true);
- }
-
- if (log_io) {
- /* Flush any remaining output and free pty-related memory. */
- pty_close(cstat);
- }
-
-#ifdef HAVE_SELINUX
- if (ISSET(details->flags, CD_RBAC_ENABLED)) {
- /* This is probably not needed in log_io mode. */
- if (selinux_restore_tty() != 0)
- sudo_warnx(U_("unable to restore tty label"));
- }
-#endif
-
- /* Free things up. */
- sudo_ev_base_free(evbase);
- sudo_ev_free(sigfwd_event);
- sudo_ev_free(signal_event);
- sudo_ev_free(backchannel_event);
- TAILQ_FOREACH_SAFE(sigfwd, &sigfwd_list, entries, sigfwd_next) {
- free(sigfwd);
- }
- TAILQ_INIT(&sigfwd_list);
-done:
- debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);
-}
-
-/*
- * Forward a signal to the command (non-pty version) or handle
- * changes to the command's status (SIGCHLD).
- */
-static void
-dispatch_signal(struct exec_closure *ec, int signo, char *signame)
-{
- debug_decl(dispatch_signal, SUDO_DEBUG_EXEC)
-
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "%s: evbase %p, child: %d, signo %s(%d), cstat %p",
- __func__, ec->evbase, (int)ec->child, signame, signo, ec->cstat);
-
- if (ec->child == -1)
- goto done;
-
- if (signo == SIGCHLD) {
- pid_t pid;
- int status;
- /*
- * The command stopped or exited.
- */
- do {
- pid = waitpid(ec->child, &status, WUNTRACED|WNOHANG);
- } while (pid == -1 && errno == EINTR);
- if (pid == ec->child) {
- if (WIFSTOPPED(status)) {
- /*
- * Save the controlling terminal's process group
- * so we can restore it after we resume, if needed.
- * Most well-behaved shells change the pgrp back to
- * its original value before suspending so we must
- * not try to restore in that case, lest we race with
- * the child upon resume, potentially stopping sudo
- * with SIGTTOU while the command continues to run.
- */
- sigaction_t sa, osa;
- pid_t saved_pgrp = -1;
- int signo = WSTOPSIG(status);
- int fd = open(_PATH_TTY, O_RDWR);
- if (fd != -1) {
- saved_pgrp = tcgetpgrp(fd);
- if (saved_pgrp == -1) {
- close(fd);
- fd = -1;
- }
- }
- if (saved_pgrp != -1) {
- /*
- * Child was stopped trying to access controlling
- * terminal. If the child has a different pgrp
- * and we own the controlling terminal, give it
- * to the child's pgrp and let it continue.
- */
- if (signo == SIGTTOU || signo == SIGTTIN) {
- if (saved_pgrp == ppgrp) {
- pid_t child_pgrp = getpgid(ec->child);
- if (child_pgrp != ppgrp) {
- if (tcsetpgrp(fd, child_pgrp) == 0) {
- if (killpg(child_pgrp, SIGCONT) != 0) {
- sudo_warn("kill(%d, SIGCONT)",
- (int)child_pgrp);
- }
- close(fd);
- goto done;
- }
- }
- }
- }
- }
- if (signo == SIGTSTP) {
- memset(&sa, 0, sizeof(sa));
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- sa.sa_handler = SIG_DFL;
- if (sudo_sigaction(SIGTSTP, &sa, &osa) != 0) {
- sudo_warn(U_("unable to set handler for signal %d"),
- SIGTSTP);
- }
- }
- if (kill(getpid(), signo) != 0)
- sudo_warn("kill(%d, SIG%s)", (int)getpid(), signame);
- if (signo == SIGTSTP) {
- if (sudo_sigaction(SIGTSTP, &osa, NULL) != 0) {
- sudo_warn(U_("unable to restore handler for signal %d"),
- SIGTSTP);
- }
- }
- if (saved_pgrp != -1) {
- /*
- * Restore command's process group if different.
- * Otherwise, we cannot resume some shells.
- */
- if (saved_pgrp != ppgrp)
- (void)tcsetpgrp(fd, saved_pgrp);
- close(fd);
- }
- } else {
- /* Child has exited or been killed, we are done. */
- ec->child = -1;
- ec->cstat->type = CMD_WSTATUS;
- ec->cstat->val = status;
- sudo_ev_del(ec->evbase, signal_event);
- sudo_ev_loopexit(ec->evbase);
- goto done;
- }
- }
- } else {
- /* 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);
- }
- }
-done:
- debug_return;
-}
-
-/*
- * Forward a signal to the monitor (pty version) or handle
- * changes to the monitors's status (SIGCHLD).
- */
-static void
-dispatch_signal_pty(struct exec_closure *ec, int signo, char *signame)
-{
- debug_decl(dispatch_signal_pty, SUDO_DEBUG_EXEC)
-
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "%s: evbase %p, child: %d, signo %s(%d), cstat %p",
- __func__, ec->evbase, (int)ec->child, signame, signo, ec->cstat);
-
- if (ec->child == -1)
- goto done;
-
- if (signo == SIGCHLD) {
- int n, status;
- pid_t pid;
- /*
- * Monitor process was signaled; wait for it as needed.
- */
- do {
- pid = waitpid(ec->child, &status, WUNTRACED|WNOHANG);
- } while (pid == -1 && errno == EINTR);
- if (pid == ec->child) {
- /*
- * If the monitor dies we get notified via backchannel_cb().
- * If it was stopped, we should stop too (the command keeps
- * running in its pty) and continue it when we come back.
- */
- if (WIFSTOPPED(status)) {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "monitor stopped, suspending parent");
- n = suspend_parent(WSTOPSIG(status));
- kill(pid, SIGCONT);
- schedule_signal(ec->evbase, n);
- /* Re-enable I/O events and restart event loop. */
- add_io_events(ec->evbase);
- sudo_ev_loopcontinue(ec->evbase);
- goto done;
- } else if (WIFSIGNALED(status)) {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "monitor killed, signal %d", WTERMSIG(status));
- ec->child = -1;
- } else {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "monitor exited, status %d", WEXITSTATUS(status));
- ec->child = -1;
- }
- }
- } else {
- /* Schedule signo to be forwared to the child. */
- schedule_signal(ec->evbase, signo);
- /* Restart event loop to service signal immediately. */
- sudo_ev_loopcontinue(ec->evbase);
- }
-done:
- debug_return;
-}
-
-/* Signal pipe callback */
-static void
-signal_pipe_cb(int fd, int what, void *v)
-{
- struct exec_closure *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, "received SIG%s", signame);
- if (ec->log_io) {
- dispatch_signal_pty(ec, signo, signame);
- } else {
- dispatch_signal(ec, signo, signame);
- }
- } while (ec->child != -1);
- debug_return;
-}
-
/*
* Drain pending signals from signal_pipe written by sudo_handler().
* Handles the case where the signal was sent to us before
}
/*
- * Forward signals in sigfwd_list to child listening on fd.
- */
-static void
-forward_signals(int sock, int what, void *v)
-{
- char signame[SIG2STR_MAX];
- struct sigforward *sigfwd;
- struct command_status cstat;
- ssize_t nsent;
- debug_decl(forward_signals, SUDO_DEBUG_EXEC)
-
- while (!TAILQ_EMPTY(&sigfwd_list)) {
- sigfwd = TAILQ_FIRST(&sigfwd_list);
- if (sigfwd->signo == SIGCONT_FG)
- strlcpy(signame, "CONT_FG", sizeof(signame));
- else if (sigfwd->signo == SIGCONT_BG)
- strlcpy(signame, "CONT_BG", sizeof(signame));
- else if (sig2str(sigfwd->signo, signame) == -1)
- snprintf(signame, sizeof(signame), "%d", sigfwd->signo);
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "sending SIG%s to child over backchannel", signame);
- cstat.type = CMD_SIGNO;
- cstat.val = sigfwd->signo;
- do {
- nsent = send(sock, &cstat, sizeof(cstat), 0);
- } while (nsent == -1 && errno == EINTR);
- TAILQ_REMOVE(&sigfwd_list, sigfwd, entries);
- free(sigfwd);
- if (nsent != sizeof(cstat)) {
- if (errno == EPIPE) {
- struct sigforward *sigfwd_next;
- sudo_debug_printf(SUDO_DEBUG_ERROR,
- "broken pipe writing to child over backchannel");
- /* Other end of socket gone, empty out sigfwd_list. */
- TAILQ_FOREACH_SAFE(sigfwd, &sigfwd_list, entries, sigfwd_next) {
- free(sigfwd);
- }
- TAILQ_INIT(&sigfwd_list);
- /* XXX - child (monitor) is dead, we should exit too? */
- }
- break;
- }
- }
-}
-
-/*
- * Schedule a signal to be forwarded.
- */
-static void
-schedule_signal(struct sudo_event_base *evbase, int signo)
-{
- struct sigforward *sigfwd;
- char signame[SIG2STR_MAX];
- debug_decl(schedule_signal, SUDO_DEBUG_EXEC)
-
- if (signo == SIGCONT_FG)
- strlcpy(signame, "CONT_FG", sizeof(signame));
- else if (signo == SIGCONT_BG)
- strlcpy(signame, "CONT_BG", sizeof(signame));
- else if (sig2str(signo, signame) == -1)
- snprintf(signame, sizeof(signame), "%d", signo);
- sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for child", signame);
-
- if ((sigfwd = calloc(1, sizeof(*sigfwd))) == NULL)
- sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
- sigfwd->signo = signo;
- TAILQ_INSERT_TAIL(&sigfwd_list, sigfwd, entries);
-
- if (sudo_ev_add(evbase, sigfwd_event, NULL, true) == -1)
- sudo_fatal(U_("unable to add event to queue"));
-
- debug_return;
-}
-
-/*
- * Generic handler for signals passed from parent -> child.
- * The other end of signal_pipe is checked in the main event loop.
+ * Execute a command, potentially in a pty with I/O loggging, and
+ * wait for it to finish.
+ * This is a little bit tricky due to how POSIX job control works and
+ * we fact that we have two different controlling terminals to deal with.
*/
-#ifdef SA_SIGINFO
-void
-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
-handler(int s)
+int
+sudo_execute(struct command_details *details, struct command_status *cstat)
{
- 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
+ debug_decl(sudo_execute, SUDO_DEBUG_EXEC)
-#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
-handler_user_only(int s, siginfo_t *info, void *context)
-{
- unsigned char signo = (unsigned char)s;
+ dispatch_pending_signals(cstat);
- /*
- * 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;
+ /* If running in background mode, fork and exit. */
+ if (ISSET(details->flags, CD_BACKGROUND)) {
+ switch (sudo_debug_fork()) {
+ case -1:
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ debug_return_int(-1);
+ case 0:
+ /* child continues without controlling terminal */
+ (void)setpgid(0, 0);
+ break;
+ default:
+ /* parent exits (but does not flush buffers) */
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__,
+ sudo_debug_subsys, 0);
+ _exit(0);
}
}
/*
- * The pipe is non-blocking, if we overflow the kernel's pipe
- * buffer we drop the signal. This is not a problem in practice.
+ * If we have an I/O plugin or the policy plugin has requested one, we
+ * need to allocate a pty.
*/
- while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
- if (errno != EINTR)
- break;
+ if (!TAILQ_EMPTY(&io_plugins) || ISSET(details->flags, CD_USE_PTY)) {
+ /*
+ * Run the command in a new pty, wait for it to finish and
+ * send the plugin the exit status.
+ */
+ exec_pty(details, cstat);
+ } else if (!ISSET(details->flags, CD_SET_TIMEOUT|CD_SUDOEDIT) &&
+ policy_plugin.u.policy->close == NULL) {
+ /*
+ * If we are not running the command in a pty, we were not invoked
+ * as sudoedit, there is no command timeout and there is no close
+ * function, just exec directly. Only returns on error.
+ */
+ exec_cmnd(details, -1);
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ } else {
+ /*
+ * No pty but we need to wait for the command to finish to
+ * send the plugin the exit status.
+ */
+ exec_nopty(details, cstat);
}
+ debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);
}
-#endif /* SA_SIGINFO */
/*
* Open a pipe and make both ends non-blocking.
debug_return_int(ret);
}
+
+/*
+ * Kill command with increasing urgency.
+ */
+void
+terminate_command(pid_t pid, bool use_pgrp)
+{
+ debug_decl(terminate_command, SUDO_DEBUG_EXEC);
+
+ /* Avoid killing more than a single process or process group. */
+ if (pid <= 0)
+ debug_return;
+
+ /*
+ * Note that SIGCHLD will interrupt the sleep()
+ */
+ if (use_pgrp) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGHUP", (int)pid);
+ killpg(pid, SIGHUP);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGTERM", (int)pid);
+ killpg(pid, SIGTERM);
+ sleep(2);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGKILL", (int)pid);
+ killpg(pid, SIGKILL);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGHUP", (int)pid);
+ kill(pid, SIGHUP);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGTERM", (int)pid);
+ kill(pid, SIGTERM);
+ sleep(2);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGKILL", (int)pid);
+ kill(pid, SIGKILL);
+ }
+
+ debug_return;
+}
--- /dev/null
+/*
+ * Copyright (c) 2009-2017 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "sudo.h"
+#include "sudo_event.h"
+#include "sudo_exec.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+struct monitor_closure {
+ struct sudo_event_base *evbase;
+ struct sudo_event *errpipe_event;
+ struct sudo_event *backchannel_event;
+ struct sudo_event *signal_pipe_event;
+ struct command_status *cstat;
+ int backchannel;
+};
+
+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.
+ * The signal was either forwarded to us by the parent sudo process
+ * or was received by the monitor itself.
+ *
+ * There are two "special" signals, SIGCONT_FG and SIGCONT_BG that
+ * also specify whether the command should have the controlling tty.
+ */
+static void
+deliver_signal(pid_t pid, int signo, bool from_parent)
+{
+ char signame[SIG2STR_MAX];
+ int status;
+ debug_decl(deliver_signal, SUDO_DEBUG_EXEC);
+
+ /* Avoid killing more than a single process or process group. */
+ if (pid <= 0)
+ debug_return;
+
+ if (signo == SIGCONT_FG)
+ strlcpy(signame, "CONT_FG", sizeof(signame));
+ else if (signo == SIGCONT_BG)
+ strlcpy(signame, "CONT_BG", sizeof(signame));
+ else if (sig2str(signo, signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", signo);
+
+ /* Handle signal from parent or monitor. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "received SIG%s%s",
+ signame, from_parent ? " from parent" : "");
+ switch (signo) {
+ case SIGALRM:
+ terminate_command(pid, true);
+ break;
+ case SIGCONT_FG:
+ /* Continue in foreground, grant it controlling tty. */
+ do {
+ status = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
+ } while (status == -1 && errno == EINTR);
+ killpg(pid, SIGCONT);
+ break;
+ case SIGCONT_BG:
+ /* Continue in background, I take controlling tty. */
+ do {
+ status = tcsetpgrp(io_fds[SFD_SLAVE], mon_pgrp);
+ } while (status == -1 && errno == EINTR);
+ killpg(pid, SIGCONT);
+ break;
+ case SIGKILL:
+ _exit(1); /* XXX */
+ /* NOTREACHED */
+ default:
+ /* Relay signal to command. */
+ killpg(pid, signo);
+ break;
+ }
+ debug_return;
+}
+
+/*
+ * Send status to parent over socketpair.
+ * Return value is the same as send(2).
+ */
+static int
+send_status(int fd, struct command_status *cstat)
+{
+ int 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);
+ do {
+ n = send(fd, cstat, sizeof(*cstat), 0);
+ } while (n == -1 && errno == EINTR);
+ if (n != sizeof(*cstat)) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "unable to send status to parent: %s", strerror(errno));
+ }
+ cstat->type = CMD_INVALID; /* prevent re-sending */
+ }
+ debug_return_int(n);
+}
+
+/*
+ * Wait for command status after receiving SIGCHLD.
+ * If the command was stopped, the status is send back to the parent.
+ * Otherwise, cstat is filled in but not sent.
+ */
+static void
+handle_sigchld(int backchannel, struct command_status *cstat)
+{
+ char signame[SIG2STR_MAX];
+ int status;
+ pid_t pid;
+ debug_decl(handle_sigchld, SUDO_DEBUG_EXEC);
+
+ /* Read command status. */
+ do {
+ pid = waitpid(cmnd_pid, &status, WUNTRACED|WCONTINUED|WNOHANG);
+ } while (pid == -1 && errno == EINTR);
+ switch (pid) {
+ case 0:
+ errno = ECHILD;
+ /* FALLTHROUGH */
+ case -1:
+ sudo_debug_printf(SUDO_DEBUG_DIAG,
+ "waitpid returned %d, expected pid %d", pid, cmnd_pid);
+ sudo_warn(U_("%s: %s"), __func__, "waitpid");
+ debug_return;
+ }
+
+ if (WIFCONTINUED(status)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) resumed",
+ __func__, cmnd_pid);
+ } else if (WIFSTOPPED(status)) {
+ if (sig2str(WSTOPSIG(status), signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", WSTOPSIG(status));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s",
+ __func__, cmnd_pid, signame);
+ } else if (WIFSIGNALED(status)) {
+ if (sig2str(WTERMSIG(status), signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
+ __func__, cmnd_pid, signame);
+ cmnd_pid = -1;
+ } else if (WIFEXITED(status)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
+ __func__, cmnd_pid, WEXITSTATUS(status));
+ cmnd_pid = -1;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_WARN,
+ "%s: unexpected wait status %d for command (%d)",
+ __func__, status, (int)cmnd_pid);
+ }
+
+ /* Don't overwrite execve() failure with child exit status. */
+ if (cstat->type != CMD_ERRNO) {
+ /*
+ * Store wait status in cstat and forward to parent if stopped.
+ */
+ cstat->type = CMD_WSTATUS;
+ cstat->val = status;
+ if (WIFSTOPPED(status)) {
+ /* Save the foreground pgid so we can restore it later. */
+ do {
+ pid = tcgetpgrp(io_fds[SFD_SLAVE]);
+ } while (pid == -1 && errno == EINTR);
+ if (pid != mon_pgrp)
+ cmnd_pgrp = pid;
+ send_status(backchannel, cstat);
+ }
+ }
+
+ debug_return;
+}
+
+static void
+mon_signal_pipe_cb(int fd, int what, void *v)
+{
+ struct monitor_closure *mc = v;
+ unsigned char signo;
+ ssize_t nread;
+ 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
+ * directly to the command.
+ */
+ if (signo == SIGCHLD) {
+ handle_sigchld(mc->backchannel, mc->cstat);
+ if (cmnd_pid == -1) {
+ /* Remove all but the errpipe event. */
+ sudo_ev_del(mc->evbase, mc->backchannel_event);
+ sudo_ev_del(mc->evbase, mc->signal_pipe_event);
+ }
+ } else {
+ deliver_signal(cmnd_pid, signo, false);
+ }
+ }
+ debug_return;
+}
+
+/* Note: this is basically the same as errpipe_cb() in exec_nopty.c */
+static void
+mon_errpipe_cb(int fd, int what, void *v)
+{
+ struct monitor_closure *mc = v;
+ ssize_t n;
+ int errval;
+ debug_decl(mon_errpipe_cb, SUDO_DEBUG_EXEC);
+
+ /* read errno from child or EOF when command is executed. */
+ n = read(fd, &errval, sizeof(errval));
+ switch (n) {
+ case -1:
+ switch (errno) {
+ case EINTR:
+ /* got a signal, restart loop to service it. */
+ sudo_ev_loopcontinue(mc->evbase);
+ break;
+ case EAGAIN:
+ /* not ready after all... */
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "failed to read error pipe: %s", strerror(errno));
+ mc->cstat->type = CMD_ERRNO;
+ mc->cstat->val = errno;
+ sudo_ev_loopbreak(mc->evbase);
+ break;
+ }
+ break;
+ case 0:
+ /*
+ * We get EOF when the command is executed and the other
+ * end of the error pipe is closed. Just remove the event.
+ */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "EOF on error pipe, removing event");
+ sudo_ev_del(mc->evbase, mc->errpipe_event);
+ break;
+ default:
+ /* Errno value when child is unable to execute command. */
+ 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->errpipe_event);
+ break;
+ }
+ 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);
+
+ /* Read command from backchannel, should be a signal. */
+ n = recv(fd, &cstmp, sizeof(cstmp), MSG_WAITALL);
+ if (n != sizeof(cstmp)) {
+ if (n == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ debug_return;
+ sudo_warn(U_("error reading from socketpair"));
+ } else {
+ /* short read or EOF, parent process died? */
+ }
+ sudo_ev_loopbreak(mc->evbase);
+ } else {
+ if (cstmp.type == CMD_SIGNO) {
+ deliver_signal(cmnd_pid, cstmp.val, true);
+ } else {
+ sudo_warnx(U_("unexpected reply type on backchannel: %d"), cstmp.type);
+ }
+ }
+ debug_return;
+}
+
+/*
+ * Sets up std{in,out,err} and executes the actual command.
+ * Returns only if execve() fails.
+ */
+static void
+exec_cmnd_pty(struct command_details *details, bool foreground, int errfd)
+{
+ volatile pid_t self = getpid();
+ debug_decl(exec_cmnd_pty, SUDO_DEBUG_EXEC);
+
+ /* Register cleanup function */
+ sudo_fatal_callback_register(pty_cleanup);
+
+ /* Set command process group here too to avoid a race. */
+ setpgid(0, self);
+
+ /* Wire up standard fds, note that stdout/stderr may be pipes. */
+ if (dup2(io_fds[SFD_STDIN], STDIN_FILENO) == -1 ||
+ dup2(io_fds[SFD_STDOUT], STDOUT_FILENO) == -1 ||
+ dup2(io_fds[SFD_STDERR], STDERR_FILENO) == -1)
+ sudo_fatal("dup2");
+
+ /* Wait for parent to grant us the tty if we are foreground. */
+ if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
+ struct timespec ts = { 0, 1000 }; /* 1us */
+ while (tcgetpgrp(io_fds[SFD_SLAVE]) != self)
+ nanosleep(&ts, NULL);
+ }
+
+ /* We have guaranteed that the slave fd is > 2 */
+ if (io_fds[SFD_SLAVE] != -1)
+ close(io_fds[SFD_SLAVE]);
+ if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDIN]);
+ if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDOUT]);
+ if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDERR]);
+
+ /* Execute command; only returns on error. */
+ exec_cmnd(details, errfd);
+
+ debug_return;
+}
+
+/*
+ * Fill in the monitor closure and setup initial events.
+ * Allocates read events for the signal pipe, error pipe and backchannel.
+ */
+static void
+fill_exec_closure_monitor(struct monitor_closure *mc,
+ struct command_status *cstat, int errfd, int backchannel)
+{
+ debug_decl(fill_exec_closure_monitor, SUDO_DEBUG_EXEC);
+
+ /* Fill in the non-event part of the closure. */
+ cstat->type = CMD_INVALID;
+ cstat->val = 0;
+ mc->cstat = cstat;
+ mc->backchannel = backchannel;
+
+ /* Setup event base and events. */
+ mc->evbase = sudo_ev_base_alloc();
+ if (mc->evbase == 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. */
+ mc->errpipe_event = sudo_ev_alloc(errfd,
+ SUDO_EV_READ|SUDO_EV_PERSIST, mon_errpipe_cb, mc);
+ if (mc->errpipe_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->errpipe_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ /* Event for forwarded signals via backchannel. */
+ mc->backchannel_event = sudo_ev_alloc(backchannel,
+ SUDO_EV_READ|SUDO_EV_PERSIST, mon_backchannel_cb, mc);
+ if (mc->backchannel_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(mc->evbase, mc->backchannel_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+}
+
+/*
+ * Monitor process that creates a new session with the controlling tty,
+ * resets signal handlers and forks a child to call exec_cmnd_pty().
+ * Waits for status changes from the command and relays them to the
+ * parent and relays signals from the parent to the command.
+ * Returns an error if fork(2) fails, else calls _exit(2).
+ */
+int
+exec_monitor(struct command_details *details, bool foreground, int backchannel)
+{
+ struct command_status cstat;
+ struct monitor_closure mc;
+ sigaction_t sa;
+ int errpipe[2], n;
+ debug_decl(exec_monitor, SUDO_DEBUG_EXEC);
+
+ /* Close unused fds. */
+ if (io_fds[SFD_MASTER] != -1)
+ close(io_fds[SFD_MASTER]);
+ if (io_fds[SFD_USERTTY] != -1)
+ close(io_fds[SFD_USERTTY]);
+
+ /*
+ * We use a pipe to atomically handle signal notification within
+ * the event loop.
+ */
+ if (pipe_nonblock(signal_pipe) != 0)
+ sudo_fatal(U_("unable to create pipe"));
+
+ /* Reset SIGWINCH and SIGALRM. */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ 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;
+ 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);
+
+ /* 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
+ * and the slave pty as the controlling terminal.
+ * This allows us to be notified when the command has been suspended.
+ */
+ if (setsid() == -1) {
+ sudo_warn("setsid");
+ goto bad;
+ }
+ if (pty_make_controlling() == -1) {
+ sudo_warn(U_("unable to set controlling tty"));
+ goto bad;
+ }
+
+ mon_pgrp = getpgrp(); /* save a copy of our process group */
+
+ /* Start command and wait for it to stop or exit */
+ if (pipe(errpipe) == -1)
+ sudo_fatal(U_("unable to create pipe"));
+ cmnd_pid = sudo_debug_fork();
+ if (cmnd_pid == -1) {
+ sudo_warn(U_("unable to fork"));
+ goto bad;
+ }
+ if (cmnd_pid == 0) {
+ /* We pass errno back to our parent via pipe on exec failure. */
+ close(backchannel);
+ close(signal_pipe[0]);
+ close(signal_pipe[1]);
+ close(errpipe[0]);
+ (void)fcntl(errpipe[1], F_SETFD, FD_CLOEXEC);
+ restore_signals();
+
+ /* setup tty and exec command */
+ exec_cmnd_pty(details, foreground, errpipe[1]);
+ while (write(errpipe[1], &errno, sizeof(int)) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+ _exit(1);
+ }
+ close(errpipe[1]);
+
+ /* No longer need execfd. */
+ if (details->execfd != -1) {
+ close(details->execfd);
+ details->execfd = -1;
+ }
+
+ /* Send the command's pid to main sudo process. */
+ cstat.type = CMD_PID;
+ cstat.val = cmnd_pid;
+ while (send(backchannel, &cstat, sizeof(cstat), 0) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+
+ /* If any of stdin/stdout/stderr are pipes, close them in parent. */
+ if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDIN]);
+ if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDOUT]);
+ if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
+ close(io_fds[SFD_STDERR]);
+
+ /* Put command in its own process group. */
+ cmnd_pgrp = cmnd_pid;
+ setpgid(cmnd_pid, cmnd_pgrp);
+
+ /* Make the command the foreground process for the pty slave. */
+ if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
+ do {
+ n = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
+ } 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.
+ * The event loop ends when the child is no longer running and
+ * the error pipe is closed.
+ */
+ (void) sudo_ev_loop(mc.evbase, 0);
+ if (cmnd_pid != -1) {
+ /* XXX An error occurred, should send a message back. */
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "Command still running after event loop exit, sending SIGKILL");
+ kill(cmnd_pid, SIGKILL);
+ /* XXX - wait for cmnd_pid to exit */
+ } else {
+ /* Send parent status. */
+ send_status(backchannel, &cstat);
+ }
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_restore_tty() != 0)
+ sudo_warnx(U_("unable to restore tty label"));
+ }
+#endif
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
+ _exit(1);
+
+bad:
+ debug_return_int(errno);
+}
--- /dev/null
+/*
+ * Copyright (c) 2009-2017 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "sudo.h"
+#include "sudo_exec.h"
+#include "sudo_event.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+struct exec_closure_nopty {
+ pid_t child;
+ struct command_status *cstat;
+ struct command_details *details;
+ struct sudo_event_base *evbase;
+ struct sudo_event *signal_event;
+ struct sudo_event *errpipe_event;
+};
+
+static void signal_pipe_cb(int fd, int what, void *v);
+#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 */
+static void
+errpipe_cb(int fd, int what, void *v)
+{
+ struct exec_closure_nopty *ec = v;
+ ssize_t n;
+ int errval;
+ debug_decl(errpipe_cb, SUDO_DEBUG_EXEC)
+
+ /* read errno from child or EOF when command is executed. */
+ n = read(fd, &errval, sizeof(errval));
+ switch (n) {
+ case -1:
+ switch (errno) {
+ case EINTR:
+ /* got a signal, restart loop to service it. */
+ sudo_ev_loopcontinue(ec->evbase);
+ break;
+ case EAGAIN:
+ /* not ready after all... */
+ break;
+ default:
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "failed to read error pipe: %s", strerror(errno));
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ sudo_ev_loopbreak(ec->evbase);
+ break;
+ }
+ break;
+ case 0:
+ /*
+ * We get EOF when the command is executed and the other
+ * end of the error pipe is closed. Just remove the event.
+ */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "EOF on error pipe, removing event");
+ sudo_ev_del(ec->evbase, ec->errpipe_event);
+ break;
+ default:
+ /* Errno value when child is unable to execute command. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s",
+ strerror(errval));
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errval;
+ sudo_ev_del(ec->evbase, ec->errpipe_event);
+ break;
+ }
+ debug_return;
+}
+
+/*
+ * Fill in the exec closure and setup initial exec events.
+ * Allocates events for the signal pipe and error pipe.
+ */
+static void
+fill_exec_closure_nopty(struct exec_closure_nopty *ec,
+ struct command_status *cstat, struct command_details *details, int errfd)
+{
+ debug_decl(fill_exec_closure_nopty, SUDO_DEBUG_EXEC)
+
+ /* Fill in the non-event part of the closure. */
+ ec->child = cmnd_pid;
+ ec->cstat = cstat;
+ ec->details = details;
+
+ /* Setup event base and events. */
+ ec->evbase = sudo_ev_base_alloc();
+ if (ec->evbase == 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. */
+ ec->errpipe_event = sudo_ev_alloc(errfd,
+ SUDO_EV_READ|SUDO_EV_PERSIST, errpipe_cb, ec);
+ if (ec->errpipe_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->errpipe_event, NULL, false) == -1)
+ 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);
+
+ debug_return;
+}
+
+/*
+ * Execute a command and wait for it to finish.
+ */
+int
+exec_nopty(struct command_details *details, struct command_status *cstat)
+{
+ struct exec_closure_nopty ec;
+ sigaction_t sa;
+ int errpipe[2];
+ debug_decl(exec_nopty, SUDO_DEBUG_EXEC)
+
+ /*
+ * We use a pipe to get errno if execve(2) fails in the child.
+ */
+ if (pipe(errpipe) == -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
+ * or certain pam modules won't be able to track their state.
+ */
+ if (policy_init_session(details) != true)
+ sudo_fatalx(U_("policy plugin failed session initialization"));
+
+ ppgrp = getpgrp(); /* parent's process group */
+
+ cmnd_pid = sudo_debug_fork();
+ switch (cmnd_pid) {
+ case -1:
+ sudo_fatal(U_("unable to fork"));
+ break;
+ case 0:
+ /* child */
+ close(errpipe[0]);
+ close(signal_pipe[0]);
+ close(signal_pipe[1]);
+ (void)fcntl(errpipe[1], F_SETFD, FD_CLOEXEC);
+ exec_cmnd(details, errpipe[1]);
+ while (write(errpipe[1], &errno, sizeof(int)) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
+ _exit(1);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
+ (int)cmnd_pid);
+ close(errpipe[1]);
+
+ /* No longer need execfd. */
+ if (details->execfd != -1) {
+ close(details->execfd);
+ details->execfd = -1;
+ }
+
+ /* Set command timeout if specified. */
+ if (ISSET(details->flags, CD_SET_TIMEOUT))
+ alarm(details->timeout);
+
+ /*
+ * Fill in exec closure, allocate event base and two persistent events:
+ * the signal pipe and the error pipe.
+ */
+ fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]);
+
+ /*
+ * Non-pty event loop.
+ * Wait for command to exit, handles signals and the error pipe.
+ */
+ if (sudo_ev_loop(ec.evbase, 0) == -1)
+ sudo_warn(U_("error in event loop"));
+ if (sudo_ev_got_break(ec.evbase)) {
+ /* error from callback */
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
+ /* kill command */
+ terminate_command(ec.child, true);
+ }
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_restore_tty() != 0)
+ sudo_warnx(U_("unable to restore tty label"));
+ }
+#endif
+
+ /* Free things up. */
+ sudo_ev_base_free(ec.evbase);
+ sudo_ev_free(ec.signal_event);
+ sudo_ev_free(ec.errpipe_event);
+ debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);
+}
+
+/*
+ * Forward a signal to the command (non-pty version) or handle
+ * changes to the command's status (SIGCHLD).
+ * XXX - separate SIGCHLD code?
+ */
+static void
+dispatch_signal(struct exec_closure_nopty *ec, int signo, char *signame)
+{
+ debug_decl(dispatch_signal, SUDO_DEBUG_EXEC)
+
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: evbase %p, child: %d, signo %s(%d), cstat %p",
+ __func__, ec->evbase, (int)ec->child, signame, signo, ec->cstat);
+
+ if (ec->child == -1)
+ goto done;
+
+ if (signo == SIGCHLD) {
+ pid_t pid;
+ int status;
+ /*
+ * The command stopped or exited.
+ */
+ do {
+ pid = waitpid(ec->child, &status, WUNTRACED|WNOHANG);
+ } while (pid == -1 && errno == EINTR);
+ if (pid == ec->child) {
+ if (WIFSTOPPED(status)) {
+ /*
+ * Save the controlling terminal's process group
+ * so we can restore it after we resume, if needed.
+ * Most well-behaved shells change the pgrp back to
+ * its original value before suspending so we must
+ * not try to restore in that case, lest we race with
+ * the child upon resume, potentially stopping sudo
+ * with SIGTTOU while the command continues to run.
+ */
+ sigaction_t sa, osa;
+ pid_t saved_pgrp = -1;
+ int signo = WSTOPSIG(status);
+ int fd = open(_PATH_TTY, O_RDWR);
+ if (fd != -1) {
+ saved_pgrp = tcgetpgrp(fd);
+ if (saved_pgrp == -1) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ if (saved_pgrp != -1) {
+ /*
+ * Child was stopped trying to access controlling
+ * terminal. If the child has a different pgrp
+ * and we own the controlling terminal, give it
+ * to the child's pgrp and let it continue.
+ */
+ if (signo == SIGTTOU || signo == SIGTTIN) {
+ if (saved_pgrp == ppgrp) {
+ pid_t child_pgrp = getpgid(ec->child);
+ if (child_pgrp != ppgrp) {
+ if (tcsetpgrp(fd, child_pgrp) == 0) {
+ if (killpg(child_pgrp, SIGCONT) != 0) {
+ sudo_warn("kill(%d, SIGCONT)",
+ (int)child_pgrp);
+ }
+ close(fd);
+ goto done;
+ }
+ }
+ }
+ }
+ }
+ if (signo == SIGTSTP) {
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ if (sudo_sigaction(SIGTSTP, &sa, &osa) != 0) {
+ sudo_warn(U_("unable to set handler for signal %d"),
+ SIGTSTP);
+ }
+ }
+ if (kill(getpid(), signo) != 0)
+ sudo_warn("kill(%d, SIG%s)", (int)getpid(), signame);
+ if (signo == SIGTSTP) {
+ if (sudo_sigaction(SIGTSTP, &osa, NULL) != 0) {
+ sudo_warn(U_("unable to restore handler for signal %d"),
+ SIGTSTP);
+ }
+ }
+ if (saved_pgrp != -1) {
+ /*
+ * Restore command's process group if different.
+ * Otherwise, we cannot resume some shells.
+ */
+ if (saved_pgrp != ppgrp)
+ (void)tcsetpgrp(fd, saved_pgrp);
+ close(fd);
+ }
+ } else {
+ /* Child has exited or been killed, we are done. */
+ ec->child = -1;
+ /* Don't overwrite execve() failure with child exit status. */
+ if (ec->cstat->type != CMD_ERRNO) {
+ ec->cstat->type = CMD_WSTATUS;
+ ec->cstat->val = status;
+ }
+ sudo_ev_del(ec->evbase, ec->signal_event);
+ sudo_ev_loopexit(ec->evbase);
+ goto done;
+ }
+ }
+ } else {
+ /* 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);
+ }
+ }
+done:
+ 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, "received SIG%s", signame);
+ /* XXX - deliver vs. SIGCHLD? */
+ dispatch_signal(ec, signo, 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 */
#include <sys/types.h>
#include <sys/socket.h>
-#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <stdio.h>
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <unistd.h>
-#ifdef TIME_WITH_SYS_TIME
-# include <time.h>
-#endif
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
-#include <termios.h>
#include "sudo.h"
#include "sudo_event.h"
#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
-#define SFD_STDIN 0
-#define SFD_STDOUT 1
-#define SFD_STDERR 2
-#define SFD_MASTER 3
-#define SFD_SLAVE 4
-#define SFD_USERTTY 5
-
/* Evaluates to true if the event has /dev/tty as its fd. */
#define USERTTY_EVENT(_ev) (sudo_ev_get_fd((_ev)) == io_fds[SFD_USERTTY])
#define TERM_COOKED 0
#define TERM_RAW 1
+/* We keep a tailq of signals to forward to child. */
+struct sigforward {
+ TAILQ_ENTRY(sigforward) entries;
+ int signo;
+};
+TAILQ_HEAD(sigfwd_list, sigforward);
+
+struct exec_closure_pty {
+ pid_t child;
+ sigset_t *omask;
+ struct command_status *cstat;
+ struct command_details *details;
+ struct sudo_event_base *evbase;
+ struct sudo_event *signal_event;
+ struct sudo_event *sigfwd_event;
+ struct sudo_event *backchannel_event;
+ struct sigfwd_list sigfwd_list;
+};
+
/*
* I/O buffer with associated read/write events and a logging action.
* Used to, e.g. pass data from the pty to the user's terminal
SLIST_HEAD(io_buffer_list, io_buffer);
static char slavename[PATH_MAX];
-static bool foreground, pipeline, tty_initialized;
-static int io_fds[6] = { -1, -1, -1, -1, -1, -1};
+int io_fds[6] = { -1, -1, -1, -1, -1, -1}; /* XXX - sudo_exec.h? */
+static bool foreground, pipeline;
+static bool tty_initialized;
static int ttymode = TERM_COOKED;
-static pid_t ppgrp, cmnd_pgrp, mon_pgrp;
static sigset_t ttyblock;
static struct io_buffer_list iobufs;
+static const char *utmp_user;
+static int fork_pty(struct command_details *details, int sv[], sigset_t *omask);
static void del_io_events(bool nonblocking);
-static int exec_monitor(struct command_details *details, int backchannel);
-static void exec_pty(struct command_details *details,
- struct command_status *cstat, int errfd);
static void sigwinch(int s);
static void sync_ttysize(int src, int dst);
-static void deliver_signal(pid_t pid, int signo, bool from_parent);
static int safe_close(int fd);
static void ev_free_by_fd(struct sudo_event_base *evbase, int fd);
static void check_foreground(void);
+static void add_io_events(struct sudo_event_base *evbase);
/*
* Cleanup hook for sudo_fatal()/sudo_fatalx()
*/
-static void
+void
pty_cleanup(void)
{
debug_decl(cleanup, SUDO_DEBUG_EXEC);
if (!TAILQ_EMPTY(&io_plugins) && io_fds[SFD_USERTTY] != -1)
sudo_term_restore(io_fds[SFD_USERTTY], false);
-#ifdef HAVE_SELINUX
- selinux_restore_tty();
-#endif
- utmp_logout(slavename, 0); /* XXX - only if CD_SET_UTMP */
+ if (utmp_user != NULL)
+ utmp_logout(slavename, 0);
debug_return;
}
-/*
- * 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
-
/*
* Allocate a pty if /dev/tty is a tty.
* Fills in io_fds[SFD_USERTTY], io_fds[SFD_MASTER], io_fds[SFD_SLAVE]
* and slavename globals.
*/
-void
-pty_setup(uid_t uid, const char *tty, const char *utmp_user)
+static void
+pty_setup(uid_t uid, const char *tty)
{
debug_decl(pty_setup, SUDO_DEBUG_EXEC);
debug_return;
}
+int
+pty_make_controlling(void)
+{
+ if (io_fds[SFD_USERTTY] != -1) {
+#ifdef TIOCSCTTY
+ if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
+ return -1;
+#else
+ /* Set controlling tty by reopening slave. */
+ int fd = open(slavename, O_RDWR);
+ if (fd == -1)
+ return -1;
+ close(fd);
+#endif
+ }
+ return 0;
+}
+
/* Call I/O plugin tty input log method. */
static bool
log_ttyin(const char *buf, unsigned int n, struct io_buffer *iob)
* Returns SIGCONT_FG if the command should be resumed in the
* foreground or SIGCONT_BG if it is a background process.
*/
-int
-suspend_parent(int signo)
+static int
+suspend_sudo(int signo)
{
char signame[SIG2STR_MAX];
sigaction_t sa, osa;
int ret = 0;
- debug_decl(suspend_parent, SUDO_DEBUG_EXEC);
+ debug_decl(suspend_sudo, SUDO_DEBUG_EXEC);
switch (signo) {
case SIGTTOU:
debug_return_int(ret);
}
-/*
- * Kill command with increasing urgency.
- */
-void
-terminate_command(pid_t pid, bool use_pgrp)
-{
- debug_decl(terminate_command, SUDO_DEBUG_EXEC);
-
- /* Avoid killing more than a single process or process group. */
- if (pid <= 0)
- debug_return;
-
- /*
- * Note that SIGCHLD will interrupt the sleep()
- */
- if (use_pgrp) {
- sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGHUP", (int)pid);
- killpg(pid, SIGHUP);
- sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGTERM", (int)pid);
- killpg(pid, SIGTERM);
- sleep(2);
- sudo_debug_printf(SUDO_DEBUG_INFO, "killpg %d SIGKILL", (int)pid);
- killpg(pid, SIGKILL);
- } else {
- sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGHUP", (int)pid);
- kill(pid, SIGHUP);
- sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGTERM", (int)pid);
- kill(pid, SIGTERM);
- sleep(2);
- sudo_debug_printf(SUDO_DEBUG_INFO, "kill %d SIGKILL", (int)pid);
- kill(pid, SIGKILL);
- }
-
- debug_return;
-}
-
/*
* Read an iobuf that is ready.
*/
* process with std{in,out,err} hooked up to the pty or pipes as appropriate.
* Returns the child pid.
*/
-int
+static int
fork_pty(struct command_details *details, int sv[], sigset_t *omask)
{
struct command_status cstat;
io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1];
}
- /* We don't want to receive SIGTTIN/SIGTTOU, getting EIO is preferable. */
- 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);
-
- /* Job control signals to relay from parent to child. */
- sigfillset(&sa.sa_mask);
- sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
-#ifdef SA_SIGINFO
- sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = handler;
-#else
- sa.sa_handler = handler;
-#endif
- if (sudo_sigaction(SIGCHLD, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGCHLD);
- if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
-
if (foreground) {
/* Copy terminal attrs from user tty -> pty slave. */
if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) {
close(io_pipe[STDOUT_FILENO][0]);
if (io_pipe[STDERR_FILENO][0])
close(io_pipe[STDERR_FILENO][0]);
- exec_monitor(details, sv[1]);
+ /*
+ * If stdin/stdout is not a tty, start command in the background
+ * since it might be part of a pipeline that reads from /dev/tty.
+ * In this case, we rely on the command receiving SIGTTOU or SIGTTIN
+ * when it needs access to the controlling tty.
+ */
+ exec_monitor(details, foreground && !pipeline, sv[1]);
cstat.type = CMD_ERRNO;
cstat.val = errno;
- ignore_result(send(sv[1], &cstat, sizeof(cstat), 0));
+ while (send(sv[1], &cstat, sizeof(cstat), 0) == -1) {
+ if (errno != EINTR)
+ break;
+ }
_exit(1);
}
debug_return_int(child);
}
-void
+static void
pty_close(struct command_status *cstat)
{
struct io_buffer *iob;
n = io_fds[SFD_USERTTY] != -1 ?
io_fds[SFD_USERTTY] : STDOUT_FILENO;
if (write(n, reason, strlen(reason)) != -1) {
- if (WCOREDUMP(cstat->val)) {
+ if (WCOREDUMP(cstat->val))
ignore_result(write(n, " (core dumped)", 14));
- }
ignore_result(write(n, "\n", 1));
}
}
}
- utmp_logout(slavename, cstat->type == CMD_WSTATUS ? cstat->val : 0); /* XXX - only if CD_SET_UTMP */
+ if (utmp_user != NULL)
+ utmp_logout(slavename, cstat->type == CMD_WSTATUS ? cstat->val : 0);
debug_return;
}
/*
- * Schedule I/O events before starting the main event loop or
- * resuming from suspend.
+ * Schedule a signal to be forwarded.
*/
-void
-add_io_events(struct sudo_event_base *evbase)
+static void
+schedule_signal(struct exec_closure_pty *ec, int signo)
{
- struct io_buffer *iob;
- debug_decl(add_io_events, SUDO_DEBUG_EXEC);
+ struct sigforward *sigfwd;
+ char signame[SIG2STR_MAX];
+ debug_decl(schedule_signal, SUDO_DEBUG_EXEC)
+
+ if (signo == SIGCONT_FG)
+ strlcpy(signame, "CONT_FG", sizeof(signame));
+ else if (signo == SIGCONT_BG)
+ strlcpy(signame, "CONT_BG", sizeof(signame));
+ else if (sig2str(signo, signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for child", signame);
+
+ if ((sigfwd = calloc(1, sizeof(*sigfwd))) == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ sigfwd->signo = signo;
+ TAILQ_INSERT_TAIL(&ec->sigfwd_list, sigfwd, entries);
+
+ if (sudo_ev_add(ec->evbase, ec->sigfwd_event, NULL, true) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
- /*
- * Schedule all readers as long as the buffer is not full.
- * Schedule writers that contain buffered data.
- * Normally, write buffers are added on demand when data is read.
- */
- SLIST_FOREACH(iob, &iobufs, entries) {
- /* Don't read/write from /dev/tty if we are not in the foreground. */
- if (iob->revent != NULL &&
- (ttymode == TERM_RAW || !USERTTY_EVENT(iob->revent))) {
- if (iob->len != sizeof(iob->buf)) {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "added I/O revent %p, fd %d, events %d",
- iob->revent, iob->revent->fd, iob->revent->events);
- if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
- }
- }
- if (iob->wevent != NULL &&
- (foreground || !USERTTY_EVENT(iob->wevent))) {
- if (iob->len > iob->off) {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "added I/O wevent %p, fd %d, events %d",
- iob->wevent, iob->wevent->fd, iob->wevent->events);
- if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
- }
- }
- }
debug_return;
}
-/*
- * Flush any output buffered in iobufs or readable from fds other
- * than /dev/tty. Removes I/O events from the event base when done.
- */
static void
-del_io_events(bool nonblocking)
+backchannel_cb(int fd, int what, void *v)
{
- struct io_buffer *iob;
- struct sudo_event_base *evbase;
- debug_decl(del_io_events, SUDO_DEBUG_EXEC);
+ struct exec_closure_pty *ec = v;
+ ssize_t n;
+ debug_decl(backchannel_cb, SUDO_DEBUG_EXEC)
- /* Remove iobufs from existing event base. */
- SLIST_FOREACH(iob, &iobufs, entries) {
- if (iob->revent != NULL) {
- sudo_debug_printf(SUDO_DEBUG_INFO,
- "deleted I/O revent %p, fd %d, events %d",
- iob->revent, iob->revent->fd, iob->revent->events);
- sudo_ev_del(NULL, iob->revent);
+ /* read child status */
+ n = recv(fd, ec->cstat, sizeof(struct command_status), MSG_WAITALL);
+ if (n != sizeof(struct command_status)) {
+ if (n == -1) {
+ switch (errno) {
+ case EINTR:
+ /* got a signal, restart loop to service it. */
+ sudo_ev_loopcontinue(ec->evbase);
+ break;
+ case EAGAIN:
+ /* not ready after all... */
+ break;
+ default:
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "failed to read child status: %s", strerror(errno));
+ sudo_ev_loopbreak(ec->evbase);
+ break;
+ }
+ } else {
+ /* Short read or EOF. */
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "failed to read child status: %s", n ? "short read" : "EOF");
+ /* XXX - need new CMD_ type for monitor errors. */
+ errno = n ? EIO : ECONNRESET;
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ sudo_ev_loopbreak(ec->evbase);
}
- if (iob->wevent != NULL) {
+ debug_return;
+ }
+ switch (ec->cstat->type) {
+ case CMD_PID:
+ /*
+ * Once we know the command's pid we can unblock
+ * signals which ere blocked in fork_pty(). This
+ * avoids a race between exec of the command and
+ * receipt of a fatal signal from it.
+ */
+ cmnd_pid = ec->cstat->val;
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
+ ec->details->command, (int)cmnd_pid);
+ sigprocmask(SIG_SETMASK, ec->omask, NULL);
+ break;
+ case CMD_WSTATUS:
+ if (WIFSTOPPED(ec->cstat->val)) {
+ /* Suspend parent and tell child how to resume on return. */
sudo_debug_printf(SUDO_DEBUG_INFO,
- "deleted I/O wevent %p, fd %d, events %d",
- iob->wevent, iob->wevent->fd, iob->wevent->events);
- sudo_ev_del(NULL, iob->wevent);
+ "child stopped, suspending parent");
+ n = suspend_sudo(WSTOPSIG(ec->cstat->val));
+ schedule_signal(ec, n);
+ /* Re-enable I/O events and restart event loop to service signal. */
+ add_io_events(ec->evbase);
+ sudo_ev_loopcontinue(ec->evbase);
+ } else {
+ /* Child exited or was killed, either way we are done. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "child exited or was killed");
+ sudo_ev_loopexit(ec->evbase);
}
+ break;
+ case CMD_ERRNO:
+ /* Child was unable to execute command or broken pipe. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s",
+ strerror(ec->cstat->val));
+ sudo_ev_loopbreak(ec->evbase);
+ break;
}
+ debug_return;
+}
- /* Create temporary event base for flushing. */
- evbase = sudo_ev_base_alloc();
- if (evbase == NULL)
- sudo_fatal(NULL);
+/*
+ * Forward a signal to the monitor (pty version) or handle
+ * changes to the monitors's status (SIGCHLD).
+ */
+static void
+dispatch_signal_pty(struct exec_closure_pty *ec, int signo, char *signame)
+{
+ debug_decl(dispatch_signal_pty, SUDO_DEBUG_EXEC)
- /* Avoid reading from /dev/tty, just flush existing data. */
- SLIST_FOREACH(iob, &iobufs, entries) {
- /* Don't read from /dev/tty while flushing. */
- if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) {
- if (iob->len != sizeof(iob->buf)) {
- if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
- }
- }
- /* Flush any write buffers with data in them. */
- if (iob->wevent != NULL) {
- if (iob->len > iob->off) {
- if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
- }
- }
- }
- (void) sudo_ev_loop(evbase, SUDO_EVLOOP_NONBLOCK);
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "%s: evbase %p, child: %d, signo %s(%d), cstat %p",
+ __func__, ec->evbase, (int)ec->child, signame, signo, ec->cstat);
- /*
- * If not in non-blocking mode, make sure we flush write buffers.
- * We don't want to read from the pty or stdin since that might block
- * and the command is no longer running anyway.
- */
- if (!nonblocking) {
- /* Clear out iobufs from event base. */
- SLIST_FOREACH(iob, &iobufs, entries) {
- if (iob->revent != NULL && !USERTTY_EVENT(iob->revent))
- sudo_ev_del(evbase, iob->revent);
- if (iob->wevent != NULL)
- sudo_ev_del(evbase, iob->wevent);
- }
+ if (ec->child == -1)
+ goto done;
- SLIST_FOREACH(iob, &iobufs, entries) {
- /* Flush any write buffers with data in them. */
- if (iob->wevent != NULL) {
- if (iob->len > iob->off) {
- if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
- }
- }
- }
- (void) sudo_ev_loop(evbase, 0);
-
- /* We should now have flushed all write buffers. */
- SLIST_FOREACH(iob, &iobufs, entries) {
- if (iob->wevent != NULL) {
- if (iob->len > iob->off) {
- sudo_debug_printf(SUDO_DEBUG_ERROR,
- "unflushed data: wevent %p, fd %d, events %d",
- iob->wevent, iob->wevent->fd, iob->wevent->events);
- }
+ if (signo == SIGCHLD) {
+ int n, status;
+ pid_t pid;
+ /*
+ * Monitor process was signaled; wait for it as needed.
+ */
+ do {
+ pid = waitpid(ec->child, &status, WUNTRACED|WNOHANG);
+ } while (pid == -1 && errno == EINTR);
+ if (pid == ec->child) {
+ /*
+ * If the monitor dies we get notified via backchannel_cb().
+ * If it was stopped, we should stop too (the command keeps
+ * running in its pty) and continue it when we come back.
+ */
+ if (WIFSTOPPED(status)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "monitor stopped, suspending parent");
+ n = suspend_sudo(WSTOPSIG(status));
+ kill(pid, SIGCONT);
+ schedule_signal(ec, n);
+ /* Re-enable I/O events and restart event loop. */
+ add_io_events(ec->evbase);
+ sudo_ev_loopcontinue(ec->evbase);
+ goto done;
+ } else if (WIFSIGNALED(status)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "monitor killed, signal %d", WTERMSIG(status));
+ ec->child = -1;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "monitor exited, status %d", WEXITSTATUS(status));
+ ec->child = -1;
}
}
+ } else {
+ /* Schedule signo to be forwared to the child. */
+ schedule_signal(ec, signo);
+ /* Restart event loop to service signal immediately. */
+ sudo_ev_loopcontinue(ec->evbase);
}
-
- /* Free temporary event base, removing its events. */
- sudo_ev_base_free(evbase);
-
+done:
debug_return;
}
+/* Signal pipe callback */
static void
-deliver_signal(pid_t pid, int signo, bool from_parent)
+signal_pipe_cb(int fd, int what, void *v)
{
+ struct exec_closure_pty *ec = v;
char signame[SIG2STR_MAX];
- int status;
- debug_decl(deliver_signal, SUDO_DEBUG_EXEC);
-
- /* Avoid killing more than a single process or process group. */
- if (pid <= 0)
- debug_return;
+ unsigned char signo;
+ ssize_t nread;
+ debug_decl(signal_pipe_cb, SUDO_DEBUG_EXEC)
- if (signo == SIGCONT_FG)
- strlcpy(signame, "CONT_FG", sizeof(signame));
- else if (signo == SIGCONT_BG)
- strlcpy(signame, "CONT_BG", sizeof(signame));
- else if (sig2str(signo, signame) == -1)
- snprintf(signame, sizeof(signame), "%d", signo);
-
- /* Handle signal from parent. */
- sudo_debug_printf(SUDO_DEBUG_INFO, "received SIG%s%s",
- signame, from_parent ? " from parent" : "");
- switch (signo) {
- case SIGALRM:
- terminate_command(pid, true);
- break;
- case SIGCONT_FG:
- /* Continue in foreground, grant it controlling tty. */
- do {
- status = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
- } while (status == -1 && errno == EINTR);
- killpg(pid, SIGCONT);
- break;
- case SIGCONT_BG:
- /* Continue in background, I take controlling tty. */
- do {
- status = tcsetpgrp(io_fds[SFD_SLAVE], mon_pgrp);
- } while (status == -1 && errno == EINTR);
- killpg(pid, SIGCONT);
- break;
- case SIGKILL:
- _exit(1); /* XXX */
- /* NOTREACHED */
- default:
- /* Relay signal to command. */
- killpg(pid, signo);
- break;
- }
+ 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, "received SIG%s", signame);
+ dispatch_signal_pty(ec, signo, signame);
+ } while (ec->child != -1);
debug_return;
}
/*
- * Send status to parent over socketpair.
- * Return value is the same as send(2).
+ * Forward signals in sigfwd_list to the monitor so it can
+ * deliver them to the command.
*/
-static int
-send_status(int fd, struct command_status *cstat)
+static void
+sigfwd_cb(int sock, int what, void *v)
{
- int n = -1;
- debug_decl(send_status, SUDO_DEBUG_EXEC);
-
- if (cstat->type != CMD_INVALID) {
+ struct exec_closure_pty *ec = v;
+ char signame[SIG2STR_MAX];
+ struct sigforward *sigfwd;
+ struct command_status cstat;
+ ssize_t nsent;
+ debug_decl(sigfwd_cb, SUDO_DEBUG_EXEC)
+
+ while (!TAILQ_EMPTY(&ec->sigfwd_list)) {
+ sigfwd = TAILQ_FIRST(&ec->sigfwd_list);
+ if (sigfwd->signo == SIGCONT_FG)
+ strlcpy(signame, "CONT_FG", sizeof(signame));
+ else if (sigfwd->signo == SIGCONT_BG)
+ strlcpy(signame, "CONT_BG", sizeof(signame));
+ else if (sig2str(sigfwd->signo, signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", sigfwd->signo);
sudo_debug_printf(SUDO_DEBUG_INFO,
- "sending status message to parent: [%d, %d]",
- cstat->type, cstat->val);
+ "sending SIG%s to child over backchannel", signame);
+ cstat.type = CMD_SIGNO;
+ cstat.val = sigfwd->signo;
do {
- n = send(fd, cstat, sizeof(*cstat), 0);
- } while (n == -1 && errno == EINTR);
- if (n != sizeof(*cstat)) {
- sudo_debug_printf(SUDO_DEBUG_ERROR,
- "unable to send status to parent: %s", strerror(errno));
+ nsent = send(sock, &cstat, sizeof(cstat), 0);
+ } while (nsent == -1 && errno == EINTR);
+ TAILQ_REMOVE(&ec->sigfwd_list, sigfwd, entries);
+ free(sigfwd);
+ if (nsent != sizeof(cstat)) {
+ if (errno == EPIPE) {
+ struct sigforward *sigfwd_next;
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "broken pipe writing to child over backchannel");
+ /* Other end of socket gone, empty out sigfwd_list. */
+ TAILQ_FOREACH_SAFE(sigfwd, &ec->sigfwd_list, entries, sigfwd_next) {
+ free(sigfwd);
+ }
+ TAILQ_INIT(&ec->sigfwd_list);
+ /* XXX - child (monitor) is dead, we should exit too? */
+ }
+ break;
}
- cstat->type = CMD_INVALID; /* prevent re-sending */
}
- debug_return_int(n);
}
/*
- * Wait for command status after receiving SIGCHLD.
- * If the command was stopped, the status is send back to the parent.
- * Otherwise, cstat is filled in but not sent.
+ * Fill in the exec closure and setup initial exec events.
+ * Allocates events for the signal pipe and backchannel.
+ * Forwarded signals on the backchannel are enabled on demand.
*/
static void
-handle_sigchld(int backchannel, struct command_status *cstat)
+fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat,
+ struct command_details *details, pid_t child, sigset_t *omask,
+ int backchannel)
{
- char signame[SIG2STR_MAX];
- int status;
- pid_t pid;
- debug_decl(handle_sigchld, SUDO_DEBUG_EXEC);
-
- /* Read command status. */
- do {
- pid = waitpid(cmnd_pid, &status, WUNTRACED|WCONTINUED|WNOHANG);
- } while (pid == -1 && errno == EINTR);
- switch (pid) {
- case 0:
- errno = ECHILD;
- /* FALLTHROUGH */
- case -1:
- sudo_debug_printf(SUDO_DEBUG_DIAG,
- "waitpid returned %d, expected pid %d", pid, cmnd_pid);
- sudo_warn(U_("%s: %s"), __func__, "waitpid");
- debug_return;
- }
-
- if (WIFCONTINUED(status)) {
- sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) resumed",
- __func__, cmnd_pid);
- } else if (WIFSTOPPED(status)) {
- if (sig2str(WSTOPSIG(status), signame) == -1)
- snprintf(signame, sizeof(signame), "%d", WSTOPSIG(status));
- sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s",
- __func__, cmnd_pid, signame);
- } else if (WIFSIGNALED(status)) {
- if (sig2str(WTERMSIG(status), signame) == -1)
- snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
- sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
- __func__, cmnd_pid, signame);
- cmnd_pid = -1;
- } else if (WIFEXITED(status)) {
- sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
- __func__, cmnd_pid, WEXITSTATUS(status));
- cmnd_pid = -1;
- } else {
- sudo_debug_printf(SUDO_DEBUG_WARN,
- "%s: unexpected wait status %d for command (%d)",
- __func__, status, (int)cmnd_pid);
- }
-
- /* Don't overwrite execve() failure with child exit status. */
- if (cstat->type != CMD_ERRNO) {
- /*
- * Store wait status in cstat and forward to parent if stopped.
- */
- cstat->type = CMD_WSTATUS;
- cstat->val = status;
- if (WIFSTOPPED(status)) {
- /* Save the foreground pgid so we can restore it later. */
- do {
- pid = tcgetpgrp(io_fds[SFD_SLAVE]);
- } while (pid == -1 && errno == EINTR);
- if (pid != mon_pgrp)
- cmnd_pgrp = pid;
- send_status(backchannel, cstat);
- }
- }
-
- debug_return;
-}
-
-struct monitor_closure {
- struct sudo_event_base *evbase;
- struct sudo_event *errpipe_event;
- struct sudo_event *backchannel_event;
- struct sudo_event *signal_pipe_event;
- struct command_status *cstat;
- int backchannel;
-};
-
-static void
-mon_signal_pipe_cb(int fd, int what, void *v)
-{
- struct monitor_closure *mc = v;
- unsigned char signo;
- ssize_t nread;
- 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
- * directly to the command.
- */
- if (signo == SIGCHLD) {
- handle_sigchld(mc->backchannel, mc->cstat);
- if (cmnd_pid == -1) {
- /* Remove all but the errpipe event. */
- sudo_ev_del(mc->evbase, mc->backchannel_event);
- sudo_ev_del(mc->evbase, mc->signal_pipe_event);
- }
- } else {
- deliver_signal(cmnd_pid, signo, false);
- }
- }
- debug_return;
-}
+ debug_decl(fill_exec_closure_pty, SUDO_DEBUG_EXEC)
+
+ /* Fill in the non-event part of the closure. */
+ ec->child = child;
+ ec->omask = omask;
+ ec->cstat = cstat;
+ ec->details = details;
+ TAILQ_INIT(&ec->sigfwd_list);
+
+ /* Setup event base and events. */
+ ec->evbase = sudo_ev_base_alloc();
+ if (ec->evbase == NULL)
+ sudo_fatal(NULL);
-static void
-mon_errpipe_cb(int fd, int what, void *v)
-{
- struct monitor_closure *mc = v;
- ssize_t n;
- debug_decl(mon_errpipe_cb, SUDO_DEBUG_EXEC);
+ /* 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"));
- /* read errno or EOF from command pipe */
- n = read(fd, mc->cstat, sizeof(struct command_status));
- if (n == -1) {
- if (errno != EINTR && errno != EAGAIN) {
- sudo_warn(U_("error reading from pipe"));
- sudo_ev_loopbreak(mc->evbase);
- }
- } else {
- /* Got errno or EOF, either way we are done with errpipe. */
- sudo_debug_printf(SUDO_DEBUG_DIAG, "%s: type: %d, val: %d",
- __func__, mc->cstat->type, mc->cstat->val);
- sudo_ev_del(mc->evbase, mc->errpipe_event);
- close(fd);
- }
- debug_return;
-}
+ /* Event for command status via backchannel. */
+ ec->backchannel_event = sudo_ev_alloc(backchannel,
+ SUDO_EV_READ|SUDO_EV_PERSIST, backchannel_cb, ec);
+ if (ec->backchannel_event == NULL)
+ sudo_fatal(NULL);
+ if (sudo_ev_add(ec->evbase, ec->backchannel_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
-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);
+ /* The signal forwarding event gets added on demand. */
+ ec->sigfwd_event = sudo_ev_alloc(backchannel,
+ SUDO_EV_WRITE, sigfwd_cb, ec);
+ if (ec->sigfwd_event == NULL)
+ sudo_fatal(NULL);
- /* Read command from backchannel, should be a signal. */
- n = recv(fd, &cstmp, sizeof(cstmp), MSG_WAITALL);
- if (n != sizeof(cstmp)) {
- if (n == -1) {
- if (errno == EINTR || errno == EAGAIN)
- debug_return;
- sudo_warn(U_("error reading from socketpair"));
- } else {
- /* short read or EOF, parent process died? */
- }
- sudo_ev_loopbreak(mc->evbase);
- } else {
- if (cstmp.type == CMD_SIGNO) {
- deliver_signal(cmnd_pid, cstmp.val, true);
- } else {
- sudo_warnx(U_("unexpected reply type on backchannel: %d"), cstmp.type);
- }
- }
- debug_return;
+ sudo_debug_printf(SUDO_DEBUG_INFO, "signal pipe fd %d\n", signal_pipe[0]);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "backchannel fd %d\n", backchannel);
}
/*
- * Monitor process that creates a new session with the controlling tty,
- * resets signal handlers and forks a child to call exec_pty().
- * Waits for status changes from the command and relays them to the
- * parent and relays signals from the parent to the command.
- * Returns an error if fork(2) fails, else calls _exit(2).
+ * Execute a command in a pty, potentially with I/O loggging, and
+ * wait for it to finish.
+ * This is a little bit tricky due to how POSIX job control works and
+ * we fact that we have two different controlling terminals to deal with.
*/
-static int
-exec_monitor(struct command_details *details, int backchannel)
+int
+exec_pty(struct command_details *details, struct command_status *cstat)
{
- struct command_status cstat;
- struct sudo_event_base *evbase;
- struct monitor_closure mc;
+ struct sigforward *sigfwd, *sigfwd_next;
+ struct exec_closure_pty ec;
sigaction_t sa;
- int errpipe[2], n;
- debug_decl(exec_monitor, SUDO_DEBUG_EXEC);
+ sigset_t omask;
+ pid_t child;
+ int sv[2];
+ debug_decl(exec_pty, SUDO_DEBUG_EXEC)
- /* Close unused fds. */
- if (io_fds[SFD_MASTER] != -1)
- close(io_fds[SFD_MASTER]);
- if (io_fds[SFD_USERTTY] != -1)
- close(io_fds[SFD_USERTTY]);
+ /*
+ * Allocate a pty.
+ */
+ if (ISSET(details->flags, CD_SET_UTMP))
+ utmp_user = details->utmp_user ? details->utmp_user : user_details.username;
+ sudo_debug_printf(SUDO_DEBUG_INFO, "allocate pty for I/O logging");
+ pty_setup(details->euid, user_details.tty);
/*
- * We use a pipe to atomically handle signal notification within
- * the event loop.
+ * We communicate with the child over a bi-directional pair of sockets.
+ * Parent sends signal info to child and child sends back wait status.
*/
- if (pipe_nonblock(signal_pipe) != 0)
- sudo_fatal(U_("unable to create pipe"));
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1)
+ sudo_fatal(U_("unable to create sockets"));
- /* Reset SIGWINCH and SIGALRM. */
+ /*
+ * Signals to forward 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.
+ */
memset(&sa, 0, sizeof(sa));
- sigemptyset(&sa.sa_mask);
- 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;
- 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);
-
- /* 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;
+ sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
#ifdef SA_SIGINFO
sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = mon_handler;
+ sa.sa_sigaction = exec_handler;
#else
- sa.sa_handler = mon_handler;
+ 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);
-
- /* 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;
+#ifdef SIGINFO
+ if (sudo_sigaction(SIGINFO, &sa, NULL) != 0)
+ sudo_warn(U_("unable to set handler for signal %d"), SIGINFO);
#endif
- if (sudo_sigaction(SIGHUP, &sa, NULL) != 0)
- sudo_warn(U_("unable to set handler for signal %d"), SIGHUP);
+
+ /*
+ * Unlike the non-pty case, we can use our normal signal handler
+ * for tty-generated signals triggered by the user.
+ */
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
- * and the slave pty as the controlling terminal.
- * This allows us to be notified when the command has been suspended.
+ * We don't want to receive SIGTTIN/SIGTTOU, getting EIO is preferable.
*/
- if (setsid() == -1) {
- sudo_warn("setsid");
- goto bad;
- }
- if (io_fds[SFD_SLAVE] != -1) {
-#ifdef TIOCSCTTY
- if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0)
- sudo_fatal(U_("unable to set controlling tty"));
-#else
- /* Set controlling tty by reopening slave. */
- if ((n = open(slavename, O_RDWR)) >= 0)
- close(n);
-#endif
- }
-
- mon_pgrp = getpgrp(); /* save a copy of our process group */
+ 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 stdin/stdout is not a tty, start command in the background
- * since it might be part of a pipeline that reads from /dev/tty.
- * In this case, we rely on the command receiving SIGTTOU or SIGTTIN
- * when it needs access to the controlling tty.
+ * The policy plugin's session init must be run before we fork
+ * or certain pam modules won't be able to track their state.
*/
- if (pipeline)
- foreground = false;
-
- /* Start command and wait for it to stop or exit */
- if (pipe(errpipe) == -1)
- sudo_fatal(U_("unable to create pipe"));
- cmnd_pid = sudo_debug_fork();
- if (cmnd_pid == -1) {
- sudo_warn(U_("unable to fork"));
- goto bad;
- }
- if (cmnd_pid == 0) {
- /* We pass errno back to our parent via pipe on exec failure. */
- close(backchannel);
- close(signal_pipe[0]);
- close(signal_pipe[1]);
- close(errpipe[0]);
- (void)fcntl(errpipe[1], F_SETFD, FD_CLOEXEC);
- restore_signals();
+ if (policy_init_session(details) != true)
+ sudo_fatalx(U_("policy plugin failed session initialization"));
- /* setup tty and exec command */
- exec_pty(details, &cstat, errpipe[1]);
- while (write(errpipe[1], &cstat, sizeof(cstat)) == -1) {
- if (errno != EINTR)
- break;
- }
- _exit(1);
- }
- close(errpipe[1]);
+ /*
+ * Child will run the command in the pty, parent will pass data
+ * to and from pty.
+ * XXX - inline fork_pty or refactor differently?
+ */
+ child = fork_pty(details, sv, &omask);
+ close(sv[1]);
/* No longer need execfd. */
if (details->execfd != -1) {
details->execfd = -1;
}
- /* Send the command's pid to main sudo process. */
- cstat.type = CMD_PID;
- cstat.val = cmnd_pid;
- ignore_result(send(backchannel, &cstat, sizeof(cstat), 0));
-
- /* If any of stdin/stdout/stderr are pipes, close them in parent. */
- if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
- close(io_fds[SFD_STDIN]);
- if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
- close(io_fds[SFD_STDOUT]);
- if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
- close(io_fds[SFD_STDERR]);
-
- /* Put command in its own process group. */
- cmnd_pgrp = cmnd_pid;
- setpgid(cmnd_pid, cmnd_pgrp);
-
- /* Make the command the foreground process for the pty slave. */
- if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
- do {
- n = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
- } while (n == -1 && errno == EINTR);
- }
+ /* Set command timeout if specified. */
+ if (ISSET(details->flags, CD_SET_TIMEOUT))
+ alarm(details->timeout);
/*
- * Create new event base and register read events for the
- * signal pipe, error pipe, and backchannel.
+ * I/O logging must be in the C locale for floating point numbers
+ * to be logged consistently.
*/
- evbase = sudo_ev_base_alloc();
- if (evbase == NULL)
- sudo_fatal(NULL);
+ setlocale(LC_ALL, "C");
- memset(&cstat, 0, sizeof(cstat));
- mc.cstat = &cstat;
- mc.evbase = evbase;
- mc.backchannel = backchannel;
+ /*
+ * Allocate event base and two persistent events:
+ * the signal pipe and the child process's backchannel.
+ */
+ fill_exec_closure_pty(&ec, cstat, details, child, &omask, sv[0]);
- 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(evbase, mc.signal_pipe_event, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
+ /*
+ * In the event loop we pass input from user tty to master
+ * and pass output from master to stdout and IO plugin.
+ */
+ add_io_events(ec.evbase);
+ if (sudo_ev_loop(ec.evbase, 0) == -1)
+ sudo_warn(U_("error in event loop"));
+ if (sudo_ev_got_break(ec.evbase)) {
+ /* error from callback */
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
+ }
- mc.errpipe_event = sudo_ev_alloc(errpipe[0],
- SUDO_EV_READ|SUDO_EV_PERSIST, mon_errpipe_cb, &mc);
- if (mc.errpipe_event == NULL)
- sudo_fatal(NULL);
- if (sudo_ev_add(evbase, mc.errpipe_event, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
+ /* Flush any remaining output and free pty-related memory. */
+ pty_close(cstat);
- mc.backchannel_event = sudo_ev_alloc(backchannel,
- SUDO_EV_READ|SUDO_EV_PERSIST, mon_backchannel_cb, &mc);
- if (mc.backchannel_event == NULL)
- sudo_fatal(NULL);
- if (sudo_ev_add(evbase, mc.backchannel_event, NULL, false) == -1)
- sudo_fatal(U_("unable to add event to queue"));
+ /* Free things up. */
+ sudo_ev_base_free(ec.evbase);
+ sudo_ev_free(ec.sigfwd_event);
+ sudo_ev_free(ec.signal_event);
+ sudo_ev_free(ec.backchannel_event);
+ TAILQ_FOREACH_SAFE(sigfwd, &ec.sigfwd_list, entries, sigfwd_next) {
+ free(sigfwd);
+ }
+ debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0);
+}
+
+/*
+ * Schedule I/O events before starting the main event loop or
+ * resuming from suspend.
+ */
+static void
+add_io_events(struct sudo_event_base *evbase)
+{
+ struct io_buffer *iob;
+ debug_decl(add_io_events, SUDO_DEBUG_EXEC);
/*
- * Wait for errno on pipe, signal on backchannel or for SIGCHLD.
- * The event loop ends when the child is no longer running and
- * the error pipe is closed.
+ * Schedule all readers as long as the buffer is not full.
+ * Schedule writers that contain buffered data.
+ * Normally, write buffers are added on demand when data is read.
*/
- (void) sudo_ev_loop(evbase, 0);
- if (cmnd_pid != -1) {
- /* XXX An error occurred, should send a message back. */
- sudo_debug_printf(SUDO_DEBUG_ERROR,
- "Command still running after event loop exit, sending SIGKILL");
- kill(cmnd_pid, SIGKILL);
- } else {
- /* Send parent status. */
- send_status(backchannel, &cstat);
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ /* Don't read/write from /dev/tty if we are not in the foreground. */
+ if (iob->revent != NULL &&
+ (ttymode == TERM_RAW || !USERTTY_EVENT(iob->revent))) {
+ if (iob->len != sizeof(iob->buf)) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "added I/O revent %p, fd %d, events %d",
+ iob->revent, iob->revent->fd, iob->revent->events);
+ if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+ }
+ }
+ if (iob->wevent != NULL &&
+ (foreground || !USERTTY_EVENT(iob->wevent))) {
+ if (iob->len > iob->off) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "added I/O wevent %p, fd %d, events %d",
+ iob->wevent, iob->wevent->fd, iob->wevent->events);
+ if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+ }
+ }
}
- sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
- _exit(1);
-
-bad:
- debug_return_int(errno);
+ debug_return;
}
/*
- * Sets up std{in,out,err} and executes the actual command.
- * Returns only if execve() fails.
+ * Flush any output buffered in iobufs or readable from fds other
+ * than /dev/tty. Removes I/O events from the event base when done.
*/
static void
-exec_pty(struct command_details *details,
- struct command_status *cstat, int errfd)
+del_io_events(bool nonblocking)
{
- volatile pid_t self = getpid();
- debug_decl(exec_pty, SUDO_DEBUG_EXEC);
-
- /* Register cleanup function */
- sudo_fatal_callback_register(pty_cleanup);
-
- /* Set command process group here too to avoid a race. */
- setpgid(0, self);
-
- /* Wire up standard fds, note that stdout/stderr may be pipes. */
- if (dup2(io_fds[SFD_STDIN], STDIN_FILENO) == -1 ||
- dup2(io_fds[SFD_STDOUT], STDOUT_FILENO) == -1 ||
- dup2(io_fds[SFD_STDERR], STDERR_FILENO) == -1)
- sudo_fatal("dup2");
-
- /* Wait for parent to grant us the tty if we are foreground. */
- if (foreground && !ISSET(details->flags, CD_EXEC_BG)) {
- struct timespec ts = { 0, 1000 }; /* 1us */
- while (tcgetpgrp(io_fds[SFD_SLAVE]) != self)
- nanosleep(&ts, NULL);
+ struct io_buffer *iob;
+ struct sudo_event_base *evbase;
+ debug_decl(del_io_events, SUDO_DEBUG_EXEC);
+
+ /* Remove iobufs from existing event base. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ if (iob->revent != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "deleted I/O revent %p, fd %d, events %d",
+ iob->revent, iob->revent->fd, iob->revent->events);
+ sudo_ev_del(NULL, iob->revent);
+ }
+ if (iob->wevent != NULL) {
+ sudo_debug_printf(SUDO_DEBUG_INFO,
+ "deleted I/O wevent %p, fd %d, events %d",
+ iob->wevent, iob->wevent->fd, iob->wevent->events);
+ sudo_ev_del(NULL, iob->wevent);
+ }
+ }
+
+ /* Create temporary event base for flushing. */
+ evbase = sudo_ev_base_alloc();
+ if (evbase == NULL)
+ sudo_fatal(NULL);
+
+ /* Avoid reading from /dev/tty, just flush existing data. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ /* Don't read from /dev/tty while flushing. */
+ if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) {
+ if (iob->len != sizeof(iob->buf)) {
+ if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+ }
+ }
+ /* Flush any write buffers with data in them. */
+ if (iob->wevent != NULL) {
+ if (iob->len > iob->off) {
+ if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+ }
+ }
+ }
+ (void) sudo_ev_loop(evbase, SUDO_EVLOOP_NONBLOCK);
+
+ /*
+ * If not in non-blocking mode, make sure we flush write buffers.
+ * We don't want to read from the pty or stdin since that might block
+ * and the command is no longer running anyway.
+ */
+ if (!nonblocking) {
+ /* Clear out iobufs from event base. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ if (iob->revent != NULL && !USERTTY_EVENT(iob->revent))
+ sudo_ev_del(evbase, iob->revent);
+ if (iob->wevent != NULL)
+ sudo_ev_del(evbase, iob->wevent);
+ }
+
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ /* Flush any write buffers with data in them. */
+ if (iob->wevent != NULL) {
+ if (iob->len > iob->off) {
+ if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+ }
+ }
+ }
+ (void) sudo_ev_loop(evbase, 0);
+
+ /* We should now have flushed all write buffers. */
+ SLIST_FOREACH(iob, &iobufs, entries) {
+ if (iob->wevent != NULL) {
+ if (iob->len > iob->off) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR,
+ "unflushed data: wevent %p, fd %d, events %d",
+ iob->wevent, iob->wevent->fd, iob->wevent->events);
+ }
+ }
+ }
}
- /* We have guaranteed that the slave fd is > 2 */
- if (io_fds[SFD_SLAVE] != -1)
- close(io_fds[SFD_SLAVE]);
- if (io_fds[SFD_STDIN] != io_fds[SFD_SLAVE])
- close(io_fds[SFD_STDIN]);
- if (io_fds[SFD_STDOUT] != io_fds[SFD_SLAVE])
- close(io_fds[SFD_STDOUT]);
- if (io_fds[SFD_STDERR] != io_fds[SFD_SLAVE])
- close(io_fds[SFD_STDERR]);
-
- /* Execute command; only returns on error. */
- exec_cmnd(details, cstat, errfd);
+ /* Free temporary event base, removing its events. */
+ sudo_ev_base_free(evbase);
debug_return;
}
# endif
#endif
+/*
+ * Indices into io_fds[] when running a command in a pty.
+ */
+#define SFD_STDIN 0
+#define SFD_STDOUT 1
+#define SFD_STDERR 2
+#define SFD_MASTER 3
+#define SFD_SLAVE 4
+#define SFD_USERTTY 5
+
/*
* Special values to indicate whether continuing in foreground or background.
*/
#define SESH_ERR_SOME_FILES 33 /* copy error, some files copied */
/*
- * Symbols shared between exec.c and exec_pty.c
+ * Symbols shared between exec.c, exec_nopty.c, exec_pty.c and exec_monitor.c
*/
+struct command_details;
+struct command_status;
/* exec.c */
-extern volatile pid_t cmnd_pid;
+extern volatile pid_t cmnd_pid, ppgrp;
+void exec_cmnd(struct command_details *details, int errfd);
+void terminate_command(pid_t pid, bool use_pgrp);
+#ifdef SA_SIGINFO
+void exec_handler(int s, siginfo_t *info, void *context);
+#else
+void exec_handler(int s);
+#endif
/* exec_common.c */
int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec);
char **disable_execute(char *envp[], const char *dso);
+/* exec_nopty.c */
+int exec_nopty(struct command_details *details, struct command_status *cstat);
+
/* exec_pty.c */
-struct sudo_event_base;
-struct command_details;
-struct command_status;
-int fork_pty(struct command_details *details, int sv[], sigset_t *omask);
-int suspend_parent(int signo);
-void exec_cmnd(struct command_details *details, struct command_status *cstat,
- int errfd);
-void add_io_events(struct sudo_event_base *evbase);
-#ifdef SA_SIGINFO
-void handler(int s, siginfo_t *info, void *context);
-#else
-void handler(int s);
-#endif
-void pty_close(struct command_status *cstat);
-void pty_setup(uid_t uid, const char *tty, const char *utmp_user);
-void terminate_command(pid_t pid, bool use_pgrp);
+int exec_pty(struct command_details *details, struct command_status *cstat);
+void pty_cleanup(void);
+int pty_make_controlling(void);
+
+/* exec_monitor.c */
+int exec_monitor(struct command_details *details, bool foreground, int backchannel);
/* utmp.c */
bool utmp_login(const char *from_line, const char *to_line, int ttyfd,