diff --git a/MANIFEST b/MANIFEST index 628cbf4de..f2ffc4956 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1198,6 +1198,7 @@ src/exec.c src/exec_common.c src/exec_intercept.c src/exec_intercept.h +src/exec_iolog.c src/exec_monitor.c src/exec_nopty.c src/exec_preload.c diff --git a/src/Makefile.in b/src/Makefile.in index d7178b75c..a3c694883 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -141,11 +141,12 @@ SHELL = @SHELL@ PROGS = @PROGS@ -OBJS = conversation.o copy_file.o edit_open.o env_hooks.o exec.o exec_common.o \ - exec_intercept.o exec_monitor.o exec_nopty.o exec_preload.o \ - exec_ptrace.o exec_pty.o get_pty.o hooks.o limits.o load_plugins.o \ - net_ifs.o parse_args.o preserve_fds.o signal.o sudo.o sudo_edit.o \ - suspend_nopty.o tcsetpgrp_nobg.o tgetpass.o ttyname.o utmp.o @SUDO_OBJS@ +OBJS = conversation.o copy_file.o edit_open.o env_hooks.o exec.o \ + exec_common.o exec_intercept.o exec_iolog.o exec_monitor.o \ + exec_nopty.o exec_preload.o exec_ptrace.o exec_pty.o get_pty.o \ + hooks.o limits.o load_plugins.o net_ifs.o parse_args.o preserve_fds.o \ + signal.o sudo.o sudo_edit.o suspend_nopty.o tcsetpgrp_nobg.o \ + tgetpass.o ttyname.o utmp.o @SUDO_OBJS@ IOBJS = $(OBJS:.o=.i) sesh.i sudo_intercept.i sudo_intercept_common.i @@ -567,6 +568,26 @@ exec_intercept.i: $(srcdir)/exec_intercept.c $(incdir)/compat/stdbool.h \ $(CC) -E -o $@ $(CPPFLAGS) $< exec_intercept.plog: exec_intercept.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec_intercept.c --i-file $< --output-file $@ +exec_iolog.o: $(srcdir)/exec_iolog.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) $(HARDENING_CFLAGS) $(srcdir)/exec_iolog.c +exec_iolog.i: $(srcdir)/exec_iolog.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) -E -o $@ $(CPPFLAGS) $< +exec_iolog.plog: exec_iolog.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec_iolog.c --i-file $< --output-file $@ 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 \ diff --git a/src/exec.c b/src/exec.c index eaae6477b..9d9003895 100644 --- a/src/exec.c +++ b/src/exec.c @@ -531,3 +531,34 @@ terminate_command(pid_t pid, bool use_pgrp) debug_return; } + +/* + * Free the dynamically-allocated contents of the exec closure. + */ +void +free_exec_closure(struct exec_closure *ec) +{ + debug_decl(free_exec_closure, SUDO_DEBUG_EXEC); + + /* Free any remaining intercept resources. */ + intercept_cleanup(); + + sudo_ev_base_free(ec->evbase); + sudo_ev_free(ec->backchannel_event); + sudo_ev_free(ec->fwdchannel_event); + sudo_ev_free(ec->sigint_event); + sudo_ev_free(ec->sigquit_event); + sudo_ev_free(ec->sigtstp_event); + sudo_ev_free(ec->sigterm_event); + sudo_ev_free(ec->sighup_event); + sudo_ev_free(ec->sigalrm_event); + sudo_ev_free(ec->sigpipe_event); + sudo_ev_free(ec->sigusr1_event); + sudo_ev_free(ec->sigusr2_event); + sudo_ev_free(ec->sigchld_event); + sudo_ev_free(ec->sigcont_event); + sudo_ev_free(ec->siginfo_event); + sudo_ev_free(ec->sigwinch_event); + + debug_return; +} diff --git a/src/exec_iolog.c b/src/exec_iolog.c new file mode 100644 index 000000000..8575ee482 --- /dev/null +++ b/src/exec_iolog.c @@ -0,0 +1,613 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2009-2022 Todd C. Miller + * + * 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. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "sudo.h" +#include "sudo_exec.h" +#include "sudo_plugin.h" +#include "sudo_plugin_int.h" + +sigset_t ttyblock; +int ttymode = TERM_COOKED; + +struct io_buffer_list iobufs = SLIST_HEAD_INITIALIZER(&iobufs); + +int io_fds[6] = { -1, -1, -1, -1, -1, -1 }; + +/* + * Remove and free any events associated with the specified + * file descriptor present in the I/O buffers list. + */ +void +ev_free_by_fd(struct sudo_event_base *evbase, int fd) +{ + struct io_buffer *iob; + debug_decl(ev_free_by_fd, SUDO_DEBUG_EXEC); + + /* Deschedule any users of the fd and free up the events. */ + SLIST_FOREACH(iob, &iobufs, entries) { + if (iob->revent != NULL) { + if (sudo_ev_get_fd(iob->revent) == fd) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: deleting and freeing revent %p with fd %d", + __func__, iob->revent, fd); + sudo_ev_free(iob->revent); + iob->revent = NULL; + } + } + if (iob->wevent != NULL) { + if (sudo_ev_get_fd(iob->wevent) == fd) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: deleting and freeing wevent %p with fd %d", + __func__, iob->wevent, fd); + sudo_ev_free(iob->wevent); + iob->wevent = NULL; + } + } + } + debug_return; +} + +/* + * Only close the fd if it is not /dev/tty or std{in,out,err}. + * Return value is the same as close(2). + */ +int +safe_close(int fd) +{ + debug_decl(safe_close, SUDO_DEBUG_EXEC); + + /* Avoid closing /dev/tty or std{in,out,err}. */ + if (fd < 3 || fd == io_fds[SFD_USERTTY]) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: not closing fd %d (%s)", __func__, fd, _PATH_TTY); + errno = EINVAL; + debug_return_int(-1); + } + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: closing fd %d", __func__, fd); + debug_return_int(close(fd)); +} + +/* + * Allocate a new I/O buffer and associated read/write events. + */ +void +io_buf_new(int rfd, int wfd, + bool (*action)(const char *, unsigned int, struct io_buffer *), + void (*read_cb)(int fd, int what, void *v), + void (*write_cb)(int fd, int what, void *v), + struct exec_closure *ec, struct io_buffer_list *head) +{ + int n; + struct io_buffer *iob; + debug_decl(io_buf_new, SUDO_DEBUG_EXEC); + + /* Set non-blocking mode. */ + n = fcntl(rfd, F_GETFL, 0); + if (n != -1 && !ISSET(n, O_NONBLOCK)) + (void) fcntl(rfd, F_SETFL, n | O_NONBLOCK); + n = fcntl(wfd, F_GETFL, 0); + if (n != -1 && !ISSET(n, O_NONBLOCK)) + (void) fcntl(wfd, F_SETFL, n | O_NONBLOCK); + + /* Allocate and add to head of list. */ + if ((iob = malloc(sizeof(*iob))) == NULL) + sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + iob->ec = ec; + iob->revent = sudo_ev_alloc(rfd, SUDO_EV_READ|SUDO_EV_PERSIST, + read_cb, iob); + iob->wevent = sudo_ev_alloc(wfd, SUDO_EV_WRITE|SUDO_EV_PERSIST, + write_cb, iob); + iob->len = 0; + iob->off = 0; + iob->action = action; + iob->buf[0] = '\0'; + if (iob->revent == NULL || iob->wevent == NULL) + sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + SLIST_INSERT_HEAD(head, iob, entries); + + debug_return; +} + +/* + * Schedule I/O events before starting the main event loop or + * resuming from suspend. + */ +void +add_io_events(struct sudo_event_base *evbase) +{ + struct io_buffer *iob; + debug_decl(add_io_events, SUDO_DEBUG_EXEC); + + /* + * Schedule all readers as long as the buffer is not full. + * Schedule writers that contain buffered data. + * Normally, write buffers are added on demand when data is read. + */ + SLIST_FOREACH(iob, &iobufs, entries) { + /* Don't read from /dev/tty if we are not in the foreground. */ + if (iob->revent != NULL && + (ttymode == TERM_RAW || !USERTTY_EVENT(iob->revent))) { + if (iob->len != sizeof(iob->buf)) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "added I/O revent %p, fd %d, events %d", + iob->revent, iob->revent->fd, iob->revent->events); + if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1) + sudo_fatal("%s", U_("unable to add event to queue")); + } + } + if (iob->wevent != NULL) { + /* Enable writer if buffer is not empty. */ + if (iob->len > iob->off) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "added I/O wevent %p, fd %d, events %d", + iob->wevent, iob->wevent->fd, iob->wevent->events); + if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1) + sudo_fatal("%s", U_("unable to add event to queue")); + } + } + } + debug_return; +} + +/* + * Flush any output buffered in iobufs or readable from fds other + * than /dev/tty. Removes I/O events from the event base when done. + */ +void +del_io_events(bool nonblocking) +{ + struct io_buffer *iob; + struct sudo_event_base *evbase; + debug_decl(del_io_events, SUDO_DEBUG_EXEC); + + /* Remove iobufs from existing event base. */ + SLIST_FOREACH(iob, &iobufs, entries) { + if (iob->revent != NULL) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "deleted I/O revent %p, fd %d, events %d", + iob->revent, iob->revent->fd, iob->revent->events); + sudo_ev_del(NULL, iob->revent); + } + if (iob->wevent != NULL) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "deleted I/O wevent %p, fd %d, events %d", + iob->wevent, iob->wevent->fd, iob->wevent->events); + sudo_ev_del(NULL, iob->wevent); + } + } + + /* Create temporary event base for flushing. */ + evbase = sudo_ev_base_alloc(); + if (evbase == NULL) + sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + + /* Avoid reading from /dev/tty, just flush existing data. */ + SLIST_FOREACH(iob, &iobufs, entries) { + /* Don't read from /dev/tty while flushing. */ + if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) { + if (iob->len != sizeof(iob->buf)) { + if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1) + sudo_fatal("%s", U_("unable to add event to queue")); + } + } + /* Flush any write buffers with data in them. */ + if (iob->wevent != NULL) { + if (iob->len > iob->off) { + if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1) + sudo_fatal("%s", U_("unable to add event to queue")); + } + } + } + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: flushing remaining I/O buffers (nonblocking)", __func__); + (void) sudo_ev_loop(evbase, SUDO_EVLOOP_NONBLOCK); + + /* + * If not in non-blocking mode, make sure we flush write buffers. + * We don't want to read from the pty or stdin since that might block + * and the command is no longer running anyway. + */ + if (!nonblocking) { + /* Clear out iobufs from event base. */ + SLIST_FOREACH(iob, &iobufs, entries) { + if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) + sudo_ev_del(evbase, iob->revent); + if (iob->wevent != NULL) + sudo_ev_del(evbase, iob->wevent); + } + + SLIST_FOREACH(iob, &iobufs, entries) { + /* Flush any write buffers with data in them. */ + if (iob->wevent != NULL) { + if (iob->len > iob->off) { + if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1) + sudo_fatal("%s", U_("unable to add event to queue")); + } + } + } + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: flushing remaining write buffers (blocking)", __func__); + (void) sudo_ev_dispatch(evbase); + + /* We should now have flushed all write buffers. */ + SLIST_FOREACH(iob, &iobufs, entries) { + if (iob->wevent != NULL) { + if (iob->len > iob->off) { + sudo_debug_printf(SUDO_DEBUG_ERROR, + "unflushed data: wevent %p, fd %d, events %d", + iob->wevent, iob->wevent->fd, iob->wevent->events); + } + } + } + } + + /* Free temporary event base, removing its events. */ + sudo_ev_base_free(evbase); + + debug_return; +} + +/* + * Free the contents of the I/O buffers queue. + */ +void +free_io_bufs(void) +{ + struct io_buffer *iob; + debug_decl(free_io_bufs, SUDO_DEBUG_EXEC); + + while ((iob = SLIST_FIRST(&iobufs)) != NULL) { + SLIST_REMOVE_HEAD(&iobufs, entries); + if (iob->revent != NULL) + sudo_ev_free(iob->revent); + if (iob->wevent != NULL) + sudo_ev_free(iob->wevent); + free(iob); + } + + debug_return; +} + +/* Call I/O plugin tty input log method. */ +bool +log_ttyin(const char *buf, unsigned int n, struct io_buffer *iob) +{ + struct plugin_container *plugin; + const char *errstr = NULL; + sigset_t omask; + bool ret = true; + debug_decl(log_ttyin, SUDO_DEBUG_EXEC); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + if (plugin->u.io->log_ttyin) { + int rc; + + sudo_debug_set_active_instance(plugin->debug_instance); + rc = plugin->u.io->log_ttyin(buf, n, &errstr); + if (rc <= 0) { + if (rc < 0) { + /* Error: disable plugin's I/O function. */ + plugin->u.io->log_ttyin = NULL; + audit_error(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("I/O plugin error"), + iob->ec->details->info); + } else { + audit_reject(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("command rejected by I/O plugin"), + iob->ec->details->info); + } + ret = false; + break; + } + } + } + sudo_debug_set_active_instance(sudo_debug_instance); + sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return_bool(ret); +} + +/* Call I/O plugin stdin log method. */ +bool +log_stdin(const char *buf, unsigned int n, struct io_buffer *iob) +{ + struct plugin_container *plugin; + const char *errstr = NULL; + sigset_t omask; + bool ret = true; + debug_decl(log_stdin, SUDO_DEBUG_EXEC); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + if (plugin->u.io->log_stdin) { + int rc; + + sudo_debug_set_active_instance(plugin->debug_instance); + rc = plugin->u.io->log_stdin(buf, n, &errstr); + if (rc <= 0) { + if (rc < 0) { + /* Error: disable plugin's I/O function. */ + plugin->u.io->log_stdin = NULL; + audit_error(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("I/O plugin error"), + iob->ec->details->info); + } else { + audit_reject(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("command rejected by I/O plugin"), + iob->ec->details->info); + } + ret = false; + break; + } + } + } + sudo_debug_set_active_instance(sudo_debug_instance); + sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return_bool(ret); +} + +/* Call I/O plugin tty output log method. */ +bool +log_ttyout(const char *buf, unsigned int n, struct io_buffer *iob) +{ + struct plugin_container *plugin; + const char *errstr = NULL; + sigset_t omask; + bool ret = true; + debug_decl(log_ttyout, SUDO_DEBUG_EXEC); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + if (plugin->u.io->log_ttyout) { + int rc; + + sudo_debug_set_active_instance(plugin->debug_instance); + rc = plugin->u.io->log_ttyout(buf, n, &errstr); + if (rc <= 0) { + if (rc < 0) { + /* Error: disable plugin's I/O function. */ + plugin->u.io->log_ttyout = NULL; + audit_error(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("I/O plugin error"), + iob->ec->details->info); + } else { + audit_reject(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("command rejected by I/O plugin"), + iob->ec->details->info); + } + ret = false; + break; + } + } + } + sudo_debug_set_active_instance(sudo_debug_instance); + if (!ret) { + /* + * I/O plugin rejected the output, delete the write event + * (user's tty) so we do not display the rejected output. + */ + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: deleting and freeing devtty wevent %p", __func__, iob->wevent); + sudo_ev_free(iob->wevent); + iob->wevent = NULL; + iob->off = iob->len = 0; + } + sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return_bool(ret); +} + +/* Call I/O plugin stdout log method. */ +bool +log_stdout(const char *buf, unsigned int n, struct io_buffer *iob) +{ + struct plugin_container *plugin; + const char *errstr = NULL; + sigset_t omask; + bool ret = true; + debug_decl(log_stdout, SUDO_DEBUG_EXEC); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + if (plugin->u.io->log_stdout) { + int rc; + + sudo_debug_set_active_instance(plugin->debug_instance); + rc = plugin->u.io->log_stdout(buf, n, &errstr); + if (rc <= 0) { + if (rc < 0) { + /* Error: disable plugin's I/O function. */ + plugin->u.io->log_stdout = NULL; + audit_error(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("I/O plugin error"), + iob->ec->details->info); + } else { + audit_reject(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("command rejected by I/O plugin"), + iob->ec->details->info); + } + ret = false; + break; + } + } + } + sudo_debug_set_active_instance(sudo_debug_instance); + if (!ret) { + /* + * I/O plugin rejected the output, delete the write event + * (user's stdout) so we do not display the rejected output. + */ + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: deleting and freeing stdout wevent %p", __func__, iob->wevent); + sudo_ev_free(iob->wevent); + iob->wevent = NULL; + iob->off = iob->len = 0; + } + sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return_bool(ret); +} + +/* Call I/O plugin stderr log method. */ +bool +log_stderr(const char *buf, unsigned int n, struct io_buffer *iob) +{ + struct plugin_container *plugin; + const char *errstr = NULL; + sigset_t omask; + bool ret = true; + debug_decl(log_stderr, SUDO_DEBUG_EXEC); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + if (plugin->u.io->log_stderr) { + int rc; + + sudo_debug_set_active_instance(plugin->debug_instance); + rc = plugin->u.io->log_stderr(buf, n, &errstr); + if (rc <= 0) { + if (rc < 0) { + /* Error: disable plugin's I/O function. */ + plugin->u.io->log_stderr = NULL; + audit_error(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("I/O plugin error"), + iob->ec->details->info); + } else { + audit_reject(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("command rejected by I/O plugin"), + iob->ec->details->info); + } + ret = false; + break; + } + } + } + sudo_debug_set_active_instance(sudo_debug_instance); + if (!ret) { + /* + * I/O plugin rejected the output, delete the write event + * (user's stderr) so we do not display the rejected output. + */ + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: deleting and freeing stderr wevent %p", __func__, iob->wevent); + sudo_ev_free(iob->wevent); + iob->wevent = NULL; + iob->off = iob->len = 0; + } + sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return_bool(ret); +} + +/* Call I/O plugin suspend log method. */ +void +log_suspend(struct exec_closure *ec, int signo) +{ + struct plugin_container *plugin; + const char *errstr = NULL; + sigset_t omask; + debug_decl(log_suspend, SUDO_DEBUG_EXEC); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + if (plugin->u.io->version < SUDO_API_MKVERSION(1, 13)) + continue; + if (plugin->u.io->log_suspend) { + int rc; + + sudo_debug_set_active_instance(plugin->debug_instance); + rc = plugin->u.io->log_suspend(signo, &errstr); + if (rc <= 0) { + /* Error: disable plugin's I/O function. */ + plugin->u.io->log_suspend = NULL; + audit_error(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("error logging suspend"), + ec->details->info); + break; + } + } + } + sudo_debug_set_active_instance(sudo_debug_instance); + sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return; +} + +/* Call I/O plugin window change log method. */ +void +log_winchange(struct exec_closure *ec, unsigned int rows, + unsigned int cols) +{ + struct plugin_container *plugin; + const char *errstr = NULL; + sigset_t omask; + debug_decl(log_winchange, SUDO_DEBUG_EXEC); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + if (plugin->u.io->version < SUDO_API_MKVERSION(1, 12)) + continue; + if (plugin->u.io->change_winsize) { + int rc; + + sudo_debug_set_active_instance(plugin->debug_instance); + rc = plugin->u.io->change_winsize(rows, cols, &errstr); + if (rc <= 0) { + /* Error: disable plugin's I/O function. */ + plugin->u.io->change_winsize = NULL; + audit_error(plugin->name, SUDO_IO_PLUGIN, + errstr ? errstr : _("error changing window size"), + ec->details->info); + break; + } + } + } + sudo_debug_set_active_instance(sudo_debug_instance); + sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return; +} + +void +init_ttyblock(void) +{ + /* So we can block tty-generated signals */ + sigemptyset(&ttyblock); + sigaddset(&ttyblock, SIGINT); + sigaddset(&ttyblock, SIGQUIT); + sigaddset(&ttyblock, SIGTSTP); + sigaddset(&ttyblock, SIGTTIN); + sigaddset(&ttyblock, SIGTTOU); +} diff --git a/src/exec_nopty.c b/src/exec_nopty.c index 95e1a3f7d..29b4d9a4e 100644 --- a/src/exec_nopty.c +++ b/src/exec_nopty.c @@ -44,39 +44,13 @@ #include "sudo_plugin.h" #include "sudo_plugin_int.h" -#ifndef __WALL -# define __WALL 0 -#endif - -struct exec_closure_nopty { - struct command_details *details; - struct sudo_event_base *evbase; - struct sudo_event *errpipe_event; - struct sudo_event *sigint_event; - struct sudo_event *sigquit_event; - struct sudo_event *sigtstp_event; - struct sudo_event *sigterm_event; - struct sudo_event *sighup_event; - struct sudo_event *sigalrm_event; - struct sudo_event *sigpipe_event; - struct sudo_event *sigusr1_event; - struct sudo_event *sigusr2_event; - struct sudo_event *sigchld_event; - struct sudo_event *sigcont_event; - struct sudo_event *siginfo_event; - struct command_status *cstat; - void *intercept; - pid_t cmnd_pid; - pid_t ppgrp; -}; - -static void handle_sigchld_nopty(struct exec_closure_nopty *ec); +static void handle_sigchld_nopty(struct exec_closure *ec); /* 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; + struct exec_closure *ec = v; ssize_t nread; int errval; debug_decl(errpipe_cb, SUDO_DEBUG_EXEC); @@ -110,7 +84,7 @@ errpipe_cb(int fd, int what, void *v) ec->cstat->type = CMD_ERRNO; ec->cstat->val = errval; } - sudo_ev_del(ec->evbase, ec->errpipe_event); + sudo_ev_del(ec->evbase, ec->backchannel_event); close(fd); break; } @@ -122,7 +96,7 @@ static void signal_cb_nopty(int signo, int what, void *v) { struct sudo_ev_siginfo_container *sc = v; - struct exec_closure_nopty *ec = sc->closure; + struct exec_closure *ec = sc->closure; char signame[SIG2STR_MAX]; debug_decl(signal_cb_nopty, SUDO_DEBUG_EXEC); @@ -203,10 +177,10 @@ signal_cb_nopty(int signo, int what, void *v) * Allocates events for the signal pipe and error pipe. */ static void -fill_exec_closure_nopty(struct exec_closure_nopty *ec, +fill_exec_closure(struct exec_closure *ec, struct command_status *cstat, struct command_details *details, int errfd) { - debug_decl(fill_exec_closure_nopty, SUDO_DEBUG_EXEC); + debug_decl(fill_exec_closure, SUDO_DEBUG_EXEC); /* Fill in the non-event part of the closure. */ ec->ppgrp = getpgrp(); @@ -218,11 +192,11 @@ fill_exec_closure_nopty(struct exec_closure_nopty *ec, details->evbase = NULL; /* Event for command status via errfd. */ - ec->errpipe_event = sudo_ev_alloc(errfd, + ec->backchannel_event = sudo_ev_alloc(errfd, SUDO_EV_READ|SUDO_EV_PERSIST, errpipe_cb, ec); - if (ec->errpipe_event == NULL) + if (ec->backchannel_event == NULL) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - if (sudo_ev_add(ec->evbase, ec->errpipe_event, NULL, false) == -1) + if (sudo_ev_add(ec->evbase, ec->backchannel_event, NULL, false) == -1) sudo_fatal("%s", U_("unable to add event to queue")); sudo_debug_printf(SUDO_DEBUG_INFO, "error pipe fd %d\n", errfd); @@ -319,42 +293,13 @@ fill_exec_closure_nopty(struct exec_closure_nopty *ec, debug_return; } -/* - * Free the dynamically-allocated contents of the exec closure. - */ -static void -free_exec_closure_nopty(struct exec_closure_nopty *ec) -{ - debug_decl(free_exec_closure_nopty, SUDO_DEBUG_EXEC); - - /* Free any remaining intercept resources. */ - intercept_cleanup(); - - sudo_ev_base_free(ec->evbase); - sudo_ev_free(ec->errpipe_event); - sudo_ev_free(ec->sigint_event); - sudo_ev_free(ec->sigquit_event); - sudo_ev_free(ec->sigtstp_event); - sudo_ev_free(ec->sigterm_event); - sudo_ev_free(ec->sighup_event); - sudo_ev_free(ec->sigalrm_event); - sudo_ev_free(ec->sigpipe_event); - sudo_ev_free(ec->sigusr1_event); - sudo_ev_free(ec->sigusr2_event); - sudo_ev_free(ec->sigchld_event); - sudo_ev_free(ec->sigcont_event); - sudo_ev_free(ec->siginfo_event); - - debug_return; -} - /* * Execute a command and wait for it to finish. */ void exec_nopty(struct command_details *details, struct command_status *cstat) { - struct exec_closure_nopty ec = { 0 }; + struct exec_closure ec = { 0 }; int intercept_sv[2] = { -1, -1 }; sigset_t set, oset; int errpipe[2]; @@ -446,7 +391,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat) * Fill in exec closure, allocate event base, signal events and * the error pipe event. */ - fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]); + fill_exec_closure(&ec, cstat, details, errpipe[0]); if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) { int rc = 1; @@ -492,7 +437,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat) #endif /* Free things up. */ - free_exec_closure_nopty(&ec); + free_exec_closure(&ec); debug_return; } @@ -503,7 +448,7 @@ exec_nopty(struct command_details *details, struct command_status *cstat) * the tty pgrp when sudo resumes. */ static void -handle_sigchld_nopty(struct exec_closure_nopty *ec) +handle_sigchld_nopty(struct exec_closure *ec) { pid_t pid; int status; diff --git a/src/exec_pty.c b/src/exec_pty.c index e2fd9c60e..44621e90a 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -48,82 +48,22 @@ #include "sudo_plugin.h" #include "sudo_plugin_int.h" -#ifndef __WALL -# define __WALL 0 -#endif - -/* Evaluates to true if the event has /dev/tty as its fd. */ -#define USERTTY_EVENT(_ev) (sudo_ev_get_fd((_ev)) == io_fds[SFD_USERTTY]) - -#define TERM_COOKED 0 -#define TERM_RAW 1 - /* Tail queue of messages to send to the monitor. */ struct monitor_message { TAILQ_ENTRY(monitor_message) entries; struct command_status cstat; }; TAILQ_HEAD(monitor_message_list, monitor_message); - -struct exec_closure_pty { - struct command_details *details; - struct sudo_event_base *evbase; - struct sudo_event *backchannel_event; - struct sudo_event *fwdchannel_event; - struct sudo_event *sigint_event; - struct sudo_event *sigquit_event; - struct sudo_event *sigtstp_event; - struct sudo_event *sigterm_event; - struct sudo_event *sighup_event; - struct sudo_event *sigalrm_event; - struct sudo_event *sigusr1_event; - struct sudo_event *sigusr2_event; - struct sudo_event *sigchld_event; - struct sudo_event *sigwinch_event; - struct command_status *cstat; - void *intercept; - struct monitor_message_list monitor_messages; - pid_t monitor_pid; - pid_t cmnd_pid; - pid_t ppgrp; - short rows; - short cols; -}; - -/* - * I/O buffer with associated read/write events and a logging action. - * Used to, e.g. pass data from the pty to the user's terminal - * and any I/O logging plugins. - */ -struct io_buffer; -typedef bool (*sudo_io_action_t)(const char *, unsigned int, struct io_buffer *); -struct io_buffer { - SLIST_ENTRY(io_buffer) entries; - struct exec_closure_pty *ec; - struct sudo_event *revent; - struct sudo_event *wevent; - sudo_io_action_t action; - int len; /* buffer length (how much produced) */ - int off; /* write position (how much already consumed) */ - char buf[64 * 1024]; -}; -SLIST_HEAD(io_buffer_list, io_buffer); +static struct monitor_message_list monitor_messages = + TAILQ_HEAD_INITIALIZER(monitor_messages); static char ptyname[PATH_MAX]; -int io_fds[6] = { -1, -1, -1, -1, -1, -1}; static bool foreground, pipeline; -static int ttymode = TERM_COOKED; -static sigset_t ttyblock; -static struct io_buffer_list iobufs; static const char *utmp_user; -static void del_io_events(bool nonblocking); -static void sync_ttysize(struct exec_closure_pty *ec); -static int safe_close(int fd); -static void ev_free_by_fd(struct sudo_event_base *evbase, int fd); -static pid_t check_foreground(struct exec_closure_pty *ec); -static void add_io_events(struct sudo_event_base *evbase); -static void schedule_signal(struct exec_closure_pty *ec, int signo); +static void sync_ttysize(struct exec_closure *ec); +static pid_t check_foreground(struct exec_closure *ec); +static void schedule_signal(struct exec_closure *ec, int signo); /* * Cleanup hook for sudo_fatal()/sudo_fatalx() @@ -202,307 +142,6 @@ pty_make_controlling(void) return 0; } -/* Call I/O plugin tty input log method. */ -static bool -log_ttyin(const char *buf, unsigned int n, struct io_buffer *iob) -{ - struct plugin_container *plugin; - const char *errstr = NULL; - sigset_t omask; - bool ret = true; - debug_decl(log_ttyin, SUDO_DEBUG_EXEC); - - sigprocmask(SIG_BLOCK, &ttyblock, &omask); - TAILQ_FOREACH(plugin, &io_plugins, entries) { - if (plugin->u.io->log_ttyin) { - int rc; - - sudo_debug_set_active_instance(plugin->debug_instance); - rc = plugin->u.io->log_ttyin(buf, n, &errstr); - if (rc <= 0) { - if (rc < 0) { - /* Error: disable plugin's I/O function. */ - plugin->u.io->log_ttyin = NULL; - audit_error(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("I/O plugin error"), - iob->ec->details->info); - } else { - audit_reject(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("command rejected by I/O plugin"), - iob->ec->details->info); - } - ret = false; - break; - } - } - } - sudo_debug_set_active_instance(sudo_debug_instance); - sigprocmask(SIG_SETMASK, &omask, NULL); - - debug_return_bool(ret); -} - -/* Call I/O plugin stdin log method. */ -static bool -log_stdin(const char *buf, unsigned int n, struct io_buffer *iob) -{ - struct plugin_container *plugin; - const char *errstr = NULL; - sigset_t omask; - bool ret = true; - debug_decl(log_stdin, SUDO_DEBUG_EXEC); - - sigprocmask(SIG_BLOCK, &ttyblock, &omask); - TAILQ_FOREACH(plugin, &io_plugins, entries) { - if (plugin->u.io->log_stdin) { - int rc; - - sudo_debug_set_active_instance(plugin->debug_instance); - rc = plugin->u.io->log_stdin(buf, n, &errstr); - if (rc <= 0) { - if (rc < 0) { - /* Error: disable plugin's I/O function. */ - plugin->u.io->log_stdin = NULL; - audit_error(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("I/O plugin error"), - iob->ec->details->info); - } else { - audit_reject(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("command rejected by I/O plugin"), - iob->ec->details->info); - } - ret = false; - break; - } - } - } - sudo_debug_set_active_instance(sudo_debug_instance); - sigprocmask(SIG_SETMASK, &omask, NULL); - - debug_return_bool(ret); -} - -/* Call I/O plugin tty output log method. */ -static bool -log_ttyout(const char *buf, unsigned int n, struct io_buffer *iob) -{ - struct plugin_container *plugin; - const char *errstr = NULL; - sigset_t omask; - bool ret = true; - debug_decl(log_ttyout, SUDO_DEBUG_EXEC); - - sigprocmask(SIG_BLOCK, &ttyblock, &omask); - TAILQ_FOREACH(plugin, &io_plugins, entries) { - if (plugin->u.io->log_ttyout) { - int rc; - - sudo_debug_set_active_instance(plugin->debug_instance); - rc = plugin->u.io->log_ttyout(buf, n, &errstr); - if (rc <= 0) { - if (rc < 0) { - /* Error: disable plugin's I/O function. */ - plugin->u.io->log_ttyout = NULL; - audit_error(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("I/O plugin error"), - iob->ec->details->info); - } else { - audit_reject(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("command rejected by I/O plugin"), - iob->ec->details->info); - } - ret = false; - break; - } - } - } - sudo_debug_set_active_instance(sudo_debug_instance); - if (!ret) { - /* - * I/O plugin rejected the output, delete the write event - * (user's tty) so we do not display the rejected output. - */ - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: deleting and freeing devtty wevent %p", __func__, iob->wevent); - sudo_ev_free(iob->wevent); - iob->wevent = NULL; - iob->off = iob->len = 0; - } - sigprocmask(SIG_SETMASK, &omask, NULL); - - debug_return_bool(ret); -} - -/* Call I/O plugin stdout log method. */ -static bool -log_stdout(const char *buf, unsigned int n, struct io_buffer *iob) -{ - struct plugin_container *plugin; - const char *errstr = NULL; - sigset_t omask; - bool ret = true; - debug_decl(log_stdout, SUDO_DEBUG_EXEC); - - sigprocmask(SIG_BLOCK, &ttyblock, &omask); - TAILQ_FOREACH(plugin, &io_plugins, entries) { - if (plugin->u.io->log_stdout) { - int rc; - - sudo_debug_set_active_instance(plugin->debug_instance); - rc = plugin->u.io->log_stdout(buf, n, &errstr); - if (rc <= 0) { - if (rc < 0) { - /* Error: disable plugin's I/O function. */ - plugin->u.io->log_stdout = NULL; - audit_error(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("I/O plugin error"), - iob->ec->details->info); - } else { - audit_reject(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("command rejected by I/O plugin"), - iob->ec->details->info); - } - ret = false; - break; - } - } - } - sudo_debug_set_active_instance(sudo_debug_instance); - if (!ret) { - /* - * I/O plugin rejected the output, delete the write event - * (user's stdout) so we do not display the rejected output. - */ - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: deleting and freeing stdout wevent %p", __func__, iob->wevent); - sudo_ev_free(iob->wevent); - iob->wevent = NULL; - iob->off = iob->len = 0; - } - sigprocmask(SIG_SETMASK, &omask, NULL); - - debug_return_bool(ret); -} - -/* Call I/O plugin stderr log method. */ -static bool -log_stderr(const char *buf, unsigned int n, struct io_buffer *iob) -{ - struct plugin_container *plugin; - const char *errstr = NULL; - sigset_t omask; - bool ret = true; - debug_decl(log_stderr, SUDO_DEBUG_EXEC); - - sigprocmask(SIG_BLOCK, &ttyblock, &omask); - TAILQ_FOREACH(plugin, &io_plugins, entries) { - if (plugin->u.io->log_stderr) { - int rc; - - sudo_debug_set_active_instance(plugin->debug_instance); - rc = plugin->u.io->log_stderr(buf, n, &errstr); - if (rc <= 0) { - if (rc < 0) { - /* Error: disable plugin's I/O function. */ - plugin->u.io->log_stderr = NULL; - audit_error(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("I/O plugin error"), - iob->ec->details->info); - } else { - audit_reject(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("command rejected by I/O plugin"), - iob->ec->details->info); - } - ret = false; - break; - } - } - } - sudo_debug_set_active_instance(sudo_debug_instance); - if (!ret) { - /* - * I/O plugin rejected the output, delete the write event - * (user's stderr) so we do not display the rejected output. - */ - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: deleting and freeing stderr wevent %p", __func__, iob->wevent); - sudo_ev_free(iob->wevent); - iob->wevent = NULL; - iob->off = iob->len = 0; - } - sigprocmask(SIG_SETMASK, &omask, NULL); - - debug_return_bool(ret); -} - -/* Call I/O plugin suspend log method. */ -static void -log_suspend(struct exec_closure_pty *ec, int signo) -{ - struct plugin_container *plugin; - const char *errstr = NULL; - sigset_t omask; - debug_decl(log_suspend, SUDO_DEBUG_EXEC); - - sigprocmask(SIG_BLOCK, &ttyblock, &omask); - TAILQ_FOREACH(plugin, &io_plugins, entries) { - if (plugin->u.io->version < SUDO_API_MKVERSION(1, 13)) - continue; - if (plugin->u.io->log_suspend) { - int rc; - - sudo_debug_set_active_instance(plugin->debug_instance); - rc = plugin->u.io->log_suspend(signo, &errstr); - if (rc <= 0) { - /* Error: disable plugin's I/O function. */ - plugin->u.io->log_suspend = NULL; - audit_error(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("error logging suspend"), - ec->details->info); - break; - } - } - } - sudo_debug_set_active_instance(sudo_debug_instance); - sigprocmask(SIG_SETMASK, &omask, NULL); - - debug_return; -} - -/* Call I/O plugin window change log method. */ -static void -log_winchange(struct exec_closure_pty *ec, unsigned int rows, unsigned int cols) -{ - struct plugin_container *plugin; - const char *errstr = NULL; - sigset_t omask; - debug_decl(log_winchange, SUDO_DEBUG_EXEC); - - sigprocmask(SIG_BLOCK, &ttyblock, &omask); - TAILQ_FOREACH(plugin, &io_plugins, entries) { - if (plugin->u.io->version < SUDO_API_MKVERSION(1, 12)) - continue; - if (plugin->u.io->change_winsize) { - int rc; - - sudo_debug_set_active_instance(plugin->debug_instance); - rc = plugin->u.io->change_winsize(rows, cols, &errstr); - if (rc <= 0) { - /* Error: disable plugin's I/O function. */ - plugin->u.io->change_winsize = NULL; - audit_error(plugin->name, SUDO_IO_PLUGIN, - errstr ? errstr : _("error changing window size"), - ec->details->info); - break; - } - } - } - sudo_debug_set_active_instance(sudo_debug_instance); - sigprocmask(SIG_SETMASK, &omask, NULL); - - debug_return; -} - /* * Check whether we are running in the foregroup. * Updates the foreground global and updates the window size. @@ -510,7 +149,7 @@ log_winchange(struct exec_closure_pty *ec, unsigned int rows, unsigned int cols) * on success, or -1 on failure (tty revoked). */ static pid_t -check_foreground(struct exec_closure_pty *ec) +check_foreground(struct exec_closure *ec) { int ret = 0; debug_decl(check_foreground, SUDO_DEBUG_EXEC); @@ -532,7 +171,7 @@ check_foreground(struct exec_closure_pty *ec) * foreground or SIGCONT_BG if it is a background process. */ static int -suspend_sudo_pty(struct exec_closure_pty *ec, int signo) +suspend_sudo_pty(struct exec_closure *ec, int signo) { char signame[SIG2STR_MAX]; struct sigaction sa, osa; @@ -823,71 +462,25 @@ write_callback(int fd, int what, void *v) debug_return; } -static void -io_buf_new(int rfd, int wfd, - bool (*action)(const char *, unsigned int, struct io_buffer *), - struct exec_closure_pty *ec, struct io_buffer_list *head) -{ - int n; - struct io_buffer *iob; - debug_decl(io_buf_new, SUDO_DEBUG_EXEC); - - /* Set non-blocking mode. */ - n = fcntl(rfd, F_GETFL, 0); - if (n != -1 && !ISSET(n, O_NONBLOCK)) - (void) fcntl(rfd, F_SETFL, n | O_NONBLOCK); - n = fcntl(wfd, F_GETFL, 0); - if (n != -1 && !ISSET(n, O_NONBLOCK)) - (void) fcntl(wfd, F_SETFL, n | O_NONBLOCK); - - /* Allocate and add to head of list. */ - if ((iob = malloc(sizeof(*iob))) == NULL) - sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - iob->ec = ec; - iob->revent = sudo_ev_alloc(rfd, SUDO_EV_READ|SUDO_EV_PERSIST, - read_callback, iob); - iob->wevent = sudo_ev_alloc(wfd, SUDO_EV_WRITE|SUDO_EV_PERSIST, - write_callback, iob); - iob->len = 0; - iob->off = 0; - iob->action = action; - iob->buf[0] = '\0'; - if (iob->revent == NULL || iob->wevent == NULL) - sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - SLIST_INSERT_HEAD(head, iob, entries); - - debug_return; -} - /* * We already closed the follower so reads from the leader will not block. */ static void pty_finish(struct command_status *cstat) { - struct io_buffer *iob; - int n; + int flags; debug_decl(pty_finish, SUDO_DEBUG_EXEC); - /* Flush any remaining output (the plugin already got it). */ + /* Flush any remaining output (the plugin already got it) and free bufs. */ if (io_fds[SFD_USERTTY] != -1) { - n = fcntl(io_fds[SFD_USERTTY], F_GETFL, 0); - if (n != -1 && ISSET(n, O_NONBLOCK)) { - CLR(n, O_NONBLOCK); - (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, n); + flags = fcntl(io_fds[SFD_USERTTY], F_GETFL, 0); + if (flags != -1 && ISSET(flags, O_NONBLOCK)) { + CLR(flags, O_NONBLOCK); + (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, flags); } } del_io_events(false); - - /* Free I/O buffers. */ - while ((iob = SLIST_FIRST(&iobufs)) != NULL) { - SLIST_REMOVE_HEAD(&iobufs, entries); - if (iob->revent != NULL) - sudo_ev_free(iob->revent); - if (iob->wevent != NULL) - sudo_ev_free(iob->wevent); - free(iob); - } + free_io_bufs(); /* Restore terminal settings. */ if (io_fds[SFD_USERTTY] != -1) @@ -904,7 +497,7 @@ pty_finish(struct command_status *cstat) * Send command status to the monitor (signal or window size change). */ static void -send_command_status(struct exec_closure_pty *ec, int type, int val) +send_command_status(struct exec_closure *ec, int type, int val) { struct monitor_message *msg; debug_decl(send_command, SUDO_DEBUG_EXEC); @@ -913,7 +506,7 @@ send_command_status(struct exec_closure_pty *ec, int type, int val) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); msg->cstat.type = type; msg->cstat.val = val; - TAILQ_INSERT_TAIL(&ec->monitor_messages, msg, entries); + TAILQ_INSERT_TAIL(&monitor_messages, msg, entries); if (sudo_ev_add(ec->evbase, ec->fwdchannel_event, NULL, true) == -1) sudo_fatal("%s", U_("unable to add event to queue")); @@ -928,7 +521,7 @@ send_command_status(struct exec_closure_pty *ec, int type, int val) * Schedule a signal to be forwarded. */ static void -schedule_signal(struct exec_closure_pty *ec, int signo) +schedule_signal(struct exec_closure *ec, int signo) { char signame[SIG2STR_MAX]; debug_decl(schedule_signal, SUDO_DEBUG_EXEC); @@ -949,10 +542,27 @@ schedule_signal(struct exec_closure_pty *ec, int signo) debug_return; } +/* + * Free any remaining monitor messages in the queue. + */ +static void +flush_monitor_messages(void) +{ + struct monitor_message *msg; + debug_decl(flush_monitor_messages, SUDO_DEBUG_EXEC); + + while ((msg = TAILQ_FIRST(&monitor_messages)) != NULL) { + TAILQ_REMOVE(&monitor_messages, msg, entries); + free(msg); + } + + debug_return; +} + static void backchannel_cb(int fd, int what, void *v) { - struct exec_closure_pty *ec = v; + struct exec_closure *ec = v; struct command_status cstat; ssize_t nread; debug_decl(backchannel_cb, SUDO_DEBUG_EXEC); @@ -1060,7 +670,7 @@ backchannel_cb(int fd, int what, void *v) * Handle changes to the monitors's status (SIGCHLD). */ static void -handle_sigchld_pty(struct exec_closure_pty *ec) +handle_sigchld_pty(struct exec_closure *ec) { int n, status; pid_t pid; @@ -1129,7 +739,7 @@ static void signal_cb_pty(int signo, int what, void *v) { struct sudo_ev_siginfo_container *sc = v; - struct exec_closure_pty *ec = sc->closure; + struct exec_closure *ec = sc->closure; char signame[SIG2STR_MAX]; debug_decl(signal_cb_pty, SUDO_DEBUG_EXEC); @@ -1180,13 +790,13 @@ signal_cb_pty(int signo, int what, void *v) static void fwdchannel_cb(int sock, int what, void *v) { - struct exec_closure_pty *ec = v; + struct exec_closure *ec = v; char signame[SIG2STR_MAX]; struct monitor_message *msg; ssize_t nsent; debug_decl(fwdchannel_cb, SUDO_DEBUG_EXEC); - while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) { + while ((msg = TAILQ_FIRST(&monitor_messages)) != NULL) { switch (msg->cstat.type) { case CMD_SIGNO: if (msg->cstat.val == SIGCONT_FG) @@ -1209,7 +819,7 @@ fwdchannel_cb(int sock, int what, void *v) msg->cstat.type, msg->cstat.val); break; } - TAILQ_REMOVE(&ec->monitor_messages, msg, entries); + TAILQ_REMOVE(&monitor_messages, msg, entries); nsent = send(sock, &msg->cstat, sizeof(msg->cstat), 0); if (nsent != sizeof(msg->cstat)) { if (errno == EPIPE) { @@ -1217,10 +827,7 @@ fwdchannel_cb(int sock, int what, void *v) "broken pipe writing to monitor over backchannel"); /* Other end of socket gone, empty out monitor_messages. */ free(msg); - while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) { - TAILQ_REMOVE(&ec->monitor_messages, msg, entries); - free(msg); - } + flush_monitor_messages(); /* XXX - need new CMD_ type for monitor errors. */ ec->cstat->type = CMD_ERRNO; ec->cstat->val = errno; @@ -1238,10 +845,10 @@ fwdchannel_cb(int sock, int what, void *v) * Forwarded signals on the backchannel are enabled on demand. */ static void -fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat, +fill_exec_closure(struct exec_closure *ec, struct command_status *cstat, struct command_details *details, pid_t ppgrp, int backchannel) { - debug_decl(fill_exec_closure_pty, SUDO_DEBUG_EXEC); + debug_decl(fill_exec_closure, SUDO_DEBUG_EXEC); /* Fill in the non-event part of the closure. */ ec->cmnd_pid = -1; @@ -1250,7 +857,6 @@ fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat, ec->details = details; ec->rows = user_details.ts_rows; ec->cols = user_details.ts_cols; - TAILQ_INIT(&ec->monitor_messages); /* Reset cstat for running the command. */ cstat->type = CMD_INVALID; @@ -1352,40 +958,6 @@ fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat, debug_return; } -/* - * Free the dynamically-allocated contents of the exec closure. - */ -static void -free_exec_closure_pty(struct exec_closure_pty *ec) -{ - struct monitor_message *msg; - debug_decl(free_exec_closure_pty, SUDO_DEBUG_EXEC); - - /* Free any remaining intercept resources. */ - intercept_cleanup(); - - sudo_ev_base_free(ec->evbase); - sudo_ev_free(ec->backchannel_event); - sudo_ev_free(ec->fwdchannel_event); - sudo_ev_free(ec->sigint_event); - sudo_ev_free(ec->sigquit_event); - sudo_ev_free(ec->sigtstp_event); - sudo_ev_free(ec->sigterm_event); - sudo_ev_free(ec->sighup_event); - sudo_ev_free(ec->sigalrm_event); - sudo_ev_free(ec->sigusr1_event); - sudo_ev_free(ec->sigusr2_event); - sudo_ev_free(ec->sigchld_event); - sudo_ev_free(ec->sigwinch_event); - - while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) { - TAILQ_REMOVE(&ec->monitor_messages, msg, entries); - free(msg); - } - - debug_return; -} - /* * Execute a command in a pty, potentially with I/O logging, and * wait for it to finish. @@ -1398,7 +970,7 @@ exec_pty(struct command_details *details, struct command_status *cstat) int io_pipe[3][2] = { { -1, -1 }, { -1, -1 }, { -1, -1 } }; bool interpose[3] = { false, false, false }; int sv[2], intercept_sv[2] = { -1, -1 }; - struct exec_closure_pty ec = { 0 }; + struct exec_closure ec = { 0 }; struct plugin_container *plugin; int evloop_retries = -1; sigset_t set, oset; @@ -1461,15 +1033,7 @@ exec_pty(struct command_details *details, struct command_status *cstat) * Child will run the command in the pty, parent will pass data * to and from pty. */ - - /* So we can block tty-generated signals */ - sigemptyset(&ttyblock); - sigaddset(&ttyblock, SIGINT); - sigaddset(&ttyblock, SIGQUIT); - sigaddset(&ttyblock, SIGTSTP); - sigaddset(&ttyblock, SIGTTIN); - sigaddset(&ttyblock, SIGTTOU); - + init_ttyblock(); ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */ /* Determine whether any of std{in,out,err} should be logged. */ @@ -1495,12 +1059,12 @@ exec_pty(struct command_details *details, struct command_status *cstat) /* Read from /dev/tty, write to pty leader */ if (!ISSET(details->flags, CD_BACKGROUND)) { io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_LEADER], - log_ttyin, &ec, &iobufs); + log_ttyin, read_callback, write_callback, &ec, &iobufs); } /* Read from pty leader, write to /dev/tty */ io_buf_new(io_fds[SFD_LEADER], io_fds[SFD_USERTTY], - log_ttyout, &ec, &iobufs); + log_ttyout, read_callback, write_callback, &ec, &iobufs); /* Are we the foreground process? */ foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp; @@ -1527,7 +1091,7 @@ exec_pty(struct command_details *details, struct command_status *cstat) if (pipe2(io_pipe[STDIN_FILENO], O_CLOEXEC) != 0) sudo_fatal("%s", U_("unable to create pipe")); io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1], - log_stdin, &ec, &iobufs); + log_stdin, read_callback, write_callback, &ec, &iobufs); io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0]; } } @@ -1548,7 +1112,7 @@ exec_pty(struct command_details *details, struct command_status *cstat) if (pipe2(io_pipe[STDOUT_FILENO], O_CLOEXEC) != 0) sudo_fatal("%s", U_("unable to create pipe")); io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO, - log_stdout, &ec, &iobufs); + log_stdout, read_callback, write_callback, &ec, &iobufs); io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1]; } } @@ -1568,7 +1132,7 @@ exec_pty(struct command_details *details, struct command_status *cstat) if (pipe2(io_pipe[STDERR_FILENO], O_CLOEXEC) != 0) sudo_fatal("%s", U_("unable to create pipe")); io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO, - log_stderr, &ec, &iobufs); + log_stderr, read_callback, write_callback, &ec, &iobufs); io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1]; } } @@ -1676,7 +1240,7 @@ exec_pty(struct command_details *details, struct command_status *cstat) * Fill in exec closure, allocate event base, signal events and * the backchannel event. */ - fill_exec_closure_pty(&ec, cstat, details, ppgrp, sv[0]); + fill_exec_closure(&ec, cstat, details, ppgrp, sv[0]); /* Create event and closure for intercept mode. */ if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) { @@ -1734,156 +1298,17 @@ exec_pty(struct command_details *details, struct command_status *cstat) pty_finish(cstat); /* Free things up. */ - free_exec_closure_pty(&ec); + free_exec_closure(&ec); debug_return_bool(true); } -/* - * Schedule I/O events before starting the main event loop or - * resuming from suspend. - */ -static void -add_io_events(struct sudo_event_base *evbase) -{ - struct io_buffer *iob; - debug_decl(add_io_events, SUDO_DEBUG_EXEC); - - /* - * Schedule all readers as long as the buffer is not full. - * Schedule writers that contain buffered data. - * Normally, write buffers are added on demand when data is read. - */ - SLIST_FOREACH(iob, &iobufs, entries) { - /* Don't read from /dev/tty if we are not in the foreground. */ - if (iob->revent != NULL && - (ttymode == TERM_RAW || !USERTTY_EVENT(iob->revent))) { - if (iob->len != sizeof(iob->buf)) { - sudo_debug_printf(SUDO_DEBUG_INFO, - "added I/O revent %p, fd %d, events %d", - iob->revent, iob->revent->fd, iob->revent->events); - if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1) - sudo_fatal("%s", U_("unable to add event to queue")); - } - } - if (iob->wevent != NULL) { - /* Enable writer if buffer is not empty. */ - if (iob->len > iob->off) { - sudo_debug_printf(SUDO_DEBUG_INFO, - "added I/O wevent %p, fd %d, events %d", - iob->wevent, iob->wevent->fd, iob->wevent->events); - if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1) - sudo_fatal("%s", U_("unable to add event to queue")); - } - } - } - debug_return; -} - -/* - * Flush any output buffered in iobufs or readable from fds other - * than /dev/tty. Removes I/O events from the event base when done. - */ -static void -del_io_events(bool nonblocking) -{ - struct io_buffer *iob; - struct sudo_event_base *evbase; - debug_decl(del_io_events, SUDO_DEBUG_EXEC); - - /* Remove iobufs from existing event base. */ - SLIST_FOREACH(iob, &iobufs, entries) { - if (iob->revent != NULL) { - sudo_debug_printf(SUDO_DEBUG_INFO, - "deleted I/O revent %p, fd %d, events %d", - iob->revent, iob->revent->fd, iob->revent->events); - sudo_ev_del(NULL, iob->revent); - } - if (iob->wevent != NULL) { - sudo_debug_printf(SUDO_DEBUG_INFO, - "deleted I/O wevent %p, fd %d, events %d", - iob->wevent, iob->wevent->fd, iob->wevent->events); - sudo_ev_del(NULL, iob->wevent); - } - } - - /* Create temporary event base for flushing. */ - evbase = sudo_ev_base_alloc(); - if (evbase == NULL) - sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - - /* Avoid reading from /dev/tty, just flush existing data. */ - SLIST_FOREACH(iob, &iobufs, entries) { - /* Don't read from /dev/tty while flushing. */ - if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) { - if (iob->len != sizeof(iob->buf)) { - if (sudo_ev_add(evbase, iob->revent, NULL, false) == -1) - sudo_fatal("%s", U_("unable to add event to queue")); - } - } - /* Flush any write buffers with data in them. */ - if (iob->wevent != NULL) { - if (iob->len > iob->off) { - if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1) - sudo_fatal("%s", U_("unable to add event to queue")); - } - } - } - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: flushing remaining I/O buffers (nonblocking)", __func__); - (void) sudo_ev_loop(evbase, SUDO_EVLOOP_NONBLOCK); - - /* - * If not in non-blocking mode, make sure we flush write buffers. - * We don't want to read from the pty or stdin since that might block - * and the command is no longer running anyway. - */ - if (!nonblocking) { - /* Clear out iobufs from event base. */ - SLIST_FOREACH(iob, &iobufs, entries) { - if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) - sudo_ev_del(evbase, iob->revent); - if (iob->wevent != NULL) - sudo_ev_del(evbase, iob->wevent); - } - - SLIST_FOREACH(iob, &iobufs, entries) { - /* Flush any write buffers with data in them. */ - if (iob->wevent != NULL) { - if (iob->len > iob->off) { - if (sudo_ev_add(evbase, iob->wevent, NULL, false) == -1) - sudo_fatal("%s", U_("unable to add event to queue")); - } - } - } - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: flushing remaining write buffers (blocking)", __func__); - (void) sudo_ev_dispatch(evbase); - - /* We should now have flushed all write buffers. */ - SLIST_FOREACH(iob, &iobufs, entries) { - if (iob->wevent != NULL) { - if (iob->len > iob->off) { - sudo_debug_printf(SUDO_DEBUG_ERROR, - "unflushed data: wevent %p, fd %d, events %d", - iob->wevent, iob->wevent->fd, iob->wevent->events); - } - } - } - } - - /* Free temporary event base, removing its events. */ - sudo_ev_base_free(evbase); - - debug_return; -} - /* * Check for tty size changes. * Passes the new window size to the I/O plugin and to the monitor. */ static void -sync_ttysize(struct exec_closure_pty *ec) +sync_ttysize(struct exec_closure *ec) { struct winsize wsize; debug_decl(sync_ttysize, SUDO_DEBUG_EXEC); @@ -1907,57 +1332,3 @@ sync_ttysize(struct exec_closure_pty *ec) debug_return; } - -/* - * Remove and free any events associated with the specified - * file descriptor present in the I/O buffers list. - */ -static void -ev_free_by_fd(struct sudo_event_base *evbase, int fd) -{ - struct io_buffer *iob; - debug_decl(ev_free_by_fd, SUDO_DEBUG_EXEC); - - /* Deschedule any users of the fd and free up the events. */ - SLIST_FOREACH(iob, &iobufs, entries) { - if (iob->revent != NULL) { - if (sudo_ev_get_fd(iob->revent) == fd) { - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: deleting and freeing revent %p with fd %d", - __func__, iob->revent, fd); - sudo_ev_free(iob->revent); - iob->revent = NULL; - } - } - if (iob->wevent != NULL) { - if (sudo_ev_get_fd(iob->wevent) == fd) { - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: deleting and freeing wevent %p with fd %d", - __func__, iob->wevent, fd); - sudo_ev_free(iob->wevent); - iob->wevent = NULL; - } - } - } - debug_return; -} - -/* - * Only close the fd if it is not /dev/tty or std{in,out,err}. - * Return value is the same as close(2). - */ -static int -safe_close(int fd) -{ - debug_decl(safe_close, SUDO_DEBUG_EXEC); - - /* Avoid closing /dev/tty or std{in,out,err}. */ - if (fd < 3 || fd == io_fds[SFD_USERTTY]) { - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: not closing fd %d (%s)", __func__, fd, _PATH_TTY); - errno = EINVAL; - debug_return_int(-1); - } - sudo_debug_printf(SUDO_DEBUG_INFO, "%s: closing fd %d", __func__, fd); - debug_return_int(close(fd)); -} diff --git a/src/regress/noexec/check_noexec.c b/src/regress/noexec/check_noexec.c index e62869e17..4e0a402e8 100644 --- a/src/regress/noexec/check_noexec.c +++ b/src/regress/noexec/check_noexec.c @@ -39,6 +39,7 @@ #include "sudo_compat.h" #include "sudo_fatal.h" #include "sudo_util.h" +#include "sudo_queue.h" #include "sudo_exec.h" static bool verbose; diff --git a/src/sudo_exec.h b/src/sudo_exec.h index b8a8bcff2..819957e9e 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -26,6 +26,13 @@ # define MSG_WAITALL 0 #endif +/* + * Linux-specific wait flag used with ptrace(2). + */ +#ifndef __WALL +# define __WALL 0 +#endif + /* * Some older systems support siginfo but predate SI_USER. */ @@ -35,8 +42,66 @@ # define USER_SIGNALED(_info) ((_info) != NULL && (_info)->si_code <= 0) #endif +/* Values for ttymode. */ +#define TERM_COOKED 0 +#define TERM_RAW 1 + +struct command_details; +struct command_status; +struct sudo_event_base; +struct stat; + /* - * Indices into io_fds[] when running a command in a pty. + * Closure passed to I/O event callbacks. + */ +struct exec_closure { + struct command_details *details; + struct sudo_event_base *evbase; + struct sudo_event *backchannel_event; + struct sudo_event *fwdchannel_event; + struct sudo_event *sigint_event; + struct sudo_event *sigquit_event; + struct sudo_event *sigtstp_event; + struct sudo_event *sigterm_event; + struct sudo_event *sighup_event; + struct sudo_event *sigalrm_event; + struct sudo_event *sigpipe_event; + struct sudo_event *sigusr1_event; + struct sudo_event *sigusr2_event; + struct sudo_event *sigchld_event; + struct sudo_event *sigcont_event; + struct sudo_event *siginfo_event; + struct sudo_event *sigwinch_event; + struct command_status *cstat; + void *intercept; + pid_t monitor_pid; + pid_t cmnd_pid; + pid_t ppgrp; + short rows; + short cols; +}; + +/* + * I/O buffer with associated read/write events and a logging action. + * Used to, e.g. pass data from the pty to the user's terminal + * and any I/O logging plugins. + */ +struct io_buffer; +typedef bool (*sudo_io_action_t)(const char *, unsigned int, struct io_buffer *); +struct io_buffer { + SLIST_ENTRY(io_buffer) entries; + struct exec_closure *ec; + struct sudo_event *revent; + struct sudo_event *wevent; + sudo_io_action_t action; + int len; /* buffer length (how much produced) */ + int off; /* write position (how much already consumed) */ + char buf[64 * 1024]; +}; +SLIST_HEAD(io_buffer_list, io_buffer); + +/* + * Indices into io_fds[] when logging I/O. */ #define SFD_STDIN 0 #define SFD_STDOUT 1 @@ -45,6 +110,9 @@ #define SFD_FOLLOWER 4 #define SFD_USERTTY 5 +/* Evaluates to true if the event has /dev/tty as its fd. */ +#define USERTTY_EVENT(_ev) (sudo_ev_get_fd((_ev)) == io_fds[SFD_USERTTY]) + /* * Special values to indicate whether continuing in foreground or background. */ @@ -104,18 +172,11 @@ union sudo_token_un { # endif /* HAVE_DECL_SECCOMP_MODE_FILTER */ #endif /* _PATH_SUDO_INTERCEPT && __linux__ */ -/* - * Symbols shared between exec.c, exec_nopty.c, exec_pty.c and exec_monitor.c - */ -struct command_details; -struct command_status; -struct sudo_event_base; -struct stat; - /* exec.c */ void exec_cmnd(struct command_details *details, sigset_t *mask, int intercept_fd, int errfd); void terminate_command(pid_t pid, bool use_pgrp); bool sudo_terminated(struct command_status *cstat); +void free_exec_closure(struct exec_closure *ec); /* exec_common.c */ int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], int intercept_fd, int flags); @@ -126,6 +187,24 @@ char **enable_monitor(char *envp[], const char *dso); void *intercept_setup(int fd, struct sudo_event_base *evbase, struct command_details *details); void intercept_cleanup(void); +/* exec_iolog.c */ +bool log_ttyin(const char *buf, unsigned int n, struct io_buffer *iob); +bool log_stdin(const char *buf, unsigned int n, struct io_buffer *iob); +bool log_ttyout(const char *buf, unsigned int n, struct io_buffer *iob); +bool log_stdout(const char *buf, unsigned int n, struct io_buffer *iob); +bool log_stderr(const char *buf, unsigned int n, struct io_buffer *iob); +void log_suspend(struct exec_closure *ec, int signo); +void log_winchange(struct exec_closure *ec, unsigned int rows, unsigned int cols); +void io_buf_new(int rfd, int wfd, bool (*action)(const char *, unsigned int, struct io_buffer *), void (*read_cb)(int fd, int what, void *v), void (*write_cb)(int fd, int what, void *v), struct exec_closure *ec, struct io_buffer_list *head); +int safe_close(int fd); +void ev_free_by_fd(struct sudo_event_base *evbase, int fd); +void free_io_bufs(void); +void add_io_events(struct sudo_event_base *evbase); +void del_io_events(bool nonblocking); +void init_ttyblock(void); +extern struct io_buffer_list iobufs; +extern int ttymode; + /* exec_nopty.c */ void exec_nopty(struct command_details *details, struct command_status *cstat); diff --git a/src/sudo_intercept_common.c b/src/sudo_intercept_common.c index 9a21fefc2..d28c00184 100644 --- a/src/sudo_intercept_common.c +++ b/src/sudo_intercept_common.c @@ -51,8 +51,8 @@ #include "sudo_compat.h" #include "sudo_conf.h" #include "sudo_debug.h" -#include "sudo_fatal.h" #include "sudo_exec.h" +#include "sudo_fatal.h" #include "sudo_gettext.h" #include "sudo_util.h" #include "intercept.pb-c.h"