From a52e3776f04ef103da34d44df9013cc5a877dbe1 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Tue, 17 May 2022 14:26:03 -0600 Subject: [PATCH] Fix suspending a sudo-run shell in ptrace intercept mode with no pty. When ptracing a process, we receive the signal-delivery-stop signal before the group-stop signal. If sudo is running the command in the same terminal, we need to wait until the stop signal is actually delivered to the command before we can suspend sudo itself. If we suspend sudo before receiving the group-stop, the command will be restarted with PTRACE_LISTEN too late and will miss the SIGCONT from sudo. --- src/exec_nopty.c | 4 ++-- src/exec_ptrace.c | 26 +++++++++++++------------- src/exec_pty.c | 2 +- src/regress/intercept/test_ptrace.c | 8 +++++++- src/sudo_exec.h | 2 +- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/exec_nopty.c b/src/exec_nopty.c index 63d6fe323..a9fc72408 100644 --- a/src/exec_nopty.c +++ b/src/exec_nopty.c @@ -568,8 +568,8 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec) "%s: process %d stopped, SIG%s", __func__, (int)pid, signame); if (ISSET(ec->details->flags, CD_USE_PTRACE)) { - /* Did exec_ptrace_handled() suppress the signal? */ - if (exec_ptrace_handled(pid, status, ec->intercept)) + /* If not a group-stop signal, just continue. */ + if (!exec_ptrace_stopped(pid, status, ec->intercept)) continue; } diff --git a/src/exec_ptrace.c b/src/exec_ptrace.c index 169eedfda..6661e943f 100644 --- a/src/exec_ptrace.c +++ b/src/exec_ptrace.c @@ -1195,17 +1195,19 @@ done: /* * Handle a process stopped due to ptrace. - * Returns true if the signal was suppressed and false if it was delivered. + * Restarts the tracee with PTRACE_LISTEN (for a group-stop) + * or PTRACE_CONT (for signal-delivery-stop). + * Returns true if stopped by a group-stop, else false. */ bool -exec_ptrace_handled(pid_t pid, int status, void *intercept) +exec_ptrace_stopped(pid_t pid, int status, void *intercept) { struct intercept_closure *closure = intercept; const int stopsig = WSTOPSIG(status); const int sigtrap = status >> 8; long signo = 0; bool group_stop = false; - debug_decl(exec_ptrace_handled, SUDO_DEBUG_EXEC); + debug_decl(exec_ptrace_stopped, SUDO_DEBUG_EXEC); if (sigtrap == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) { if (!ptrace_intercept_execve(pid, closure)) { @@ -1255,24 +1257,22 @@ exec_ptrace_handled(pid_t pid, int status, void *intercept) } } - /* Continue child. */ - /* XXX - handle ptrace returning ESRCH if process dies */ if (group_stop) { /* * Restart child but prevent it from executing * until SIGCONT is received (simulate SIGSTOP, etc). */ - if (ptrace(PTRACE_LISTEN, pid, NULL, 0L) == -1) - sudo_warn("%s: ptrace(PTRACE_LISTEN, %d, NULL, %d)", - __func__, (int)pid, stopsig); + if (ptrace(PTRACE_LISTEN, pid, NULL, 0L) == -1 && errno != ESRCH) + sudo_warn("%s: ptrace(PTRACE_LISTEN, %d, NULL, 0L)", + __func__, (int)pid); } else { - /* Restart child. */ - if (ptrace(PTRACE_CONT, pid, NULL, signo) == -1) + /* Restart child immediately. */ + if (ptrace(PTRACE_CONT, pid, NULL, signo) == -1 && errno != ESRCH) sudo_warn("%s: ptrace(PTRACE_CONT, %d, NULL, %ld)", __func__, (int)pid, signo); } - debug_return_bool(signo == 0); + debug_return_bool(group_stop); } #else /* STUB */ @@ -1284,9 +1284,9 @@ have_seccomp_action(const char *action) /* STUB */ bool -exec_ptrace_handled(pid_t pid, int status, void *intercept) +exec_ptrace_stopped(pid_t pid, int status, void *intercept) { - return false; + return true; } /* STUB */ diff --git a/src/exec_pty.c b/src/exec_pty.c index 119afd774..4e9b71d3d 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -1100,7 +1100,7 @@ handle_sigchld_pty(struct exec_closure_pty *ec) } else if (WIFSTOPPED(status)) { if (pid != ec->monitor_pid) { if (ISSET(ec->details->flags, CD_USE_PTRACE)) - exec_ptrace_handled(pid, status, ec->intercept); + exec_ptrace_stopped(pid, status, ec->intercept); continue; } diff --git a/src/regress/intercept/test_ptrace.c b/src/regress/intercept/test_ptrace.c index 02550cdc1..704222a18 100644 --- a/src/regress/intercept/test_ptrace.c +++ b/src/regress/intercept/test_ptrace.c @@ -210,7 +210,13 @@ main(int argc, char *argv[]) if (pid == child) return WTERMSIG(status) | 128; } else if (WIFSTOPPED(status)) { - exec_ptrace_handled(pid, status, &closure); + if (exec_ptrace_stopped(pid, status, &closure)) { + if (pid == child) { + /* TODO - terminal pgrp switching like exec_nopty.c. */ + kill(getpid(), WSTOPSIG(status)); + kill(child, SIGCONT); + } + } } else { sudo_fatalx("%d: unknown status 0x%x", pid, status); } diff --git a/src/sudo_exec.h b/src/sudo_exec.h index a640da636..9c58cd2ab 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -147,7 +147,7 @@ bool utmp_logout(const char *line, int status); char **sudo_preload_dso(char *envp[], const char *dso_file, int intercept_fd); /* exec_ptrace.c */ -bool exec_ptrace_handled(pid_t pid, int status, void *intercept); +bool exec_ptrace_stopped(pid_t pid, int status, void *intercept); bool set_exec_filter(void); int exec_ptrace_seize(pid_t child);