TQ_DECLARE(sigforward)
static struct sigforward_list sigfwd_list;
+volatile pid_t cmnd_pid = -1;
+
static int handle_signals(int sv[2], pid_t child, int log_io,
struct command_status *cstat);
static void forward_signals(int fd);
static void schedule_signal(int signo);
#ifdef SA_SIGINFO
-static void handler_nofwd(int s, siginfo_t *info, void *context);
+static void handler_user_only(int s, siginfo_t *info, void *context);
#endif
/*
{
struct command_status cstat;
sigaction_t sa;
- pid_t child;
debug_decl(fork_cmnd, SUDO_DEBUG_EXEC)
zero_bytes(&sa, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
+#ifdef SA_SIGINFO
+ sa.sa_flags |= SA_SIGINFO;
+ sa.sa_sigaction = handler;
+#else
sa.sa_handler = handler;
+#endif
sigaction(SIGCONT, &sa, NULL);
/*
if (policy_init_session(details) != true)
errorx(1, _("policy plugin failed session initialization"));
- child = sudo_debug_fork();
- switch (child) {
+ cmnd_pid = sudo_debug_fork();
+ switch (cmnd_pid) {
case -1:
error(1, _("unable to fork"));
break;
sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
_exit(1);
}
- debug_return_int(child);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
+ cmnd_pid);
+ debug_return_int(cmnd_pid);
}
static struct signal_state {
bool log_io = false;
fd_set *fdsr, *fdsw;
sigaction_t sa;
+ sigset_t omask;
pid_t child;
debug_decl(sudo_execute, SUDO_DEBUG_EXEC)
* Note: HP-UX select() will not be interrupted if SA_RESTART set.
*/
sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
+#ifdef SA_SIGINFO
+ sa.sa_flags |= SA_SIGINFO;
+ sa.sa_sigaction = handler;
+#else
sa.sa_handler = handler;
+#endif
sigaction(SIGALRM, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
#ifdef SA_SIGINFO
if (!log_io) {
sa.sa_flags |= SA_SIGINFO;
- sa.sa_sigaction = handler_nofwd;
+ sa.sa_sigaction = handler_user_only;
}
#endif
sigaction(SIGHUP, &sa, NULL);
* to and from pty. Adjusts maxfd as needed.
*/
if (log_io)
- child = fork_pty(details, sv, &maxfd);
+ child = fork_pty(details, sv, &maxfd, &omask);
else
child = fork_cmnd(details, sv);
close(sv[1]);
break;
}
}
- if (cstat->type == CMD_WSTATUS) {
+ if (cstat->type == CMD_PID) {
+ /*
+ * Once we know the command's pid we can unblock
+ * signals which ere blocked in fork_pty(). This
+ * avoids a race between exec of the command and
+ * receipt of a fatal signal from it.
+ */
+ cmnd_pid = cstat->val;
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
+ details->command, cmnd_pid);
+ if (log_io)
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ } else if (cstat->type == CMD_WSTATUS) {
if (WIFSTOPPED(cstat->val)) {
/* Suspend parent and tell child how to resume on return. */
sudo_debug_printf(SUDO_DEBUG_INFO,
} else {
/* Nothing listening on sv[0], send directly. */
if (signo == SIGALRM)
- terminate_child(child, false);
+ terminate_command(child, false);
else if (kill(child, signo) != 0)
warning("kill(%d, %d)", (int)child, signo);
}
* Generic handler for signals passed from parent -> child.
* The other end of signal_pipe is checked in the main event loop.
*/
+#ifdef SA_SIGINFO
+void
+handler(int s, siginfo_t *info, void *context)
+{
+ unsigned char signo = (unsigned char)s;
+
+ /*
+ * If the signal came from the command we ran, just ignore
+ * it since we don't want the child to indirectly kill itself.
+ * This can happen with, e.g. BSD-derived versions of reboot
+ * that call kill(-1, SIGTERM) to kill all other processes.
+ */
+ if (info != NULL && info->si_code == SI_USER && info->si_pid == cmnd_pid)
+ return;
+
+ /*
+ * The pipe is non-blocking, if we overflow the kernel's pipe
+ * buffer we drop the signal. This is not a problem in practice.
+ */
+ ignore_result(write(signal_pipe[1], &signo, sizeof(signo)));
+}
+#else
void
handler(int s)
{
*/
ignore_result(write(signal_pipe[1], &signo, sizeof(signo)));
}
+#endif
#ifdef SA_SIGINFO
/*
* signals that are generated by the kernel.
*/
static void
-handler_nofwd(int s, siginfo_t *info, void *context)
+handler_user_only(int s, siginfo_t *info, void *context)
{
unsigned char signo = (unsigned char)s;
static bool foreground, pipeline, tty_initialized;
static int io_fds[6] = { -1, -1, -1, -1, -1, -1};
static int ttymode = TERM_COOKED;
-static pid_t ppgrp, child, child_pgrp;
+static pid_t ppgrp, cmnd_pgrp;
static sigset_t ttyblock;
static struct io_buffer *iobufs;
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
+void
+mon_handler(int s, siginfo_t *info, void *context)
+{
+ unsigned char signo = (unsigned char)s;
+
+ /*
+ * If the signal came from the command we ran, just ignore
+ * it since we don't want the child to indirectly kill itself.
+ * This can happen with, e.g. BSD-derived versions of reboot
+ * that call kill(-1, SIGTERM) to kill all other processes.
+ */
+ if (info != NULL && info->si_code == SI_USER && info->si_pid == cmnd_pid)
+ return;
+
+ /*
+ * The pipe is non-blocking, if we overflow the kernel's pipe
+ * buffer we drop the signal. This is not a problem in practice.
+ */
+ ignore_result(write(signal_pipe[1], &signo, sizeof(signo)));
+}
+#else
+void
+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.
+ */
+ ignore_result(write(signal_pipe[1], &signo, sizeof(signo)));
+}
+#endif
+
/*
* Allocate a pty if /dev/tty is a tty.
* Fills in io_fds[SFD_USERTTY], io_fds[SFD_MASTER], io_fds[SFD_SLAVE]
/*
* Suspend sudo if the underlying command is suspended.
- * Returns SIGCONT_FG if the child should be resume in the
+ * Returns SIGCONT_FG if the command should be resumed in the
* foreground or SIGCONT_BG if it is a background process.
*/
int
case SIGTTOU:
case SIGTTIN:
/*
- * If we are the foreground process, just resume the child.
+ * If we are the foreground process, just resume the command.
* Otherwise, re-send the signal with the handler disabled.
*/
if (!foreground)
} while (!n && errno == EINTR);
ttymode = TERM_RAW;
}
- rval = SIGCONT_FG; /* resume child in foreground */
+ rval = SIGCONT_FG; /* resume command in foreground */
break;
}
ttymode = TERM_RAW;
} while (!n && errno == EINTR);
}
- /* Suspend self and continue child when we resume. */
+ /* Suspend self and continue command when we resume. */
zero_bytes(&sa, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
/*
* Only modify term if we are foreground process and either
- * the old tty mode was not cooked or child got SIGTT{IN,OU}
+ * the old tty mode was not cooked or command got SIGTT{IN,OU}
*/
sudo_debug_printf(SUDO_DEBUG_INFO, "parent is in %s, ttymode %d -> %d",
foreground ? "foreground" : "background", oldmode, ttymode);
}
/*
- * Kill child with increasing urgency.
+ * Kill command with increasing urgency.
*/
void
-terminate_child(pid_t pid, bool use_pgrp)
+terminate_command(pid_t pid, bool use_pgrp)
{
- debug_decl(terminate_child, SUDO_DEBUG_EXEC);
+ debug_decl(terminate_command, SUDO_DEBUG_EXEC);
/*
* Note that SIGCHLD will interrupt the sleep()
sudo_debug_printf(SUDO_DEBUG_INFO,
"read %d bytes from fd %d", n, iob->rfd);
if (!iob->action(iob->buf + iob->len, n))
- terminate_child(child, true);
+ terminate_command(cmnd_pid, true);
iob->len += n;
break;
}
* Returns the child pid.
*/
int
-fork_pty(struct command_details *details, int sv[], int *maxfd)
+fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask)
{
struct command_status cstat;
struct io_buffer *iob;
int io_pipe[3][2], n;
sigaction_t sa;
+ sigset_t mask;
+ pid_t child;
debug_decl(fork_pty, SUDO_DEBUG_EXEC);
ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */
/* Job control signals to relay from parent to child. */
sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
+#ifdef SA_SIGINFO
+ sa.sa_flags |= SA_SIGINFO;
+ sa.sa_sigaction = handler;
+#else
sa.sa_handler = handler;
+#endif
sigaction(SIGTSTP, &sa, NULL);
/* We don't want to receive SIGTTIN/SIGTTOU, getting EIO is preferable. */
if (policy_init_session(details) != true)
errorx(1, _("policy plugin failed session initialization"));
+ /*
+ * Block some signals until cmnd_pid is set in the parent to avoid a
+ * race between exec of the command and receipt of a fatal signal from it.
+ */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGTERM);
+ sigaddset(&mask, SIGHUP);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGQUIT);
+ sigprocmask(SIG_BLOCK, &mask, omask);
+
child = sudo_debug_fork();
switch (child) {
case -1:
close(signal_pipe[0]);
close(signal_pipe[1]);
fcntl(sv[1], F_SETFD, FD_CLOEXEC);
+ sigprocmask(SIG_SETMASK, omask, NULL);
if (exec_setup(details, slavename, io_fds[SFD_SLAVE]) == true) {
/* Close the other end of the stdin/stdout/stderr pipes and exec. */
if (io_pipe[STDIN_FILENO][1])
}
cstat.type = CMD_ERRNO;
cstat.val = errno;
- send(sv[1], &cstat, sizeof(cstat), 0);
+ ignore_result(send(sv[1], &cstat, sizeof(cstat), 0));
_exit(1);
}
from_parent ? " from parent" : "");
switch (signo) {
case SIGALRM:
- terminate_child(pid, true);
+ terminate_command(pid, true);
break;
case SIGCONT_FG:
/* Continue in foreground, grant it controlling tty. */
do {
- status = tcsetpgrp(io_fds[SFD_SLAVE], child_pgrp);
+ status = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
} while (status == -1 && errno == EINTR);
killpg(pid, SIGCONT);
break;
_exit(1); /* XXX */
/* NOTREACHED */
default:
- /* Relay signal to child. */
+ /* Relay signal to command. */
killpg(pid, signo);
break;
}
}
/*
- * Wait for child status after receiving SIGCHLD.
- * If the child was stopped, the status is send back to the parent.
+ * 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.
- * Returns true if child is still alive, else false.
+ * Returns true if command is still alive, else false.
*/
static bool
handle_sigchld(int backchannel, struct command_status *cstat)
pid_t pid;
debug_decl(handle_sigchld, SUDO_DEBUG_EXEC);
- /* read child status */
+ /* read command status */
do {
- pid = waitpid(child, &status, WUNTRACED|WNOHANG);
+ pid = waitpid(cmnd_pid, &status, WUNTRACED|WNOHANG);
} while (pid == -1 && errno == EINTR);
- if (pid == child) {
+ if (pid == cmnd_pid) {
if (cstat->type != CMD_ERRNO) {
cstat->type = CMD_WSTATUS;
cstat->val = status;
sudo_debug_printf(SUDO_DEBUG_INFO, "command stopped, signal %d",
WSTOPSIG(status));
do {
- child_pgrp = tcgetpgrp(io_fds[SFD_SLAVE]);
- } while (child_pgrp == -1 && errno == EINTR);
+ cmnd_pgrp = tcgetpgrp(io_fds[SFD_SLAVE]);
+ } while (cmnd_pgrp == -1 && errno == EINTR);
if (send_status(backchannel, cstat) == -1)
return alive; /* XXX */
} else if (WIFSIGNALED(status)) {
/* Note: HP-UX select() will not be interrupted if SA_RESTART set */
sa.sa_flags = SA_INTERRUPT;
- sa.sa_handler = handler;
+#ifdef SA_SIGINFO
+ sa.sa_flags |= SA_SIGINFO;
+ sa.sa_sigaction = mon_handler;
+#else
+ sa.sa_handler = mon_handler;
+#endif
sigaction(SIGCHLD, &sa, NULL);
/* Catch common signals so we can cleanup properly. */
sa.sa_flags = SA_RESTART;
- sa.sa_handler = handler;
+#ifdef SA_SIGINFO
+ sa.sa_flags |= SA_SIGINFO;
+ sa.sa_sigaction = mon_handler;
+#else
+ sa.sa_handler = mon_handler;
+#endif
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
/*
* 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 child has been suspended.
+ * This allows us to be notified when the command has been suspended.
*/
if (setsid() == -1) {
warning("setsid");
/* Start command and wait for it to stop or exit */
if (pipe(errpipe) == -1)
error(1, _("unable to create pipe"));
- child = sudo_debug_fork();
- if (child == -1) {
+ cmnd_pid = sudo_debug_fork();
+ if (cmnd_pid == -1) {
warning(_("unable to fork"));
goto bad;
}
- if (child == 0) {
+ if (cmnd_pid == 0) {
/* We pass errno back to our parent via pipe on exec failure. */
close(backchannel);
close(signal_pipe[0]);
}
close(errpipe[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]);
close(io_fds[SFD_STDERR]);
/*
- * Put child in its own process group. If we are starting the command
+ * Put command in its own process group. If we are starting the command
* in the foreground, assign its pgrp to the tty.
*/
- child_pgrp = child;
- setpgid(child, child_pgrp);
+ cmnd_pgrp = cmnd_pid;
+ setpgid(cmnd_pid, cmnd_pgrp);
if (foreground) {
do {
- status = tcsetpgrp(io_fds[SFD_SLAVE], child_pgrp);
+ status = tcsetpgrp(io_fds[SFD_SLAVE], cmnd_pgrp);
} while (status == -1 && errno == EINTR);
}
}
/*
* Handle SIGCHLD specially and deliver other signals
- * directly to the child.
+ * directly to the command.
*/
if (signo == SIGCHLD) {
if (!handle_sigchld(backchannel, &cstat))
alive = false;
} else {
- deliver_signal(child, signo, false);
+ deliver_signal(cmnd_pid, signo, false);
}
continue;
}
cstmp.type);
continue;
}
- deliver_signal(child, cstmp.val, true);
+ deliver_signal(cmnd_pid, cstmp.val, true);
}
}
done:
if (alive) {
/* XXX An error occurred, should send an error back. */
- kill(child, SIGKILL);
+ kill(cmnd_pid, SIGKILL);
} else {
/* Send parent status. */
send_status(backchannel, &cstat);
pid_t self = getpid();
debug_decl(exec_pty, SUDO_DEBUG_EXEC);
- /* Set child process group here too to avoid a race. */
+ /* Set command process group here too to avoid a race. */
setpgid(0, self);
/* Wire up standard fds, note that stdout/stderr may be pipes. */