/*1* SPDX-License-Identifier: ISC2*3* Copyright (c) 2009-2023 Todd C. Miller <[email protected]>4*5* Permission to use, copy, modify, and distribute this software for any6* purpose with or without fee is hereby granted, provided that the above7* copyright notice and this permission notice appear in all copies.8*9* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES10* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF11* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR12* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES13* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN14* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF15* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.16*/1718#include <config.h>1920#include <stdio.h>21#include <string.h>22#include <unistd.h>23#include <errno.h>24#include <fcntl.h>25#include <signal.h>2627#include <pathnames.h>28#include <sudo_debug.h>29#include <sudo_fatal.h>30#include <sudo_gettext.h>31#include <sudo_exec.h>3233static volatile sig_atomic_t got_sigttou;3435/*36* SIGTTOU signal handler for tcsetpgrp_nobg() that just sets a flag.37*/38static void39sigttou(int signo)40{41got_sigttou = 1;42}4344/*45* Like tcsetpgrp() but restarts on EINTR _except_ for SIGTTOU.46* Returns 0 on success or -1 on failure, setting errno.47* Sets got_sigttou on failure if interrupted by SIGTTOU.48*/49static int50tcsetpgrp_nobg(int fd, pid_t pgrp_id)51{52struct sigaction sa, osa;53int rc;54debug_decl(tcsetpgrp_nobg, SUDO_DEBUG_UTIL);5556/*57* If we receive SIGTTOU from tcsetpgrp() it means we are58* not in the foreground process group.59* This avoid a TOCTOU race compared to using tcgetpgrp().60*/61memset(&sa, 0, sizeof(sa));62sigemptyset(&sa.sa_mask);63sa.sa_flags = 0; /* do not restart syscalls */64sa.sa_handler = sigttou;65got_sigttou = 0;66(void)sigaction(SIGTTOU, &sa, &osa);67do {68rc = tcsetpgrp(fd, pgrp_id);69} while (rc != 0 && errno == EINTR && !got_sigttou);70(void)sigaction(SIGTTOU, &osa, NULL);7172debug_return_int(rc);73}7475/*76* Suspend the main process in response to an interactive child process77* being suspended.78*/79void80sudo_suspend_parent(int signo, pid_t my_pid, pid_t my_pgrp, pid_t cmnd_pid,81void *closure, void (*callback)(void *, int))82{83struct sigaction sa, osa;84pid_t saved_pgrp = -1;85int fd;86debug_decl(sudo_suspend_parent, SUDO_DEBUG_EXEC);8788/*89* Save the controlling terminal's process group so we can restore90* it after we resume, if needed. Most well-behaved shells change91* the pgrp back to its original value before suspending so we must92* not try to restore in that case, lest we race with the command93* upon resume, potentially stopping sudo with SIGTTOU while the94* command continues to run.95*/96fd = open(_PATH_TTY, O_RDWR);97if (fd != -1) {98saved_pgrp = tcgetpgrp(fd);99if (saved_pgrp == -1) {100close(fd);101fd = -1;102}103}104105if (saved_pgrp != -1) {106/*107* Command was stopped trying to access the controlling108* terminal. If the command has a different pgrp and we109* own the controlling terminal, give it to the command's110* pgrp and let it continue.111*/112if (signo == SIGTTOU || signo == SIGTTIN) {113if (saved_pgrp == my_pgrp) {114pid_t cmnd_pgrp = getpgid(cmnd_pid);115if (cmnd_pgrp != my_pgrp) {116if (tcsetpgrp_nobg(fd, cmnd_pgrp) == 0) {117if (killpg(cmnd_pgrp, SIGCONT) != 0)118sudo_warn("kill(%d, SIGCONT)", (int)cmnd_pgrp);119close(fd);120debug_return;121}122}123}124}125}126127/* Run callback before we suspend. */128if (callback != NULL)129callback(closure, signo);130131if (signo == SIGTSTP) {132memset(&sa, 0, sizeof(sa));133sigemptyset(&sa.sa_mask);134sa.sa_flags = SA_RESTART;135sa.sa_handler = SIG_DFL;136if (sigaction(SIGTSTP, &sa, &osa) != 0)137sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);138}139if (kill(my_pid, signo) != 0)140sudo_warn("kill(%d, %d)", (int)my_pid, signo);141if (signo == SIGTSTP) {142if (sigaction(SIGTSTP, &osa, NULL) != 0)143sudo_warn(U_("unable to restore handler for signal %d"), SIGTSTP);144}145146/* Run callback on resume. */147if (callback != NULL)148callback(closure, SIGCONT);149150if (saved_pgrp != -1) {151/*152* On resume, restore foreground process group, if different.153* Otherwise, we cannot resume some shells (pdksh).154*155* It is possible that we are no longer the foreground process,156* use tcsetpgrp_nobg() to prevent sudo from receiving SIGTTOU.157*/158if (saved_pgrp != my_pgrp)159tcsetpgrp_nobg(fd, saved_pgrp);160close(fd);161}162163debug_return;164}165166167