Catch SIGCONT and restore terminal settings on resume from SIGSTOP.
While we cannot catch SIGSTOP, we _can_ catch SIGCONT and set /dev/tty to raw mode when running in the foreground. Ignore SIGCONT in suspend_sudo_pty() so we don't call resume_terminal() twice.
This commit is contained in:
110
src/exec_pty.c
110
src/exec_pty.c
@@ -172,6 +172,43 @@ check_foreground(struct exec_closure *ec)
|
|||||||
debug_return_int(ret);
|
debug_return_int(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Restore the terminal when sudo is resumed in response to SIGCONT.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
resume_terminal(struct exec_closure *ec)
|
||||||
|
{
|
||||||
|
debug_decl(resume_terminal, SUDO_DEBUG_EXEC);
|
||||||
|
|
||||||
|
if (check_foreground(ec) == -1) {
|
||||||
|
/* User's tty was revoked. */
|
||||||
|
debug_return_bool(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We always resume the command in the foreground if sudo itself
|
||||||
|
* is the foreground process. This helps work around poorly behaved
|
||||||
|
* programs that catch SIGTTOU/SIGTTIN but suspend themselves with
|
||||||
|
* SIGSTOP. At worst, sudo will go into the background but upon
|
||||||
|
* resume the command will be runnable. Otherwise, we can get into
|
||||||
|
* a situation where the command will immediately suspend itself.
|
||||||
|
*/
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_INFO, "parent is in %s, ttymode %d -> %d",
|
||||||
|
foreground ? "foreground" : "background", ttymode,
|
||||||
|
foreground ? TERM_RAW : TERM_COOKED);
|
||||||
|
|
||||||
|
if (foreground) {
|
||||||
|
/* Foreground process, set tty to raw mode. */
|
||||||
|
if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
|
||||||
|
ttymode = TERM_RAW;
|
||||||
|
} else {
|
||||||
|
/* Background process, no access to tty. */
|
||||||
|
ttymode = TERM_COOKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_return_bool(true);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Suspend sudo if the underlying command is suspended.
|
* Suspend sudo if the underlying command is suspended.
|
||||||
* Returns SIGCONT_FG if the command should be resumed in the
|
* Returns SIGCONT_FG if the command should be resumed in the
|
||||||
@@ -181,10 +218,21 @@ static int
|
|||||||
suspend_sudo_pty(struct exec_closure *ec, int signo)
|
suspend_sudo_pty(struct exec_closure *ec, int signo)
|
||||||
{
|
{
|
||||||
char signame[SIG2STR_MAX];
|
char signame[SIG2STR_MAX];
|
||||||
struct sigaction sa, osa;
|
struct sigaction sa, osa, saved_sigcont;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
debug_decl(suspend_sudo_pty, SUDO_DEBUG_EXEC);
|
debug_decl(suspend_sudo_pty, SUDO_DEBUG_EXEC);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ignore SIGCONT when we suspend to avoid calling resume_terminal()
|
||||||
|
* multiple times.
|
||||||
|
*/
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = SA_RESTART;
|
||||||
|
sa.sa_handler = SIG_IGN;
|
||||||
|
if (sudo_sigaction(signo, &sa, &saved_sigcont) != 0)
|
||||||
|
sudo_warn(U_("unable to set handler for SIGCONT"));
|
||||||
|
|
||||||
switch (signo) {
|
switch (signo) {
|
||||||
case SIGTTOU:
|
case SIGTTOU:
|
||||||
case SIGTTIN:
|
case SIGTTIN:
|
||||||
@@ -209,6 +257,7 @@ suspend_sudo_pty(struct exec_closure *ec, int signo)
|
|||||||
FALLTHROUGH;
|
FALLTHROUGH;
|
||||||
case SIGSTOP:
|
case SIGSTOP:
|
||||||
case SIGTSTP:
|
case SIGTSTP:
|
||||||
|
default:
|
||||||
/* Flush any remaining output and deschedule I/O events. */
|
/* Flush any remaining output and deschedule I/O events. */
|
||||||
del_io_events(true);
|
del_io_events(true);
|
||||||
|
|
||||||
@@ -226,13 +275,11 @@ suspend_sudo_pty(struct exec_closure *ec, int signo)
|
|||||||
|
|
||||||
/* Suspend self and continue command when we resume. */
|
/* Suspend self and continue command when we resume. */
|
||||||
if (signo != SIGSTOP) {
|
if (signo != SIGSTOP) {
|
||||||
memset(&sa, 0, sizeof(sa));
|
|
||||||
sigemptyset(&sa.sa_mask);
|
|
||||||
sa.sa_flags = SA_RESTART;
|
|
||||||
sa.sa_handler = SIG_DFL;
|
sa.sa_handler = SIG_DFL;
|
||||||
if (sudo_sigaction(signo, &sa, &osa) != 0)
|
if (sudo_sigaction(signo, &sa, &osa) != 0)
|
||||||
sudo_warn(U_("unable to set handler for SIG%s"), signame);
|
sudo_warn(U_("unable to set handler for SIG%s"), signame);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We stop sudo's process group, even if sudo is not the process
|
* We stop sudo's process group, even if sudo is not the process
|
||||||
* group leader. If we only send the signal to sudo itself,
|
* group leader. If we only send the signal to sudo itself,
|
||||||
@@ -250,37 +297,6 @@ suspend_sudo_pty(struct exec_closure *ec, int signo)
|
|||||||
"no parent to suspend, terminating command.");
|
"no parent to suspend, terminating command.");
|
||||||
terminate_command(ec->cmnd_pid, true);
|
terminate_command(ec->cmnd_pid, true);
|
||||||
ec->cmnd_pid = -1;
|
ec->cmnd_pid = -1;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Log the resume event. */
|
|
||||||
log_suspend(ec, SIGCONT);
|
|
||||||
|
|
||||||
/* Check foreground/background status on resume. */
|
|
||||||
if (check_foreground(ec) == -1) {
|
|
||||||
/* User's tty was revoked. */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We always resume the command in the foreground if sudo itself
|
|
||||||
* is the foreground process. This helps work around poorly behaved
|
|
||||||
* programs that catch SIGTTOU/SIGTTIN but suspend themselves with
|
|
||||||
* SIGSTOP. At worst, sudo will go into the background but upon
|
|
||||||
* resume the command will be runnable. Otherwise, we can get into
|
|
||||||
* a situation where the command will immediately suspend itself.
|
|
||||||
*/
|
|
||||||
sudo_debug_printf(SUDO_DEBUG_INFO, "parent is in %s, ttymode %d -> %d",
|
|
||||||
foreground ? "foreground" : "background", ttymode,
|
|
||||||
foreground ? TERM_RAW : TERM_COOKED);
|
|
||||||
|
|
||||||
if (foreground) {
|
|
||||||
/* Foreground process, set tty to raw mode. */
|
|
||||||
if (sudo_term_raw(io_fds[SFD_USERTTY], 0))
|
|
||||||
ttymode = TERM_RAW;
|
|
||||||
} else {
|
|
||||||
/* Background process, no access to tty. */
|
|
||||||
ttymode = TERM_COOKED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signo != SIGSTOP) {
|
if (signo != SIGSTOP) {
|
||||||
@@ -288,10 +304,24 @@ suspend_sudo_pty(struct exec_closure *ec, int signo)
|
|||||||
sudo_warn(U_("unable to restore handler for SIG%s"), signame);
|
sudo_warn(U_("unable to restore handler for SIG%s"), signame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If we failed to suspend, the command is no longer running. */
|
||||||
|
if (ec->cmnd_pid == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Log the resume event. */
|
||||||
|
log_suspend(ec, SIGCONT);
|
||||||
|
|
||||||
|
/* Restore the user's terminal settings. */
|
||||||
|
if (!resume_terminal(ec))
|
||||||
|
break;
|
||||||
|
|
||||||
ret = ttymode == TERM_RAW ? SIGCONT_FG : SIGCONT_BG;
|
ret = ttymode == TERM_RAW ? SIGCONT_FG : SIGCONT_BG;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sudo_sigaction(signo, &saved_sigcont, NULL) != 0)
|
||||||
|
sudo_warn(U_("unable to restore handler for SIGCONT"));
|
||||||
|
|
||||||
debug_return_int(ret);
|
debug_return_int(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,6 +829,9 @@ signal_cb_pty(int signo, int what, void *v)
|
|||||||
case SIGCHLD:
|
case SIGCHLD:
|
||||||
handle_sigchld_pty(ec);
|
handle_sigchld_pty(ec);
|
||||||
break;
|
break;
|
||||||
|
case SIGCONT:
|
||||||
|
resume_terminal(ec);
|
||||||
|
break;
|
||||||
case SIGWINCH:
|
case SIGWINCH:
|
||||||
sync_ttysize(ec);
|
sync_ttysize(ec);
|
||||||
break;
|
break;
|
||||||
@@ -986,6 +1019,13 @@ fill_exec_closure(struct exec_closure *ec, struct command_status *cstat,
|
|||||||
if (sudo_ev_add(ec->evbase, ec->sigchld_event, NULL, false) == -1)
|
if (sudo_ev_add(ec->evbase, ec->sigchld_event, NULL, false) == -1)
|
||||||
sudo_fatal("%s", U_("unable to add event to queue"));
|
sudo_fatal("%s", U_("unable to add event to queue"));
|
||||||
|
|
||||||
|
ec->sigcont_event = sudo_ev_alloc(SIGCONT,
|
||||||
|
SUDO_EV_SIGINFO, signal_cb_pty, ec);
|
||||||
|
if (ec->sigcont_event == NULL)
|
||||||
|
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
||||||
|
if (sudo_ev_add(ec->evbase, ec->sigcont_event, NULL, false) == -1)
|
||||||
|
sudo_fatal("%s", U_("unable to add event to queue"));
|
||||||
|
|
||||||
ec->sigwinch_event = sudo_ev_alloc(SIGWINCH,
|
ec->sigwinch_event = sudo_ev_alloc(SIGWINCH,
|
||||||
SUDO_EV_SIGINFO, signal_cb_pty, ec);
|
SUDO_EV_SIGINFO, signal_cb_pty, ec);
|
||||||
if (ec->sigwinch_event == NULL)
|
if (ec->sigwinch_event == NULL)
|
||||||
|
Reference in New Issue
Block a user