From 54acf4f9918f62c8cc7a99a22d6e291051eb8bc2 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Wed, 29 Nov 2017 12:06:12 -0700 Subject: [PATCH] Handle receipt of SIGTTIN/SIGTTOU when reading/writing from/to the tty. We can't use a signal event for these since that would restart the system call after the signal was handled and the callback would not get a chance to run. Fixes running a command in the background that write to the tty when the TOSTOP terminal flag is set. --- src/exec_pty.c | 93 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/src/exec_pty.c b/src/exec_pty.c index 65caf2f6a..26821aafb 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -110,6 +110,7 @@ static int safe_close(int fd); static void ev_free_by_fd(struct sudo_event_base *evbase, int fd); static void check_foreground(pid_t ppgrp); static void add_io_events(struct sudo_event_base *evbase); +static void schedule_signal(struct exec_closure_pty *ec, int signo); /* * Cleanup hook for sudo_fatal()/sudo_fatalx() @@ -517,6 +518,17 @@ suspend_sudo(int signo, pid_t ppgrp) debug_return_int(ret); } +/* + * SIGTTIN signal handler for read_callback that just sets a flag. + */ +static volatile sig_atomic_t got_sigttin; + +static void +sigttin(int signo) +{ + got_sigttin = 1; +} + /* * Read an iobuf that is ready. */ @@ -524,17 +536,36 @@ static void read_callback(int fd, int what, void *v) { struct io_buffer *iob = v; - struct sudo_event_base *evbase; - int n; + struct sudo_event_base *evbase = sudo_ev_get_base(iob->revent); + struct sigaction sa, osa; + int saved_errno; + ssize_t n; debug_decl(read_callback, SUDO_DEBUG_EXEC); - evbase = sudo_ev_get_base(iob->revent); - do { - n = read(fd, iob->buf + iob->len, sizeof(iob->buf) - iob->len); - } while (n == -1 && errno == EINTR); + /* + * We ignore SIGTTIN by default but we need to handle it when reading + * from the terminal. A signal event won't work here because the + * read() would be restarted, preventing the callback from running. + */ + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = sigttin; + got_sigttin = 0; + sigaction(SIGTTIN, &sa, &osa); + n = read(fd, iob->buf + iob->len, sizeof(iob->buf) - iob->len); + saved_errno = errno; + sigaction(SIGTTIN, &osa, NULL); + errno = saved_errno; + switch (n) { case -1: - if (errno == EAGAIN) + if (got_sigttin) { + /* Schedule SIGTTIN to be forwared to the command. */ + schedule_signal(iob->ec, SIGTTIN); + /* Restart event loop to service signal immediately. */ + sudo_ev_loopcontinue(evbase); + } + if (errno == EAGAIN || errno == EINTR) break; /* treat read error as fatal and close the fd */ sudo_debug_printf(SUDO_DEBUG_ERROR, @@ -557,7 +588,7 @@ read_callback(int fd, int what, void *v) break; default: sudo_debug_printf(SUDO_DEBUG_INFO, - "read %d bytes from fd %d", n, fd); + "read %zd bytes from fd %d", n, fd); if (!iob->action(iob->buf + iob->len, n, iob)) { terminate_command(iob->ec->cmnd_pid, true); iob->ec->cmnd_pid = -1; @@ -577,6 +608,17 @@ read_callback(int fd, int what, void *v) } } +/* + * SIGTTOU signal handler for write_callback that just sets a flag. + */ +static volatile sig_atomic_t got_sigttou; + +static void +sigttou(int signo) +{ + got_sigttou = 1; +} + /* * Write an iobuf that is ready. */ @@ -584,14 +626,27 @@ static void write_callback(int fd, int what, void *v) { struct io_buffer *iob = v; - struct sudo_event_base *evbase; - int n; + struct sudo_event_base *evbase = sudo_ev_get_base(iob->wevent); + struct sigaction sa, osa; + int saved_errno; + ssize_t n; debug_decl(write_callback, SUDO_DEBUG_EXEC); - evbase = sudo_ev_get_base(iob->wevent); - do { - n = write(fd, iob->buf + iob->off, iob->len - iob->off); - } while (n == -1 && errno == EINTR); + /* + * We ignore SIGTTOU by default but we need to handle it when writing + * to the terminal. A signal event won't work here because the + * write() would be restarted, preventing the callback from running. + */ + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = sigttou; + got_sigttou = 0; + sigaction(SIGTTOU, &sa, &osa); + n = write(fd, iob->buf + iob->off, iob->len - iob->off); + saved_errno = errno; + sigaction(SIGTTOU, &osa, NULL); + errno = saved_errno; + if (n == -1) { switch (errno) { case EPIPE: @@ -610,6 +665,14 @@ write_callback(int fd, int what, void *v) safe_close(fd); ev_free_by_fd(evbase, fd); break; + case EINTR: + if (got_sigttou) { + /* Schedule SIGTTOU to be forwared to the command. */ + schedule_signal(iob->ec, SIGTTOU); + /* Restart event loop to service signal immediately. */ + sudo_ev_loopcontinue(evbase); + } + /* FALLTHROUGH */ case EAGAIN: /* not an error */ break; @@ -624,7 +687,7 @@ write_callback(int fd, int what, void *v) } } else { sudo_debug_printf(SUDO_DEBUG_INFO, - "wrote %d bytes to fd %d", n, fd); + "wrote %zd bytes to fd %d", n, fd); iob->off += n; /* Reset buffer if fully consumed. */ if (iob->off == iob->len) { -- 2.40.0