diff --git a/lib/util/term.c b/lib/util/term.c index 41e23d7f1..657fb6605 100644 --- a/lib/util/term.c +++ b/lib/util/term.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #ifdef HAVE_STRING_H @@ -255,6 +256,7 @@ bool sudo_term_copy_v1(int src, int dst) { struct termios tt_src, tt_dst; + struct winsize wsize; speed_t speed; int i; debug_decl(sudo_term_copy, SUDO_DEBUG_UTIL) @@ -285,7 +287,11 @@ sudo_term_copy_v1(int src, int dst) speed = cfgetispeed(&tt_src); cfsetispeed(&tt_dst, speed); - if (tcsetattr_nobg(dst, TCSASOFT|TCSAFLUSH, &tt_dst) == 0) - debug_return_bool(true); - debug_return_bool(false); + if (tcsetattr_nobg(dst, TCSASOFT|TCSAFLUSH, &tt_dst) == -1) + debug_return_bool(false); + + if (ioctl(src, TIOCGWINSZ, &wsize) == 0) + (void)ioctl(dst, TIOCSWINSZ, &wsize); + + debug_return_bool(true); } diff --git a/src/exec_monitor.c b/src/exec_monitor.c index 1dc1d370d..4b4a7f92e 100644 --- a/src/exec_monitor.c +++ b/src/exec_monitor.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 Todd C. Miller + * Copyright (c) 2009-2018 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 @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include "sudo.h" #include "sudo_event.h" @@ -58,6 +60,8 @@ struct monitor_closure { struct sudo_event *sigchld_event; }; +static bool tty_initialized; + /* * Deliver a signal to the running command. * The signal was either forwarded to us by the parent sudo process @@ -97,6 +101,11 @@ deliver_signal(struct monitor_closure *mc, int signo, bool from_parent) "%s: unable to set foreground pgrp to %d (command)", __func__, (int)mc->cmnd_pgrp); } + /* Lazily initialize the pty if needed. */ + if (!tty_initialized) { + if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) + tty_initialized = true; + } killpg(mc->cmnd_pid, SIGCONT); break; case SIGCONT_BG: @@ -119,6 +128,34 @@ deliver_signal(struct monitor_closure *mc, int signo, bool from_parent) debug_return; } +/* + * Unpack rows and cols from a CMD_TTYWINCH value, set the new window + * size on the pty slave and inform the command of the change. + */ +static void +handle_winch(struct monitor_closure *mc, unsigned int wsize_packed) +{ + struct winsize wsize, owsize; + debug_decl(handle_winch, SUDO_DEBUG_EXEC); + + /* Rows and colums are stored as two shorts packed into a single int. */ + wsize.ws_row = wsize_packed & 0xffff; + wsize.ws_col = (wsize_packed >> 16) & 0xffff; + + if (ioctl(io_fds[SFD_SLAVE], TIOCGWINSZ, &owsize) == 0 && + (wsize.ws_row != owsize.ws_row || wsize.ws_col != owsize.ws_col)) { + + sudo_debug_printf(SUDO_DEBUG_INFO, + "window size change %dx%d -> %dx%d", + owsize.ws_col, owsize.ws_row, wsize.ws_col, wsize.ws_row); + + (void)ioctl(io_fds[SFD_SLAVE], TIOCSWINSZ, &wsize); + deliver_signal(mc, SIGWINCH, true); + } + + debug_return; +} + /* * Send status to parent over socketpair. * Return value is the same as send(2). @@ -210,7 +247,7 @@ mon_handle_sigchld(struct monitor_closure *mc) send_status(mc->backchannel, mc->cstat); } } - } else { + } else { sudo_debug_printf(SUDO_DEBUG_WARN, "%s: not overwriting command status %d,%d with %d,%d", __func__, mc->cstat->type, mc->cstat->val, CMD_WSTATUS, status); @@ -328,10 +365,16 @@ mon_backchannel_cb(int fd, int what, void *v) mc->cstat->val = n ? EIO : ECONNRESET; sudo_ev_loopbreak(mc->evbase); } else { - if (cstmp.type == CMD_SIGNO) { + switch (cstmp.type) { + case CMD_TTYWINCH: + handle_winch(mc, cstmp.val); + break; + case CMD_SIGNO: deliver_signal(mc, cstmp.val, true); - } else { + break; + default: sudo_warnx(U_("unexpected reply type on backchannel: %d"), cstmp.type); + break; } } debug_return; @@ -414,7 +457,7 @@ fill_exec_closure_monitor(struct monitor_closure *mc, /* Setup event base and events. */ mc->evbase = sudo_ev_base_alloc(); if (mc->evbase == NULL) - sudo_fatal(NULL); + sudo_fatal(NULL); /* Event for command status via errfd. */ mc->errpipe_event = sudo_ev_alloc(errfd, @@ -513,11 +556,9 @@ exec_monitor(struct command_details *details, sigset_t *oset, int errpipe[2]; debug_decl(exec_monitor, SUDO_DEBUG_EXEC); - /* Close unused fds. */ + /* The pty master is not used by the monitor. */ if (io_fds[SFD_MASTER] != -1) close(io_fds[SFD_MASTER]); - if (io_fds[SFD_USERTTY] != -1) - close(io_fds[SFD_USERTTY]); /* Ignore any SIGTTIN or SIGTTOU we receive (shouldn't be possible). */ memset(&sa, 0, sizeof(sa)); @@ -529,6 +570,10 @@ exec_monitor(struct command_details *details, sigset_t *oset, if (sudo_sigaction(SIGTTOU, &sa, NULL) != 0) sudo_warn(U_("unable to set handler for signal %d"), SIGTTOU); + /* If we are starting in the foreground, the pty was already initialized. */ + if (foreground) + tty_initialized = true; + /* * Start a new session with the parent as the session leader * and the slave pty as the controlling terminal. @@ -559,6 +604,8 @@ exec_monitor(struct command_details *details, sigset_t *oset, sigprocmask(SIG_SETMASK, oset, NULL); close(backchannel); close(errpipe[0]); + if (io_fds[SFD_USERTTY] != -1) + close(io_fds[SFD_USERTTY]); restore_signals(); /* setup tty and exec command */ diff --git a/src/exec_pty.c b/src/exec_pty.c index 814b0e497..d3f96edaf 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -47,12 +47,12 @@ #define TERM_COOKED 0 #define TERM_RAW 1 -/* We keep a tailq of signals to forward to the monitor. */ -struct sigforward { - TAILQ_ENTRY(sigforward) entries; - int signo; +/* Tail queue of messages to send to the monitor. */ +struct monitor_message { + TAILQ_ENTRY(monitor_message) entries; + struct command_status cstat; }; -TAILQ_HEAD(sigfwd_list, sigforward); +TAILQ_HEAD(monitor_message_list, monitor_message); struct exec_closure_pty { pid_t monitor_pid; @@ -62,6 +62,7 @@ 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; @@ -72,8 +73,7 @@ struct exec_closure_pty { struct sudo_event *sigusr2_event; struct sudo_event *sigchld_event; struct sudo_event *sigwinch_event; - struct sudo_event *sigfwd_event; - struct sigfwd_list sigfwd_list; + struct monitor_message_list monitor_messages; }; /* @@ -98,17 +98,16 @@ SLIST_HEAD(io_buffer_list, io_buffer); static char slavename[PATH_MAX]; int io_fds[6] = { -1, -1, -1, -1, -1, -1}; static bool foreground, pipeline; -static bool tty_initialized; 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(int src, int dst); +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 void check_foreground(pid_t ppgrp); +static void 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); @@ -161,10 +160,14 @@ pty_setup(uid_t uid, const char *tty) debug_return_bool(true); } +/* + * Make the tty slave the controlling tty. + * This is only used by the monitor but slavename[] is static. + */ int pty_make_controlling(void) { - if (io_fds[SFD_USERTTY] != -1) { + if (io_fds[SFD_SLAVE] != -1) { #ifdef TIOCSCTTY if (ioctl(io_fds[SFD_SLAVE], TIOCSCTTY, NULL) != 0) return -1; @@ -410,21 +413,15 @@ log_winchange(unsigned int rows, unsigned int cols) * the pty slave as needed. */ static void -check_foreground(pid_t ppgrp) +check_foreground(struct exec_closure_pty *ec) { debug_decl(check_foreground, SUDO_DEBUG_EXEC); if (io_fds[SFD_USERTTY] != -1) { - /* Always check for window size changes. */ - sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]); + foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ec->ppgrp; - foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp; - if (foreground) { - if (!tty_initialized) { - if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) - tty_initialized = true; - } - } + /* Also check for window size changes. */ + sync_ttysize(ec); } debug_return; @@ -436,7 +433,7 @@ check_foreground(pid_t ppgrp) * foreground or SIGCONT_BG if it is a background process. */ static int -suspend_sudo(int signo, pid_t ppgrp) +suspend_sudo(struct exec_closure_pty *ec, int signo) { char signame[SIG2STR_MAX]; struct sigaction sa, osa; @@ -451,7 +448,7 @@ suspend_sudo(int signo, pid_t ppgrp) * in the foreground. If not, we'll suspend sudo and resume later. */ if (!foreground) - check_foreground(ppgrp); + check_foreground(ec); if (foreground) { if (ttymode != TERM_RAW) { if (sudo_term_raw(io_fds[SFD_USERTTY], 0)) @@ -483,11 +480,11 @@ suspend_sudo(int signo, pid_t ppgrp) sudo_warn(U_("unable to set handler for signal %d"), signo); } sudo_debug_printf(SUDO_DEBUG_INFO, "kill parent SIG%s", signame); - if (killpg(ppgrp, signo) != 0) - sudo_warn("killpg(%d, SIG%s)", (int)ppgrp, signame); + if (killpg(ec->ppgrp, signo) != 0) + sudo_warn("killpg(%d, SIG%s)", (int)ec->ppgrp, signame); /* Check foreground/background status on resume. */ - check_foreground(ppgrp); + check_foreground(ec); /* * We always resume the command in the foreground if sudo itself @@ -804,13 +801,36 @@ pty_close(struct command_status *cstat) debug_return; } +/* + * 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) +{ + struct monitor_message *msg; + debug_decl(send_command, SUDO_DEBUG_EXEC) + + if ((msg = calloc(1, sizeof(*msg))) == NULL) + 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); + + if (sudo_ev_add(ec->evbase, ec->fwdchannel_event, NULL, true) == -1) + sudo_fatal(U_("unable to add event to queue")); + + /* Restart event loop to send the command immediately. */ + sudo_ev_loopcontinue(ec->evbase); + + debug_return; +} + /* * Schedule a signal to be forwarded. */ static void schedule_signal(struct exec_closure_pty *ec, int signo) { - struct sigforward *sigfwd; char signame[SIG2STR_MAX]; debug_decl(schedule_signal, SUDO_DEBUG_EXEC) @@ -822,16 +842,7 @@ schedule_signal(struct exec_closure_pty *ec, int signo) snprintf(signame, sizeof(signame), "%d", signo); sudo_debug_printf(SUDO_DEBUG_DIAG, "scheduled SIG%s for command", signame); - if ((sigfwd = calloc(1, sizeof(*sigfwd))) == NULL) - sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - sigfwd->signo = signo; - TAILQ_INSERT_TAIL(&ec->sigfwd_list, sigfwd, entries); - - if (sudo_ev_add(ec->evbase, ec->sigfwd_event, NULL, true) == -1) - sudo_fatal(U_("unable to add event to queue")); - - /* Restart event loop to service signal immediately. */ - sudo_ev_loopcontinue(ec->evbase); + send_command_status(ec, CMD_SIGNO, signo); debug_return; } @@ -894,7 +905,7 @@ backchannel_cb(int fd, int what, void *v) /* Suspend parent and tell monitor how to resume on return. */ sudo_debug_printf(SUDO_DEBUG_INFO, "command stopped, suspending parent"); - signo = suspend_sudo(WSTOPSIG(cstat.val), ec->ppgrp); + signo = suspend_sudo(ec, WSTOPSIG(cstat.val)); schedule_signal(ec, signo); /* Re-enable I/O events */ add_io_events(ec->evbase); @@ -962,7 +973,7 @@ handle_sigchld_pty(struct exec_closure_pty *ec) if (WIFSTOPPED(status)) { sudo_debug_printf(SUDO_DEBUG_INFO, "monitor stopped, suspending sudo"); - n = suspend_sudo(WSTOPSIG(status), ec->ppgrp); + n = suspend_sudo(ec, WSTOPSIG(status)); kill(pid, SIGCONT); schedule_signal(ec, n); /* Re-enable I/O events */ @@ -1005,7 +1016,7 @@ signal_cb_pty(int signo, int what, void *v) handle_sigchld_pty(ec); break; case SIGWINCH: - sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]); + sync_ttysize(ec); break; default: /* @@ -1032,41 +1043,52 @@ signal_cb_pty(int signo, int what, void *v) } /* - * Forward signals in sigfwd_list to the monitor so it can + * Forward signals in monitor_messages to the monitor so it can * deliver them to the command. */ static void -sigfwd_cb(int sock, int what, void *v) +fwdchannel_cb(int sock, int what, void *v) { struct exec_closure_pty *ec = v; char signame[SIG2STR_MAX]; - struct sigforward *sigfwd; - struct command_status cstat; + struct monitor_message *msg; ssize_t nsent; - debug_decl(sigfwd_cb, SUDO_DEBUG_EXEC) + debug_decl(fwdchannel_cb, SUDO_DEBUG_EXEC) - while ((sigfwd = TAILQ_FIRST(&ec->sigfwd_list)) != NULL) { - if (sigfwd->signo == SIGCONT_FG) - strlcpy(signame, "CONT_FG", sizeof(signame)); - else if (sigfwd->signo == SIGCONT_BG) - strlcpy(signame, "CONT_BG", sizeof(signame)); - else if (sig2str(sigfwd->signo, signame) == -1) - snprintf(signame, sizeof(signame), "%d", sigfwd->signo); - sudo_debug_printf(SUDO_DEBUG_INFO, - "sending SIG%s to monitor over backchannel", signame); - cstat.type = CMD_SIGNO; - cstat.val = sigfwd->signo; - TAILQ_REMOVE(&ec->sigfwd_list, sigfwd, entries); - free(sigfwd); - nsent = send(sock, &cstat, sizeof(cstat), 0); - if (nsent != sizeof(cstat)) { + while ((msg = TAILQ_FIRST(&ec->monitor_messages)) != NULL) { + switch (msg->cstat.type) { + case CMD_SIGNO: + if (msg->cstat.val == SIGCONT_FG) + strlcpy(signame, "CONT_FG", sizeof(signame)); + else if (msg->cstat.val == SIGCONT_BG) + strlcpy(signame, "CONT_BG", sizeof(signame)); + else if (sig2str(msg->cstat.val, signame) == -1) + snprintf(signame, sizeof(signame), "%d", msg->cstat.val); + sudo_debug_printf(SUDO_DEBUG_INFO, + "sending SIG%s to monitor over backchannel", signame); + break; + case CMD_TTYWINCH: + sudo_debug_printf(SUDO_DEBUG_INFO, "sending window size change " + "to monitor over backchannelL %d x %d", + msg->cstat.val & 0xffff, (msg->cstat.val >> 16) & 0xffff); + break; + default: + sudo_debug_printf(SUDO_DEBUG_INFO, + "sending cstat type %d, value %d to monitor over backchannel", + msg->cstat.type, msg->cstat.val); + break; + } + TAILQ_REMOVE(&ec->monitor_messages, msg, entries); + nsent = send(sock, &msg->cstat, sizeof(msg->cstat), 0); + if (nsent != sizeof(msg->cstat)) { if (errno == EPIPE) { sudo_debug_printf(SUDO_DEBUG_ERROR, "broken pipe writing to monitor over backchannel"); - /* Other end of socket gone, empty out sigfwd_list. */ - while ((sigfwd = TAILQ_FIRST(&ec->sigfwd_list)) != NULL) { - TAILQ_REMOVE(&ec->sigfwd_list, sigfwd, entries); - free(sigfwd); + /* 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); } /* XXX - need new CMD_ type for monitor errors. */ ec->cstat->type = CMD_ERRNO; @@ -1075,6 +1097,7 @@ sigfwd_cb(int sock, int what, void *v) } break; } + free(msg); } } @@ -1094,7 +1117,7 @@ fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat, ec->ppgrp = ppgrp; ec->cstat = cstat; ec->details = details; - TAILQ_INIT(&ec->sigfwd_list); + TAILQ_INIT(&ec->monitor_messages); /* Setup event base and events. */ ec->evbase = sudo_ev_base_alloc(); @@ -1182,9 +1205,9 @@ fill_exec_closure_pty(struct exec_closure_pty *ec, struct command_status *cstat, sudo_fatal(U_("unable to add event to queue")); /* The signal forwarding event gets added on demand. */ - ec->sigfwd_event = sudo_ev_alloc(backchannel, - SUDO_EV_WRITE, sigfwd_cb, ec); - if (ec->sigfwd_event == NULL) + ec->fwdchannel_event = sudo_ev_alloc(backchannel, + SUDO_EV_WRITE, fwdchannel_cb, ec); + if (ec->fwdchannel_event == NULL) sudo_fatal(NULL); /* Set the default event base. */ @@ -1203,6 +1226,7 @@ free_exec_closure_pty(struct exec_closure_pty *ec) 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); @@ -1213,7 +1237,6 @@ free_exec_closure_pty(struct exec_closure_pty *ec) sudo_ev_free(ec->sigusr2_event); sudo_ev_free(ec->sigchld_event); sudo_ev_free(ec->sigwinch_event); - sudo_ev_free(ec->sigfwd_event); debug_return; } @@ -1229,7 +1252,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 }; - struct sigforward *sigfwd, *sigfwd_next; + struct monitor_message *msg; struct exec_closure_pty ec = { 0 }; struct plugin_container *plugin; sigset_t set, oset; @@ -1393,13 +1416,12 @@ exec_pty(struct command_details *details, struct command_status *cstat) } } - /* Set pty window size. */ - sync_ttysize(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE]); - if (foreground) { /* Copy terminal attrs from user tty -> pty slave. */ - if (sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) - tty_initialized = true; + if (!sudo_term_copy(io_fds[SFD_USERTTY], io_fds[SFD_SLAVE])) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: unable to copy terminal settings to pty", __func__); + } /* Start out in raw mode unless part of a pipeline or backgrounded. */ if (!pipeline && !ISSET(details->flags, CD_EXEC_BG)) { @@ -1505,8 +1527,9 @@ exec_pty(struct command_details *details, struct command_status *cstat) /* Free things up. */ free_exec_closure_pty(&ec); - TAILQ_FOREACH_SAFE(sigfwd, &ec.sigfwd_list, entries, sigfwd_next) { - free(sigfwd); + while ((msg = TAILQ_FIRST(&ec.monitor_messages)) != NULL) { + TAILQ_REMOVE(&ec.monitor_messages, msg, entries); + free(msg); } debug_return_bool(true); } @@ -1651,31 +1674,30 @@ del_io_events(bool nonblocking) } /* - * Propagates tty size change signals to pty being used by the command - * and passes new window size to the I/O plugin. + * Check for tty size changes. + * Passes the new window size to the I/O plugin and to the monitor. */ static void -sync_ttysize(int src, int dst) +sync_ttysize(struct exec_closure_pty *ec) { - struct winsize wsize, owsize; - pid_t pgrp; + static struct winsize owsize; + struct winsize wsize; debug_decl(sync_ttysize, SUDO_DEBUG_EXEC); - if (ioctl(src, TIOCGWINSZ, &wsize) == 0 && - ioctl(dst, TIOCGWINSZ, &owsize) == 0 && - (wsize.ws_row != owsize.ws_row || wsize.ws_col != owsize.ws_col)) { + if (ioctl(io_fds[SFD_USERTTY], TIOCGWINSZ, &wsize) == 0) { + if (wsize.ws_row != owsize.ws_row || wsize.ws_col != owsize.ws_col) { + const unsigned int wsize_packed = (wsize.ws_row & 0xffff) | + ((wsize.ws_col & 0xffff) << 16); - sudo_debug_printf(SUDO_DEBUG_INFO, - "window size change %dx%d -> %dx%d", - owsize.ws_col, owsize.ws_row, wsize.ws_col, wsize.ws_row); - - (void)ioctl(dst, TIOCSWINSZ, &wsize); - if ((pgrp = tcgetpgrp(dst)) != -1) - killpg(pgrp, SIGWINCH); - - /* Only log window size changes, not the initial setting. */ - if (tty_initialized) + /* Log window change event. */ log_winchange(wsize.ws_row, wsize.ws_col); + + /* Send window change event to monitor process. */ + send_command_status(ec, CMD_TTYWINCH, wsize_packed); + + /* Update old value. */ + owsize = wsize; + } } debug_return; diff --git a/src/sudo.h b/src/sudo.h index a4a92f6bb..a2a4b5a5b 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -172,11 +172,12 @@ struct command_details { /* Status passed between parent and child via socketpair */ struct command_status { -#define CMD_INVALID 0 -#define CMD_ERRNO 1 -#define CMD_WSTATUS 2 -#define CMD_SIGNO 3 -#define CMD_PID 4 +#define CMD_INVALID 0 +#define CMD_ERRNO 1 +#define CMD_WSTATUS 2 +#define CMD_SIGNO 3 +#define CMD_PID 4 +#define CMD_TTYWINCH 5 int type; int val; };