summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd C. Miller <Todd.Miller@sudo.ws>2023-05-11 18:20:50 -0600
committerTodd C. Miller <Todd.Miller@sudo.ws>2023-05-11 18:20:50 -0600
commit7c63b65be3550c6767da1d708e01fea6f9de9cb9 (patch)
treed625c940871fab16ff442673f91740024874c7b8
parentcddb91108f493a22170e85a25efcdd64949bf6dd (diff)
downloadsudo-7c63b65be3550c6767da1d708e01fea6f9de9cb9.tar.gz
run_command: run editor in foreground if visudo is the foreground process
The command is now always run in its own process group. If visudo is run in the foreground, the command is run in the foreground too. Otherwise, run the command in the background. There is a race between the tcsetpgrp() call in the parent and the execve() in the child. If we lose the race and the command needs the controlling terminal, it will be stopped with SIGTTOU or SIGTTIN, which the waitpid() loop will handle.
-rw-r--r--plugins/sudoers/visudo.c144
1 files changed, 83 insertions, 61 deletions
diff --git a/plugins/sudoers/visudo.c b/plugins/sudoers/visudo.c
index bc9b0645a..2f6289e28 100644
--- a/plugins/sudoers/visudo.c
+++ b/plugins/sudoers/visudo.c
@@ -94,7 +94,7 @@ static bool install_sudoers(struct sudoersfile *, bool, bool);
static bool visudo_track_error(const char *file, int line, int column, const char *fmt, va_list args) sudo_printf0like(4, 0);
static int print_unused(struct sudoers_parse_tree *, struct alias *, void *);
static bool reparse_sudoers(char *, int, char **, bool, bool);
-static int run_command(const char *, char *const *, bool);
+static int run_command(const char *, char *const *);
static void parse_sudoers_options(void);
static void setup_signals(void);
static void visudo_cleanup(void);
@@ -560,7 +560,7 @@ edit_sudoers(struct sudoersfile *sp, char *editor, int editor_argc,
goto done;
}
- if (run_command(editor, editor_argv, true) != -1) {
+ if (run_command(editor, editor_argv) != -1) {
if (sudo_gettime_real(&times[1]) == -1) {
sudo_warn("%s", U_("unable to read the clock"));
goto done;
@@ -823,7 +823,7 @@ install_sudoers(struct sudoersfile *sp, bool set_owner, bool set_mode)
av[3] = NULL;
/* And run it... */
- if (run_command(_PATH_MV, av, false) != 0) {
+ if (run_command(_PATH_MV, av) != 0) {
sudo_warnx(U_("command failed: '%s %s %s', %s unchanged"),
_PATH_MV, sp->tpath, sp->dpath, sp->opath);
goto done;
@@ -899,53 +899,37 @@ setup_signals(void)
}
static int
-run_command(const char *path, char *const *argv, bool foreground)
+run_command(const char *path, char *const *argv)
{
+ pid_t pid, visudo_pgrp = getpgrp();
int status, ttyfd = -1;
- pid_t pid, saved_pgrp = -1;
int rv = -1;
debug_decl(run_command, SUDOERS_DEBUG_UTIL);
- if (foreground) {
- /* Save original foreground process group id. */
- ttyfd = open(_PATH_TTY, O_RDWR);
- if (ttyfd != -1) {
- saved_pgrp = tcgetpgrp(ttyfd);
- if (saved_pgrp == -1) {
- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
- "%s: unable to get foreground pgrp", __func__);
- close(ttyfd);
- ttyfd = -1;
- }
- }
- if (ttyfd == -1)
- foreground = false;
- }
+ /* We may need access to /dev/tty to set the foreground process. */
+ ttyfd = open(_PATH_TTY, O_RDWR);
+ (void)fcntl(ttyfd, F_SETFD, FD_CLOEXEC);
switch (pid = sudo_debug_fork()) {
case -1:
sudo_fatal(U_("unable to execute %s"), path);
break; /* NOTREACHED */
case 0:
- /* Run command as the foreground process of a new process group. */
- if (foreground) {
- pid = getpid();
- if (setpgid(0, pid) == -1) {
- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
- "%s: unable to set pgrp to %d (editor)",
- __func__, (int)pid);
- } else {
- /* Wait for parent to grant us the controlling tty. */
- struct timespec ts = { 0, 1000 }; /* 1us */
- sudo_debug_printf(SUDO_DEBUG_DEBUG,
- "%s: waiting for controlling tty", __func__);
- while (tcgetpgrp(ttyfd) != pid)
- nanosleep(&ts, NULL);
- sudo_debug_printf(SUDO_DEBUG_DEBUG,
- "%s: got controlling tty", __func__);
- }
- close(ttyfd);
+ /*
+ * Run command in its own process group. If visudo is run
+ * in the foreground, it will make the editor the foreground
+ * process. There is a race between the parent's call to
+ * tcsetpgrp() and the child's execve(). If the parent loses
+ * the race, the child will be stopped with SIGTTOU or SIGTTIN
+ * and be restarted immeditately.
+ */
+ if (setpgid(0, 0) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set pgrp to %d (editor)",
+ __func__, (int)getpid());
}
+ sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: executing %s",
+ __func__, path);
closefrom(STDERR_FILENO + 1);
execv(path, argv);
sudo_warn(U_("unable to run %s"), path);
@@ -954,12 +938,17 @@ run_command(const char *path, char *const *argv, bool foreground)
}
/* Set child process group in both parent and child to avoid a race. */
- if (foreground) {
- if (setpgid(pid, pid) == -1) {
- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
- "%s: unable to set pgrp to %d (editor)",
- __func__, (int)pid);
- } else if (tcsetpgrp(ttyfd, pid) == -1) {
+ if (setpgid(pid, pid) == -1) {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set pgrp to %d (editor)",
+ __func__, (int)pid);
+ } else if (tcgetpgrp(ttyfd) == visudo_pgrp) {
+ /*
+ * This races with execve() in the child. If we lose the race,
+ * the child may be stopped by SIGTTOU or SIGTTIN when it tries
+ * to use the terminal. That is handled by the waitpid() loop.
+ */
+ if (tcsetpgrp(ttyfd, pid) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to set foreground pgrp to %d (editor)",
__func__, (int)pid);
@@ -985,10 +974,33 @@ run_command(const char *path, char *const *argv, bool foreground)
const int signo = WSTOPSIG(status);
sudo_debug_printf(SUDO_DEBUG_DIAG, "%s: %d: suspended by signal %d",
__func__, (int)pid, signo);
- if (foreground) {
- sudo_suspend_parent(signo, getpid(), getpgrp(), pid,
- NULL, NULL);
+ switch (signo) {
+ case SIGTTIN:
+ case SIGTTOU:
+ /*
+ * The editor stopped because it needs to be the foreground
+ * process. Try to make it the foreground process and continue
+ * (suspending visudo itself if running in the background).
+ */
+ if (ttyfd != -1) {
+ if (tcsetpgrp(ttyfd, pid) == 0) {
+ sudo_debug_printf(SUDO_DEBUG_DIAG, "%s: %d: continuing",
+ __func__, (int)pid);
+ killpg(pid, SIGCONT);
+ break;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to set foreground pgrp to %d (visudo)",
+ __func__, (int)visudo_pgrp);
+ }
+ }
+ FALLTHROUGH;
+ default:
+ killpg(visudo_pgrp, signo);
+ sudo_debug_printf(SUDO_DEBUG_DIAG, "%s: %d: continuing",
+ __func__, (int)pid);
killpg(pid, SIGCONT);
+ break;
}
} else {
sudo_debug_printf(SUDO_DEBUG_ERROR,
@@ -996,22 +1008,32 @@ run_command(const char *path, char *const *argv, bool foreground)
}
}
- if (foreground) {
- /* Restore the original foreground process group. */
- sigset_t mask, omask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGTTOU);
- sigprocmask(SIG_BLOCK, &mask, &omask);
- sudo_debug_printf(SUDO_DEBUG_DEBUG,
- "%s: restoring foreground pgrp to %d (visudo)",
- __func__, (int)saved_pgrp);
- if (tcsetpgrp(ttyfd, saved_pgrp) == -1) {
- sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
- "%s: unable to restore foreground pgrp to %d (visudo)",
- __func__, (int)saved_pgrp);
+ if (ttyfd != -1) {
+ pid = tcgetpgrp(ttyfd);
+ if (pid != -1 && pid != visudo_pgrp) {
+ /*
+ * If the foreground process does not exist it is usually because
+ * we made the editor the foreground process and it terminated.
+ * Change it back to visudo so we can prompt the user as needed.
+ */
+ if (kill(pid, 0) == -1 && errno == ESRCH) {
+ sigset_t mask, omask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGTTOU);
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+ sudo_debug_printf(SUDO_DEBUG_DEBUG,
+ "%s: changing foreground pgrp from %d to %d (visudo)",
+ __func__, (int)pid, (int)visudo_pgrp);
+ if (tcsetpgrp(ttyfd, visudo_pgrp) == -1) {
+ /* This should not fail since we block SIGTTOU. */
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: unable to restore foreground pgrp to %d (visudo)",
+ __func__, (int)visudo_pgrp);
+ }
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ }
}
close(ttyfd);
- sigprocmask(SIG_SETMASK, &omask, NULL);
}
debug_return_int(rv);