If the process is already being traced, just resume it and clear flags.

This makes it possible to run sudo in ptrace intercept mode from within
a shell (or other process) that is already being traced by sudo.
This commit is contained in:
Todd C. Miller
2022-05-03 13:34:40 -06:00
parent cc52ab770c
commit e84fdd99fd
4 changed files with 46 additions and 20 deletions

View File

@@ -476,18 +476,21 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]); fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]);
if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) { if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) {
bool success = true; int rc = 1;
/* Create event and closure for intercept mode. */ /* Create event and closure for intercept mode. */
ec.intercept = intercept_setup(intercept_sv[0], ec.evbase, details); ec.intercept = intercept_setup(intercept_sv[0], ec.evbase, details);
if (ec.intercept == NULL) { if (ec.intercept == NULL) {
success = false; rc = -1;
} else if (ISSET(details->flags, CD_USE_PTRACE)) { } else if (ISSET(details->flags, CD_USE_PTRACE)) {
/* Seize control of the command using ptrace(2). */ /* Try to seize control of the command using ptrace(2). */
if (!exec_ptrace_seize(ec.cmnd_pid)) rc = exec_ptrace_seize(ec.cmnd_pid);
success = false; if (rc == 0) {
/* There is another tracer present. */
CLR(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS|CD_USE_PTRACE);
}
} }
if (!success) if (rc == -1)
terminate_command(ec.cmnd_pid, true); terminate_command(ec.cmnd_pid, true);
} }

View File

@@ -611,27 +611,43 @@ set_exec_filter(void)
/* /*
* Seize control of the specified child process which must be in * Seize control of the specified child process which must be in
* ptrace wait. Returns true on success and false on failure. * ptrace wait. Returns true on success, false if child is already
* being traced and -1 on error.
*/ */
bool int
exec_ptrace_seize(pid_t child) exec_ptrace_seize(pid_t child)
{ {
const long ptrace_opts = PTRACE_O_TRACESECCOMP|PTRACE_O_TRACECLONE| const long ptrace_opts = PTRACE_O_TRACESECCOMP|PTRACE_O_TRACECLONE|
PTRACE_O_TRACEFORK|PTRACE_O_TRACEVFORK; PTRACE_O_TRACEFORK|PTRACE_O_TRACEVFORK;
int ret = -1;
int status; int status;
debug_decl(exec_ptrace_seize, SUDO_DEBUG_UTIL); debug_decl(exec_ptrace_seize, SUDO_DEBUG_UTIL);
/* Seize control of the child process. */ /* Seize control of the child process. */
if (ptrace(PTRACE_SEIZE, child, NULL, ptrace_opts) == -1) { if (ptrace(PTRACE_SEIZE, child, NULL, ptrace_opts) == -1) {
sudo_warn("ptrace(PTRACE_SEIZE, %d, NULL, 0x%lx)", (int)child, /*
ptrace_opts); * If the process is already being traced, we will get EPERM.
debug_return_bool(false); * We don't treat that as a fatal error since we want it to be
* possible to run sudo inside a sudo shell with intercept enabled.
*/
if (errno != EPERM) {
sudo_warn("ptrace(PTRACE_SEIZE, %d, NULL, 0x%lx)", (int)child,
ptrace_opts);
goto done;
}
sudo_debug_printf(SUDO_DEBUG_WARN,
"%s: unable to trace process %d, already being traced?",
__func__, (int)child);
ret = false;
} }
/* The child is suspended waiting for SIGUSR1, wake it up. */ /* The child is suspended waiting for SIGUSR1, wake it up. */
if (kill(child, SIGUSR1) == -1) { if (kill(child, SIGUSR1) == -1) {
sudo_warn("kill(%d, SIGUSR1)", child); sudo_warn("kill(%d, SIGUSR1)", child);
debug_return_bool(false); goto done;
} }
if (!ret)
goto done;
/* Wait for the child to enter trace stop and continue it. */ /* Wait for the child to enter trace stop and continue it. */
for (;;) { for (;;) {
@@ -640,18 +656,21 @@ exec_ptrace_seize(pid_t child)
if (errno == EINTR) if (errno == EINTR)
continue; continue;
sudo_warn(U_("%s: %s"), __func__, "waitpid"); sudo_warn(U_("%s: %s"), __func__, "waitpid");
debug_return_bool(false); goto done;
} }
if (!WIFSTOPPED(status)) { if (!WIFSTOPPED(status)) {
sudo_warnx(U_("process %d exited unexpectedly"), (int)child); sudo_warnx(U_("process %d exited unexpectedly"), (int)child);
debug_return_bool(false); goto done;
} }
if (ptrace(PTRACE_CONT, child, NULL, (long)SIGUSR1) == -1) { if (ptrace(PTRACE_CONT, child, NULL, (long)SIGUSR1) == -1) {
sudo_warn("ptrace(PTRACE_CONT, %d, NULL, SIGUSR1)", (int)child); sudo_warn("ptrace(PTRACE_CONT, %d, NULL, SIGUSR1)", (int)child);
debug_return_bool(false); goto done;
} }
debug_return_bool(true); ret = true;
done:
debug_return_int(ret);
} }
/* /*
@@ -921,7 +940,7 @@ exec_ptrace_handled(pid_t pid, int status, void *intercept)
} }
/* STUB */ /* STUB */
bool int
exec_ptrace_seize(pid_t child) exec_ptrace_seize(pid_t child)
{ {
return true; return true;

View File

@@ -994,8 +994,12 @@ backchannel_cb(int fd, int what, void *v)
sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
ec->details->command, (int)ec->cmnd_pid); ec->details->command, (int)ec->cmnd_pid);
if (ISSET(ec->details->flags, CD_USE_PTRACE)) { if (ISSET(ec->details->flags, CD_USE_PTRACE)) {
/* Seize control of the command using ptrace(2). */ /* Try to seize control of the command using ptrace(2). */
if (!exec_ptrace_seize(ec->cmnd_pid)) { int rc = exec_ptrace_seize(ec->cmnd_pid);
if (rc == 0) {
/* There is another tracer present. */
CLR(ec->details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS|CD_USE_PTRACE);
} else if (rc == -1) {
if (ec->cstat->type == CMD_INVALID) { if (ec->cstat->type == CMD_INVALID) {
ec->cstat->type = CMD_ERRNO; ec->cstat->type = CMD_ERRNO;
ec->cstat->val = errno; ec->cstat->val = errno;

View File

@@ -146,8 +146,8 @@ char **sudo_preload_dso(char *envp[], const char *dso_file, int intercept_fd);
/* exec_ptrace.c */ /* exec_ptrace.c */
bool exec_ptrace_handled(pid_t pid, int status, void *intercept); bool exec_ptrace_handled(pid_t pid, int status, void *intercept);
bool exec_ptrace_seize(pid_t child);
bool have_seccomp_action(const char *action); bool have_seccomp_action(const char *action);
bool set_exec_filter(void); bool set_exec_filter(void);
int exec_ptrace_seize(pid_t child);
#endif /* SUDO_EXEC_H */ #endif /* SUDO_EXEC_H */