version 1.1.1.4, 2013/07/22 10:46:13
|
version 1.1.1.6, 2014/06/15 16:12:55
|
Line 1
|
Line 1
|
/* |
/* |
* Copyright (c) 2009-2013 Todd C. Miller <Todd.Miller@courtesan.com> | * Copyright (c) 2009-2014 Todd C. Miller <Todd.Miller@courtesan.com> |
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
Line 17
|
Line 17
|
#include <config.h> |
#include <config.h> |
|
|
#include <sys/types.h> |
#include <sys/types.h> |
#ifdef HAVE_SYS_SYSMACROS_H |
|
# include <sys/sysmacros.h> |
|
#endif |
|
#include <sys/socket.h> |
#include <sys/socket.h> |
#include <sys/stat.h> |
#include <sys/stat.h> |
#include <sys/time.h> |
#include <sys/time.h> |
#include <sys/wait.h> |
#include <sys/wait.h> |
#include <sys/ioctl.h> |
#include <sys/ioctl.h> |
#ifdef HAVE_SYS_SELECT_H |
|
# include <sys/select.h> |
|
#endif /* HAVE_SYS_SELECT_H */ |
|
#include <stdio.h> |
#include <stdio.h> |
#ifdef STDC_HEADERS |
#ifdef STDC_HEADERS |
# include <stdlib.h> |
# include <stdlib.h> |
Line 46
|
Line 40
|
#ifdef HAVE_UNISTD_H |
#ifdef HAVE_UNISTD_H |
# include <unistd.h> |
# include <unistd.h> |
#endif /* HAVE_UNISTD_H */ |
#endif /* HAVE_UNISTD_H */ |
#if TIME_WITH_SYS_TIME | #ifdef TIME_WITH_SYS_TIME |
# include <time.h> |
# include <time.h> |
#endif |
#endif |
#include <errno.h> |
#include <errno.h> |
Line 56
|
Line 50
|
|
|
#include "sudo.h" |
#include "sudo.h" |
#include "sudo_exec.h" |
#include "sudo_exec.h" |
|
#include "sudo_event.h" |
#include "sudo_plugin.h" |
#include "sudo_plugin.h" |
#include "sudo_plugin_int.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. */ |
/* We keep a tailq of signals to forward to child. */ |
struct sigforward { |
struct sigforward { |
struct sigforward *prev, *next; | TAILQ_ENTRY(sigforward) entries; |
int signo; |
int signo; |
}; |
}; |
TQ_DECLARE(sigforward) | TAILQ_HEAD(sigfwd_list, sigforward); |
static struct sigforward_list sigfwd_list; | 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; |
static pid_t ppgrp = -1; |
|
|
volatile pid_t cmnd_pid = -1; |
volatile pid_t cmnd_pid = -1; |
|
|
static int dispatch_signals(int sv[2], pid_t child, int log_io, | static void signal_pipe_cb(int fd, int what, void *v); |
struct command_status *cstat); | |
static int dispatch_pending_signals(struct command_status *cstat); |
static int dispatch_pending_signals(struct command_status *cstat); |
static void forward_signals(int fd); | static void forward_signals(int fd, int what, void *v); |
static void schedule_signal(int signo); | static void schedule_signal(struct sudo_event_base *evbase, int signo); |
#ifdef SA_SIGINFO |
#ifdef SA_SIGINFO |
static void handler_user_only(int s, siginfo_t *info, void *context); |
static void handler_user_only(int s, siginfo_t *info, void *context); |
#endif |
#endif |
Line 124 static int fork_cmnd(struct command_details *details,
|
Line 130 static int fork_cmnd(struct command_details *details,
|
* or certain pam modules won't be able to track their state. |
* or certain pam modules won't be able to track their state. |
*/ |
*/ |
if (policy_init_session(details) != true) |
if (policy_init_session(details) != true) |
fatalx(_("policy plugin failed session initialization")); | fatalx(U_("policy plugin failed session initialization")); |
|
|
cmnd_pid = sudo_debug_fork(); |
cmnd_pid = sudo_debug_fork(); |
switch (cmnd_pid) { |
switch (cmnd_pid) { |
case -1: |
case -1: |
fatal(_("unable to fork")); | fatal(U_("unable to fork")); |
break; |
break; |
case 0: |
case 0: |
/* child */ |
/* child */ |
Line 137 static int fork_cmnd(struct command_details *details,
|
Line 143 static int fork_cmnd(struct command_details *details,
|
close(signal_pipe[0]); |
close(signal_pipe[0]); |
close(signal_pipe[1]); |
close(signal_pipe[1]); |
fcntl(sv[1], F_SETFD, FD_CLOEXEC); |
fcntl(sv[1], F_SETFD, FD_CLOEXEC); |
exec_cmnd(details, &cstat, &sv[1]); | exec_cmnd(details, &cstat, sv[1]); |
send(sv[1], &cstat, sizeof(cstat), 0); |
send(sv[1], &cstat, sizeof(cstat), 0); |
sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1); |
sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1); |
_exit(1); |
_exit(1); |
Line 155 static int fork_cmnd(struct command_details *details,
|
Line 161 static int fork_cmnd(struct command_details *details,
|
*/ |
*/ |
void |
void |
exec_cmnd(struct command_details *details, struct command_status *cstat, |
exec_cmnd(struct command_details *details, struct command_status *cstat, |
int *errfd) | int errfd) |
{ |
{ |
debug_decl(exec_cmnd, SUDO_DEBUG_EXEC) |
debug_decl(exec_cmnd, SUDO_DEBUG_EXEC) |
|
|
Line 165 exec_cmnd(struct command_details *details, struct comm
|
Line 171 exec_cmnd(struct command_details *details, struct comm
|
sudo_debug_execve(SUDO_DEBUG_INFO, details->command, |
sudo_debug_execve(SUDO_DEBUG_INFO, details->command, |
details->argv, details->envp); |
details->argv, details->envp); |
if (details->closefrom >= 0) { |
if (details->closefrom >= 0) { |
int maxfd = details->closefrom; | /* Preserve debug fd and error pipe as needed. */ |
/* Preserve back channel if present. */ | int debug_fd = sudo_debug_fd_get(); |
if (errfd != NULL) { | if (debug_fd != -1) |
dup2(*errfd, maxfd); | add_preserved_fd(&details->preserved_fds, debug_fd); |
(void)fcntl(maxfd, F_SETFD, FD_CLOEXEC); | if (errfd != -1) |
*errfd = maxfd++; | add_preserved_fd(&details->preserved_fds, errfd); |
} | |
if (sudo_debug_fd_set(maxfd) != -1) | /* Close all fds except those explicitly preserved. */ |
maxfd++; | closefrom_except(details->closefrom, &details->preserved_fds); |
closefrom(maxfd); | |
} |
} |
#ifdef HAVE_SELINUX |
#ifdef HAVE_SELINUX |
if (ISSET(details->flags, CD_RBAC_ENABLED)) { |
if (ISSET(details->flags, CD_RBAC_ENABLED)) { |
Line 194 exec_cmnd(struct command_details *details, struct comm
|
Line 199 exec_cmnd(struct command_details *details, struct comm
|
debug_return; |
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. |
|
*/ |
|
(void)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 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); |
|
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) |
|
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) |
|
fatal(NULL); |
|
if (sudo_ev_add(evbase, signal_event, NULL, false) == -1) |
|
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) |
|
fatal(NULL); |
|
if (sudo_ev_add(evbase, backchannel_event, NULL, false) == -1) |
|
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) |
|
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 |
* Execute a command, potentially in a pty with I/O loggging, and |
* wait for it to finish. |
* wait for it to finish. |
* This is a little bit tricky due to how POSIX job control works and |
* This is a little bit tricky due to how POSIX job control works and |
Line 203 exec_cmnd(struct command_details *details, struct comm
|
Line 339 exec_cmnd(struct command_details *details, struct comm
|
int |
int |
sudo_execute(struct command_details *details, struct command_status *cstat) |
sudo_execute(struct command_details *details, struct command_status *cstat) |
{ |
{ |
int maxfd, n, nready, sv[2]; | struct sigforward *sigfwd, *sigfwd_next; |
const char *utmp_user = NULL; |
const char *utmp_user = NULL; |
|
struct sudo_event_base *evbase; |
|
struct exec_closure ec; |
bool log_io = false; |
bool log_io = false; |
fd_set *fdsr, *fdsw; |
|
sigaction_t sa; |
sigaction_t sa; |
sigset_t omask; |
|
pid_t child; |
pid_t child; |
|
int sv[2]; |
debug_decl(sudo_execute, SUDO_DEBUG_EXEC) |
debug_decl(sudo_execute, SUDO_DEBUG_EXEC) |
|
|
dispatch_pending_signals(cstat); |
dispatch_pending_signals(cstat); |
Line 238 sudo_execute(struct command_details *details, struct c
|
Line 375 sudo_execute(struct command_details *details, struct c
|
* need to allocate a pty. It is OK to set log_io in the pty-only case |
* 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. |
* as the io plugin tailqueue will be empty and no I/O logging will occur. |
*/ |
*/ |
if (!tq_empty(&io_plugins) || ISSET(details->flags, CD_USE_PTY)) { | if (!TAILQ_EMPTY(&io_plugins) || ISSET(details->flags, CD_USE_PTY)) { |
log_io = true; |
log_io = true; |
if (ISSET(details->flags, CD_SET_UTMP)) |
if (ISSET(details->flags, CD_SET_UTMP)) |
utmp_user = details->utmp_user ? details->utmp_user : user_details.username; |
utmp_user = details->utmp_user ? details->utmp_user : user_details.username; |
sudo_debug_printf(SUDO_DEBUG_INFO, "allocate pty for I/O logging"); |
sudo_debug_printf(SUDO_DEBUG_INFO, "allocate pty for I/O logging"); |
pty_setup(details->euid, user_details.tty, utmp_user); |
pty_setup(details->euid, user_details.tty, utmp_user); |
} else if (!ISSET(details->flags, CD_SET_TIMEOUT) && | } else if (!ISSET(details->flags, CD_SET_TIMEOUT|CD_SUDOEDIT) && |
policy_plugin.u.policy->close == NULL) { |
policy_plugin.u.policy->close == NULL) { |
/* If no I/O logging, timeout or policy close we can exec directly. */ | /* |
exec_cmnd(details, cstat, 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; |
goto done; |
} |
} |
|
|
Line 255 sudo_execute(struct command_details *details, struct c
|
Line 395 sudo_execute(struct command_details *details, struct c
|
* We communicate with the child over a bi-directional pair of sockets. |
* We communicate with the child over a bi-directional pair of sockets. |
* Parent sends signal info to child and child sends back wait status. |
* Parent sends signal info to child and child sends back wait status. |
*/ |
*/ |
if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) == -1) | if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) |
fatal(_("unable to create sockets")); | fatal(U_("unable to create sockets")); |
|
|
/* |
/* |
* Signals to forward to the child process (excluding SIGALRM and SIGCHLD). |
* Signals to forward to the child process (excluding SIGALRM and SIGCHLD). |
Line 296 sudo_execute(struct command_details *details, struct c
|
Line 436 sudo_execute(struct command_details *details, struct c
|
sudo_sigaction(SIGINT, &sa, NULL); |
sudo_sigaction(SIGINT, &sa, NULL); |
sudo_sigaction(SIGQUIT, &sa, NULL); |
sudo_sigaction(SIGQUIT, &sa, NULL); |
|
|
/* Max fd we will be selecting on. */ |
|
maxfd = MAX(sv[0], signal_pipe[0]); |
|
|
|
/* |
/* |
* Child will run the command in the pty, parent will pass data |
* Child will run the command in the pty, parent will pass data |
* to and from pty. Adjusts maxfd as needed. | * to and from pty. |
*/ |
*/ |
if (log_io) |
if (log_io) |
child = fork_pty(details, sv, &maxfd, &omask); | child = fork_pty(details, sv, &ec.omask); |
else |
else |
child = fork_cmnd(details, sv); |
child = fork_cmnd(details, sv); |
close(sv[1]); |
close(sv[1]); |
Line 320 sudo_execute(struct command_details *details, struct c
|
Line 457 sudo_execute(struct command_details *details, struct c
|
setlocale(LC_ALL, "C"); |
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 |
* In the event loop we pass input from user tty to master |
* and pass output from master to stdout and IO plugin. |
* and pass output from master to stdout and IO plugin. |
*/ |
*/ |
fdsr = emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); | if (log_io) |
fdsw = emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); | add_io_events(evbase); |
for (;;) { | if (sudo_ev_loop(evbase, 0) == -1) |
memset(fdsw, 0, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); | warning(U_("error in event loop")); |
memset(fdsr, 0, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); | if (sudo_ev_got_break(evbase)) { |
| /* error from callback */ |
FD_SET(signal_pipe[0], fdsr); | sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely"); |
FD_SET(sv[0], fdsr); | /* kill command if still running and not I/O logging */ |
if (!tq_empty(&sigfwd_list)) | if (!log_io && kill(child, 0) == 0) |
FD_SET(sv[0], fdsw); | terminate_command(child, true); |
if (log_io) | |
fd_set_iobs(fdsr, fdsw); /* XXX - better name */ | |
nready = select(maxfd + 1, fdsr, fdsw, NULL, NULL); | |
sudo_debug_printf(SUDO_DEBUG_DEBUG, "select returns %d", nready); | |
if (nready == -1) { | |
if (errno == EINTR || errno == ENOMEM) | |
continue; | |
if (errno == EBADF || errno == EIO) { | |
/* One of the ttys must have gone away. */ | |
goto do_tty_io; | |
} | |
warning(_("select failed")); | |
sudo_debug_printf(SUDO_DEBUG_ERROR, | |
"select failure, terminating child"); | |
schedule_signal(SIGKILL); | |
forward_signals(sv[0]); | |
break; | |
} | |
if (FD_ISSET(sv[0], fdsw)) { | |
forward_signals(sv[0]); | |
} | |
if (FD_ISSET(signal_pipe[0], fdsr)) { | |
n = dispatch_signals(sv, child, log_io, cstat); | |
if (n == 0) { | |
/* Child has exited, cstat is set, we are done. */ | |
break; | |
} | |
if (n == -1) { | |
/* Error reading signal_pipe[0], should not happen. */ | |
break; | |
} | |
/* Restart event loop so signals get sent to child immediately. */ | |
continue; | |
} | |
if (FD_ISSET(sv[0], fdsr)) { | |
/* read child status */ | |
n = recv(sv[0], cstat, sizeof(*cstat), 0); | |
if (n != sizeof(*cstat)) { | |
if (n == -1) { | |
if (errno == EINTR) | |
continue; | |
/* | |
* If not logging I/O we may receive ECONNRESET when | |
* the command is executed and sv is closed. | |
* It is safe to ignore this. | |
*/ | |
if (log_io && errno != EAGAIN) { | |
cstat->type = CMD_ERRNO; | |
cstat->val = errno; | |
break; | |
} | |
sudo_debug_printf(SUDO_DEBUG_ERROR, | |
"failed to read child status: %s", strerror(errno)); | |
} else { | |
/* Short read or EOF. */ | |
sudo_debug_printf(SUDO_DEBUG_ERROR, | |
"failed to read child status: %s", | |
n ? "short read" : "EOF"); | |
/* XXX - should set cstat */ | |
break; | |
} | |
} | |
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, (int)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, | |
"child stopped, suspending parent"); | |
n = suspend_parent(WSTOPSIG(cstat->val)); | |
schedule_signal(n); | |
continue; | |
} else { | |
/* Child exited or was killed, either way we are done. */ | |
sudo_debug_printf(SUDO_DEBUG_INFO, "child exited or was killed"); | |
break; | |
} | |
} else if (cstat->type == CMD_ERRNO) { | |
/* Child was unable to execute command or broken pipe. */ | |
sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s", | |
strerror(cstat->val)); | |
break; | |
} | |
} | |
do_tty_io: | |
if (perform_io(fdsr, fdsw, cstat) != 0) { | |
/* I/O error, kill child if still alive and finish. */ | |
sudo_debug_printf(SUDO_DEBUG_ERROR, "I/O error, terminating child"); | |
schedule_signal(SIGKILL); | |
forward_signals(sv[0]); | |
break; | |
} | |
} |
} |
|
|
if (log_io) { |
if (log_io) { |
Line 446 do_tty_io:
|
Line 497 do_tty_io:
|
if (ISSET(details->flags, CD_RBAC_ENABLED)) { |
if (ISSET(details->flags, CD_RBAC_ENABLED)) { |
/* This is probably not needed in log_io mode. */ |
/* This is probably not needed in log_io mode. */ |
if (selinux_restore_tty() != 0) |
if (selinux_restore_tty() != 0) |
warningx(_("unable to restore tty label")); | warningx(U_("unable to restore tty label")); |
} |
} |
#endif |
#endif |
|
|
efree(fdsr); | /* Free things up. */ |
efree(fdsw); | sudo_ev_base_free(evbase); |
while (!tq_empty(&sigfwd_list)) { | sudo_ev_free(sigfwd_event); |
struct sigforward *sigfwd = tq_first(&sigfwd_list); | sudo_ev_free(signal_event); |
tq_remove(&sigfwd_list, sigfwd); | sudo_ev_free(backchannel_event); |
| TAILQ_FOREACH_SAFE(sigfwd, &sigfwd_list, entries, sigfwd_next) { |
efree(sigfwd); |
efree(sigfwd); |
} |
} |
|
TAILQ_INIT(&sigfwd_list); |
done: |
done: |
debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0); |
debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0); |
} |
} |
|
|
/* |
/* |
* Read signals on signal_pipe written by handler(). | * Forward a signal to the command (non-pty version). |
* Returns -1 on error, 0 on child exit, else 1. | |
*/ |
*/ |
static int |
static int |
dispatch_signals(int sv[2], pid_t child, int log_io, struct command_status *cstat) | dispatch_signal(struct sudo_event_base *evbase, pid_t child, |
| int signo, char *signame, struct command_status *cstat) |
{ |
{ |
|
int rc = 1; |
|
debug_decl(dispatch_signal, SUDO_DEBUG_EXEC) |
|
|
|
sudo_debug_printf(SUDO_DEBUG_INFO, |
|
"%s: evbase %p, child: %d, signo %s(%d), cstat %p", |
|
__func__, evbase, (int)child, signame, signo, cstat); |
|
|
|
if (signo == SIGCHLD) { |
|
pid_t pid; |
|
int status; |
|
/* |
|
* The command stopped or exited. |
|
*/ |
|
do { |
|
pid = waitpid(child, &status, WUNTRACED|WNOHANG); |
|
} while (pid == -1 && errno == EINTR); |
|
if (pid == 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 = (pid_t)-1; |
|
int signo = WSTOPSIG(status); |
|
int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0); |
|
if (fd != -1) { |
|
saved_pgrp = tcgetpgrp(fd); |
|
/* |
|
* 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(child); |
|
if (child_pgrp != ppgrp) { |
|
if (tcsetpgrp(fd, child_pgrp) == 0) { |
|
if (killpg(child_pgrp, SIGCONT) != 0) { |
|
warning("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; |
|
sudo_sigaction(SIGTSTP, &sa, &osa); |
|
} |
|
if (kill(getpid(), signo) != 0) |
|
warning("kill(%d, SIG%s)", (int)getpid(), signame); |
|
if (signo == SIGTSTP) |
|
sudo_sigaction(SIGTSTP, &osa, NULL); |
|
if (fd != -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. */ |
|
cstat->type = CMD_WSTATUS; |
|
cstat->val = status; |
|
sudo_ev_loopexit(evbase); |
|
goto done; |
|
} |
|
} |
|
} else { |
|
/* Send signal to child. */ |
|
if (signo == SIGALRM) { |
|
terminate_command(child, false); |
|
} else if (kill(child, signo) != 0) { |
|
warning("kill(%d, SIG%s)", (int)child, signame); |
|
} |
|
} |
|
rc = 0; |
|
done: |
|
debug_return_int(rc); |
|
} |
|
|
|
/* |
|
* Forward a signal to the monitory (pty version). |
|
*/ |
|
static int |
|
dispatch_signal_pty(struct sudo_event_base *evbase, pid_t child, |
|
int signo, char *signame, struct command_status *cstat) |
|
{ |
|
int rc = 1; |
|
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__, evbase, (int)child, signame, signo, cstat); |
|
|
|
if (signo == SIGCHLD) { |
|
int n, status; |
|
pid_t pid; |
|
/* |
|
* Monitor process was signaled; wait for it as needed. |
|
*/ |
|
do { |
|
pid = waitpid(child, &status, WUNTRACED|WNOHANG); |
|
} while (pid == -1 && errno == EINTR); |
|
if (pid == 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(evbase, n); |
|
/* Re-enable I/O events and restart event loop. */ |
|
add_io_events(evbase); |
|
sudo_ev_loopcontinue(evbase); |
|
goto done; |
|
} else if (WIFSIGNALED(status)) { |
|
sudo_debug_printf(SUDO_DEBUG_INFO, |
|
"monitor killed, signal %d", WTERMSIG(status)); |
|
} else { |
|
sudo_debug_printf(SUDO_DEBUG_INFO, |
|
"monitor exited, status %d", WEXITSTATUS(status)); |
|
} |
|
} |
|
} else { |
|
/* Schedule signo to be forwared to the child. */ |
|
schedule_signal(evbase, signo); |
|
/* Restart event loop to service signal immediately. */ |
|
sudo_ev_loopcontinue(evbase); |
|
} |
|
rc = 0; |
|
done: |
|
debug_return_int(rc); |
|
} |
|
|
|
/* Signal pipe callback */ |
|
static void |
|
signal_pipe_cb(int fd, int what, void *v) |
|
{ |
|
struct exec_closure *ec = v; |
char signame[SIG2STR_MAX]; |
char signame[SIG2STR_MAX]; |
unsigned char signo; |
unsigned char signo; |
ssize_t nread; |
ssize_t nread; |
int status; | int rc = 0; |
pid_t pid; | debug_decl(signal_pipe_cb, SUDO_DEBUG_EXEC) |
debug_decl(dispatch_signals, SUDO_DEBUG_EXEC) | |
|
|
for (;;) { | do { |
/* read signal pipe */ |
/* read signal pipe */ |
nread = read(signal_pipe[0], &signo, sizeof(signo)); | nread = read(fd, &signo, sizeof(signo)); |
if (nread <= 0) { |
if (nread <= 0) { |
/* It should not be possible to get EOF but just in case. */ | /* It should not be possible to get EOF but just in case... */ |
if (nread == 0) |
if (nread == 0) |
errno = ECONNRESET; |
errno = ECONNRESET; |
/* Restart if interrupted by signal so the pipe doesn't fill. */ |
/* Restart if interrupted by signal so the pipe doesn't fill. */ |
if (errno == EINTR) |
if (errno == EINTR) |
continue; |
continue; |
/* If pipe is empty, we are done. */ | /* On error, store errno and break out of the event loop. */ |
if (errno == EAGAIN) | if (errno != EAGAIN) { |
break; | sudo_debug_printf(SUDO_DEBUG_ERROR, |
sudo_debug_printf(SUDO_DEBUG_ERROR, "error reading signal pipe %s", | "error reading signal pipe %s", strerror(errno)); |
strerror(errno)); | ec->cstat->type = CMD_ERRNO; |
cstat->type = CMD_ERRNO; | ec->cstat->val = errno; |
cstat->val = errno; | sudo_ev_loopbreak(ec->evbase); |
debug_return_int(-1); | } |
| break; |
} |
} |
if (sig2str(signo, signame) == -1) |
if (sig2str(signo, signame) == -1) |
snprintf(signame, sizeof(signame), "%d", signo); |
snprintf(signame, sizeof(signame), "%d", signo); |
sudo_debug_printf(SUDO_DEBUG_DIAG, "received SIG%s", signame); |
sudo_debug_printf(SUDO_DEBUG_DIAG, "received SIG%s", signame); |
if (signo == SIGCHLD) { | if (ec->log_io) { |
/* | rc = dispatch_signal_pty(ec->evbase, ec->child, signo, signame, |
* If logging I/O, child is the intermediate process, | ec->cstat); |
* otherwise it is the command itself. | |
*/ | |
do { | |
pid = waitpid(child, &status, WUNTRACED|WNOHANG); | |
} while (pid == -1 && errno == EINTR); | |
if (pid == child) { | |
if (log_io) { | |
/* | |
* On BSD we get ECONNRESET on sv[0] if monitor dies | |
* and select() will return with sv[0] readable. | |
* On Linux that doesn't appear to happen so if the | |
* monitor dies, shut down the socketpair to force a | |
* select() notification. | |
*/ | |
(void) shutdown(sv[0], SHUT_WR); | |
} else { | |
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 = (pid_t)-1; | |
int signo = WSTOPSIG(status); | |
int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0); | |
if (fd != -1) { | |
if ((saved_pgrp = tcgetpgrp(fd)) == ppgrp) | |
saved_pgrp = -1; | |
} | |
if (signo == SIGTSTP) { | |
memset(&sa, 0, sizeof(sa)); | |
sigemptyset(&sa.sa_mask); | |
sa.sa_flags = SA_RESTART; | |
sa.sa_handler = SIG_DFL; | |
sudo_sigaction(SIGTSTP, &sa, &osa); | |
} | |
if (kill(getpid(), signo) != 0) | |
warning("kill(%d, SIG%s)", (int)getpid(), signame); | |
if (signo == SIGTSTP) | |
sudo_sigaction(SIGTSTP, &osa, NULL); | |
if (fd != -1) { | |
/* | |
* Restore command's process group if different. | |
* Otherwise, we cannot resume some shells. | |
*/ | |
if (saved_pgrp != (pid_t)-1) | |
(void)tcsetpgrp(fd, saved_pgrp); | |
close(fd); | |
} | |
} else { | |
/* Child has exited, we are done. */ | |
cstat->type = CMD_WSTATUS; | |
cstat->val = status; | |
debug_return_int(0); | |
} | |
} | |
} | |
} else { |
} else { |
if (log_io) { | rc = dispatch_signal(ec->evbase, ec->child, signo, signame, |
/* Schedule signo to be forwared to the child. */ | ec->cstat); |
schedule_signal(signo); | |
} else { | |
/* Nothing listening on sv[0], send directly. */ | |
if (signo == SIGALRM) | |
terminate_command(child, false); | |
else if (kill(child, signo) != 0) | |
warning("kill(%d, SIG%s)", (int)child, signame); | |
} | |
} |
} |
} | } while (rc == 0); |
debug_return_int(1); | debug_return; |
} |
} |
|
|
/* |
/* |
Line 639 dispatch_pending_signals(struct command_status *cstat)
|
Line 781 dispatch_pending_signals(struct command_status *cstat)
|
* Forward signals in sigfwd_list to child listening on fd. |
* Forward signals in sigfwd_list to child listening on fd. |
*/ |
*/ |
static void |
static void |
forward_signals(int sock) | forward_signals(int sock, int what, void *v) |
{ |
{ |
char signame[SIG2STR_MAX]; |
char signame[SIG2STR_MAX]; |
struct sigforward *sigfwd; |
struct sigforward *sigfwd; |
Line 647 forward_signals(int sock)
|
Line 789 forward_signals(int sock)
|
ssize_t nsent; |
ssize_t nsent; |
debug_decl(forward_signals, SUDO_DEBUG_EXEC) |
debug_decl(forward_signals, SUDO_DEBUG_EXEC) |
|
|
while (!tq_empty(&sigfwd_list)) { | while (!TAILQ_EMPTY(&sigfwd_list)) { |
sigfwd = tq_first(&sigfwd_list); | sigfwd = TAILQ_FIRST(&sigfwd_list); |
if (sigfwd->signo == SIGCONT_FG) |
if (sigfwd->signo == SIGCONT_FG) |
strlcpy(signame, "CONT_FG", sizeof(signame)); |
strlcpy(signame, "CONT_FG", sizeof(signame)); |
else if (sigfwd->signo == SIGCONT_BG) |
else if (sigfwd->signo == SIGCONT_BG) |
Line 662 forward_signals(int sock)
|
Line 804 forward_signals(int sock)
|
do { |
do { |
nsent = send(sock, &cstat, sizeof(cstat), 0); |
nsent = send(sock, &cstat, sizeof(cstat), 0); |
} while (nsent == -1 && errno == EINTR); |
} while (nsent == -1 && errno == EINTR); |
tq_remove(&sigfwd_list, sigfwd); | TAILQ_REMOVE(&sigfwd_list, sigfwd, entries); |
efree(sigfwd); |
efree(sigfwd); |
if (nsent != sizeof(cstat)) { |
if (nsent != sizeof(cstat)) { |
if (errno == EPIPE) { |
if (errno == EPIPE) { |
|
struct sigforward *sigfwd_next; |
sudo_debug_printf(SUDO_DEBUG_ERROR, |
sudo_debug_printf(SUDO_DEBUG_ERROR, |
"broken pipe writing to child over backchannel"); |
"broken pipe writing to child over backchannel"); |
/* Other end of socket gone, empty out sigfwd_list. */ |
/* Other end of socket gone, empty out sigfwd_list. */ |
while (!tq_empty(&sigfwd_list)) { | TAILQ_FOREACH_SAFE(sigfwd, &sigfwd_list, entries, sigfwd_next) { |
sigfwd = tq_first(&sigfwd_list); | |
tq_remove(&sigfwd_list, sigfwd); | |
efree(sigfwd); |
efree(sigfwd); |
} |
} |
|
TAILQ_INIT(&sigfwd_list); |
/* XXX - child (monitor) is dead, we should exit too? */ |
/* XXX - child (monitor) is dead, we should exit too? */ |
} |
} |
break; |
break; |
} |
} |
} |
} |
debug_return; |
|
} |
} |
|
|
/* |
/* |
* Schedule a signal to be forwared. | * Schedule a signal to be forwarded. |
*/ |
*/ |
static void |
static void |
schedule_signal(int signo) | schedule_signal(struct sudo_event_base *evbase, int signo) |
{ |
{ |
struct sigforward *sigfwd; |
struct sigforward *sigfwd; |
char signame[SIG2STR_MAX]; |
char signame[SIG2STR_MAX]; |
Line 701 schedule_signal(int signo)
|
Line 842 schedule_signal(int signo)
|
sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for child", signame); |
sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for child", signame); |
|
|
sigfwd = ecalloc(1, sizeof(*sigfwd)); |
sigfwd = ecalloc(1, sizeof(*sigfwd)); |
sigfwd->prev = sigfwd; |
|
/* sigfwd->next = NULL; */ |
|
sigfwd->signo = signo; |
sigfwd->signo = signo; |
tq_append(&sigfwd_list, sigfwd); | TAILQ_INSERT_TAIL(&sigfwd_list, sigfwd, entries); |
| |
| if (sudo_ev_add(evbase, sigfwd_event, NULL, true) == -1) |
| fatal(U_("unable to add event to queue")); |
|
|
debug_return; |
debug_return; |
} |
} |