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.
This commit is contained in:
Todd C. Miller
2022-05-17 14:26:03 -06:00
parent 0bcfe6184f
commit a52e3776f0
5 changed files with 24 additions and 18 deletions

View File

@@ -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;
}

View File

@@ -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 */

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);