If event loop fails due to ENXIO, remove /dev/tty events and recover.

This fixes an issue on Solaris 11.4 (and probably others) with "sudo
reboot" when I/O logging is enabled.  Previously, sudo would kill
the command if it was still running after the event loop terminated,
leaving the system in a half-dead state.
This commit is contained in:
Todd C. Miller
2020-06-02 08:59:38 -06:00
parent 592eb7ab49
commit a380709215

View File

@@ -1,7 +1,7 @@
/* /*
* SPDX-License-Identifier: ISC * SPDX-License-Identifier: ISC
* *
* Copyright (c) 2009-2019 Todd C. Miller <Todd.Miller@sudo.ws> * Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@@ -1347,6 +1347,7 @@ exec_pty(struct command_details *details, struct command_status *cstat)
bool interpose[3] = { false, false, false }; bool interpose[3] = { false, false, false };
struct exec_closure_pty ec = { 0 }; struct exec_closure_pty ec = { 0 };
struct plugin_container *plugin; struct plugin_container *plugin;
int evloop_retries = -1;
sigset_t set, oset; sigset_t set, oset;
struct sigaction sa; struct sigaction sa;
struct stat sb; struct stat sb;
@@ -1623,23 +1624,38 @@ exec_pty(struct command_details *details, struct command_status *cstat)
/* /*
* In the event loop we pass input from user tty to master * In the event loop we pass input from user tty to master
* and pass output from master to stdout and IO plugin. * and pass output from master to stdout and IO plugin.
* Try to recover on ENXIO, it means the tty was revoked.
*/ */
add_io_events(ec.evbase); add_io_events(ec.evbase);
if (sudo_ev_dispatch(ec.evbase) == -1) do {
sudo_warn(U_("error in event loop")); if (sudo_ev_dispatch(ec.evbase) == -1)
if (sudo_ev_got_break(ec.evbase)) { sudo_warn(U_("error in event loop"));
/* error from callback or monitor died */ if (sudo_ev_got_break(ec.evbase)) {
sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely"); /* error from callback or monitor died */
/* XXX: no good way to know if we should terminate the command. */ sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
if (cstat->val == CMD_INVALID && ec.cmnd_pid != -1) { /* XXX: no good way to know if we should terminate the command. */
/* no status message, kill command */ if (cstat->val == CMD_INVALID && ec.cmnd_pid != -1) {
terminate_command(ec.cmnd_pid, true); /* no status message, kill command */
ec.cmnd_pid = -1; terminate_command(ec.cmnd_pid, true);
/* TODO: need way to pass an error to the sudo front end */ ec.cmnd_pid = -1;
cstat->type = CMD_WSTATUS; /* TODO: need way to pass an error to the sudo front end */
cstat->val = W_EXITCODE(1, SIGKILL); cstat->type = CMD_WSTATUS;
cstat->val = W_EXITCODE(1, SIGKILL);
}
} else if (!sudo_ev_got_exit(ec.evbase)) {
switch (errno) {
case ENXIO:
case EIO:
case EBADF:
/* /dev/tty was revoked, remove tty events and retry (once) */
if (evloop_retries == -1 && io_fds[SFD_USERTTY] != -1) {
ev_free_by_fd(ec.evbase, io_fds[SFD_USERTTY]);
evloop_retries = 1;
}
break;
}
} }
} } while (evloop_retries-- > 0);
/* Flush any remaining output, free I/O bufs and events, do logout. */ /* Flush any remaining output, free I/O bufs and events, do logout. */
pty_finish(cstat); pty_finish(cstat);