Reorganize the command execution code to separate out the pty and
non-pty code paths into their own event loops. The non-pty exec code is now contained in exec_nopty.c and the pty exec code is split between exec_pty.c (parent process) and exec_monitor.c (session leader). This results in a small bit of duplicated code but improves readability. Some of the duplicated code will fall out in future changes to the event subsystem (the signal pipe).
This commit is contained in:
2
MANIFEST
2
MANIFEST
@@ -594,6 +594,8 @@ src/conversation.c
|
||||
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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# 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
|
||||
@@ -114,10 +114,10 @@ SHELL = @SHELL@
|
||||
|
||||
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
|
||||
|
||||
@@ -299,6 +299,24 @@ exec_common.o: $(srcdir)/exec_common.c $(incdir)/compat/stdbool.h \
|
||||
$(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 \
|
||||
|
945
src/exec.c
945
src/exec.c
File diff suppressed because it is too large
Load Diff
664
src/exec_monitor.c
Normal file
664
src/exec_monitor.c
Normal file
@@ -0,0 +1,664 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
505
src/exec_nopty.c
Normal file
505
src/exec_nopty.c
Normal file
@@ -0,0 +1,505 @@
|
||||
/*
|
||||
* 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 */
|
1187
src/exec_pty.c
1187
src/exec_pty.c
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,16 @@
|
||||
# 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.
|
||||
*/
|
||||
@@ -69,33 +79,35 @@
|
||||
#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,
|
||||
|
Reference in New Issue
Block a user