Add scaffolding for ptrace-based intercept mode.

This commit is contained in:
Todd C. Miller
2022-04-29 12:35:31 -06:00
parent 22866f2423
commit 01733a5214
10 changed files with 468 additions and 76 deletions

View File

@@ -1183,6 +1183,7 @@ src/env_hooks.c
src/exec.c
src/exec_common.c
src/exec_intercept.c
src/exec_ptrace.c
src/exec_monitor.c
src/exec_nopty.c
src/exec_preload.c

View File

@@ -1,7 +1,7 @@
#
# SPDX-License-Identifier: ISC
#
# Copyright (c) 2010-2021 Todd C. Miller <Todd.Miller@sudo.ws>
# Copyright (c) 2010-2022 Todd C. Miller <Todd.Miller@sudo.ws>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -141,10 +141,10 @@ SHELL = @SHELL@
PROGS = @PROGS@
OBJS = conversation.o copy_file.o edit_open.o env_hooks.o exec.o exec_common.o \
exec_intercept.o exec_monitor.o exec_nopty.o exec_preload.o exec_pty.o \
get_pty.o hooks.o limits.o load_plugins.o net_ifs.o parse_args.o \
preserve_fds.o signal.o sudo.o sudo_edit.o tcsetpgrp_nobg.o tgetpass.o \
ttyname.o utmp.o @SUDO_OBJS@
exec_intercept.o exec_monitor.o exec_nopty.o exec_preload.o \
exec_ptrace.o exec_pty.o get_pty.o hooks.o limits.o load_plugins.o \
net_ifs.o parse_args.o preserve_fds.o signal.o sudo.o sudo_edit.o \
tcsetpgrp_nobg.o tgetpass.o ttyname.o utmp.o @SUDO_OBJS@
IOBJS = $(OBJS:.o=.i) sesh.i
@@ -607,6 +607,24 @@ exec_preload.o: $(srcdir)/exec_preload.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/exec_preload.c
exec_ptrace.o: $(srcdir)/exec_ptrace.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/exec_ptrace.c
exec_ptrace.i: $(srcdir)/exec_ptrace.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(CC) -E -o $@ $(CPPFLAGS) $<
exec_ptrace.plog: exec_ptrace.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec_ptrace.c --i-file $< --output-file $@
exec_pty.o: $(srcdir)/exec_pty.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
* Copyright (c) 2009-2022 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -86,6 +86,13 @@ exec_setup(struct command_details *details, int intercept_fd, int errfd)
bool ret = false;
debug_decl(exec_setup, SUDO_DEBUG_EXEC);
#ifdef HAVE_PTRACE_INTERCEPT
if (ISSET(details->flags, CD_USE_PTRACE)) {
if (!set_exec_filter())
goto done;
}
#endif /* HAVE_PTRACE_INTERCEPT */
if (details->pw != NULL) {
#ifdef HAVE_PROJECT_H
set_project(details->pw);

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
* Copyright (c) 2009-2022 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -89,6 +89,31 @@ enable_intercept(char *envp[], const char *dso, int intercept_fd)
debug_return_ptr(envp);
}
/*
* Called right before execve(2).
* The tracee will be suspended until the tracer resumes it.
*/
static void
enable_ptrace(void)
{
#ifdef HAVE_PTRACE_INTERCEPT
const pid_t pid = getpid();
debug_decl(enable_ptrace, SUDO_DEBUG_UTIL);
/*
* Parent will trace child and intercept execve(2).
* We stop the child here so the parent can seize control.
*/
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: suspending child %d",
__func__, (int)pid);
kill(pid, SIGSTOP);
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: resuming child %d",
__func__, (int)pid);
debug_return;
#endif /* HAVE_PTRACE_INTERCEPT */
}
/*
* Like execve(2) but falls back to running through /bin/sh
* ala execvp(3) if we get ENOEXEC.
@@ -104,6 +129,8 @@ sudo_execve(int fd, const char *path, char *const argv[], char *envp[],
/* Modify the environment as needed to trap execve(). */
if (ISSET(flags, CD_NOEXEC))
envp = disable_execute(envp, sudo_conf_noexec_path());
else if (ISSET(flags, CD_USE_PTRACE))
enable_ptrace();
else if (ISSET(flags, CD_INTERCEPT|CD_LOG_SUBCMDS))
envp = enable_intercept(envp, sudo_conf_intercept_path(), intercept_fd);

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
* Copyright (c) 2021-2022 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -89,6 +89,7 @@ intercept_setup(int fd, struct sudo_event_base *evbase,
struct command_details *details)
{
struct intercept_closure *closure;
int rc;
debug_decl(intercept_setup, SUDO_DEBUG_EXEC);
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
@@ -99,19 +100,24 @@ intercept_setup(int fd, struct sudo_event_base *evbase,
sudo_warnx("%s", U_("unable to allocate memory"));
goto bad;
}
/* If we've already seen an InterceptHello, expect a policy check first. */
closure->state = sudo_token_isset(intercept_token) ?
RECV_SECRET : RECV_HELLO_INITIAL;
closure->details = details;
closure->listen_sock = -1;
if (sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
/* This cannot (currently) fail. */
sudo_warn("%s", U_("unable to add event to queue"));
goto bad;
if (ISSET(details->flags, CD_USE_PTRACE)) {
/* We can perform a policy check immediately using ptrace(2). */
closure->state = RECV_POLICY_CHECK;
} else {
/*
* Not using ptrace(2), use LD_PRELOAD (or its equivalent). If
* we've already seen an InterceptHello, expect a policy check first.
*/
closure->state = sudo_token_isset(intercept_token) ?
RECV_SECRET : RECV_HELLO_INITIAL;
}
if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
rc = sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST,
intercept_cb, closure);
if (rc == -1 || sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
sudo_warn("%s", U_("unable to add event to queue"));
goto bad;
}
@@ -124,21 +130,18 @@ bad:
}
/*
* Close intercept socket and free closure when we are done with
* the connection.
* Reset intercept_closure so it can be re-used.
*/
static void
intercept_connection_close(struct intercept_closure *closure)
intercept_closure_reset(struct intercept_closure *closure)
{
const int fd = sudo_ev_get_fd(&closure->ev);
size_t n;
debug_decl(intercept_connection_close, SUDO_DEBUG_EXEC);
debug_decl(intercept_closure_reset, SUDO_DEBUG_EXEC);
sudo_ev_del(NULL, &closure->ev);
close(fd);
if (closure->listen_sock != -1)
if (closure->listen_sock != -1) {
close(closure->listen_sock);
closure->listen_sock = -1;
}
free(closure->buf);
free(closure->command);
if (closure->run_argv != NULL) {
@@ -151,6 +154,31 @@ intercept_connection_close(struct intercept_closure *closure)
free(closure->run_envp[n]);
free(closure->run_envp);
}
closure->errstr = NULL;
closure->command = NULL;
closure->run_argv = NULL;
closure->run_envp = NULL;
closure->buf = NULL;
closure->len = 0;
closure->off = 0;
/* Does not currently reset token. */
debug_return;
}
/*
* Close intercept socket and free closure when we are done with
* the connection.
*/
static void
intercept_connection_close(struct intercept_closure *closure)
{
const int fd = sudo_ev_get_fd(&closure->ev);
debug_decl(intercept_connection_close, SUDO_DEBUG_EXEC);
sudo_ev_del(NULL, &closure->ev);
close(fd);
intercept_closure_reset(closure);
free(closure);
debug_return;
@@ -305,7 +333,7 @@ intercept_check_policy(PolicyCheckRequest *req,
char **user_env_out = NULL;
char **argv = NULL, **run_argv = NULL;
bool ret = false;
int result;
int rc;
size_t n;
debug_decl(intercept_check_policy, SUDO_DEBUG_EXEC);
@@ -338,13 +366,13 @@ intercept_check_policy(PolicyCheckRequest *req,
if (ISSET(closure->details->flags, CD_INTERCEPT)) {
/* We don't currently have a good way to validate the environment. */
sudo_debug_set_active_instance(policy_plugin.debug_instance);
result = policy_plugin.u.policy->check_policy(n, argv, NULL,
rc = policy_plugin.u.policy->check_policy(n, argv, NULL,
&command_info, &run_argv, &user_env_out, &closure->errstr);
sudo_debug_set_active_instance(sudo_debug_instance);
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"check_policy returns %d", result);
"check_policy returns %d", rc);
switch (result) {
switch (rc) {
case 1:
/* Rebuild command_info[] with runcwd and extract command. */
command_info_copy = update_command_info(command_info, NULL,
@@ -475,7 +503,7 @@ static int
intercept_verify_token(int fd, struct intercept_closure *closure)
{
ssize_t nread;
debug_decl(intercept_read_token, SUDO_DEBUG_EXEC);
debug_decl(intercept_verify_token, SUDO_DEBUG_EXEC);
nread = recv(fd, closure->token.u8 + closure->off,
sizeof(closure->token) - closure->off, 0);
@@ -516,8 +544,9 @@ intercept_read(int fd, struct intercept_closure *closure)
{
struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev);
InterceptRequest *req = NULL;
ssize_t nread;
bool ret = false;
ssize_t nread;
int rc;
debug_decl(intercept_read, SUDO_DEBUG_EXEC);
if (closure->state == RECV_SECRET) {
@@ -641,12 +670,9 @@ unpack:
}
/* Switch event to write mode for the reply. */
if (sudo_ev_set(&closure->ev, fd, SUDO_EV_WRITE|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
/* This cannot (currently) fail. */
sudo_warn("%s", U_("unable to add event to queue"));
goto done;
}
if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
rc = sudo_ev_set(&closure->ev, fd, SUDO_EV_WRITE|SUDO_EV_PERSIST,
intercept_cb, closure);
if (rc == -1 || sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
sudo_warn("%s", U_("unable to add event to queue"));
goto done;
}
@@ -770,8 +796,9 @@ static bool
intercept_write(int fd, struct intercept_closure *closure)
{
struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev);
ssize_t nwritten;
bool ret = false;
ssize_t nwritten;
int rc;
debug_decl(intercept_write, SUDO_DEBUG_EXEC);
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "state %d",
@@ -825,16 +852,17 @@ intercept_write(int fd, struct intercept_closure *closure)
closure->len = 0;
closure->off = 0;
if (ISSET(closure->details->flags, CD_USE_PTRACE)) {
/* Ready for the next policy check from the tracer. */
closure->state = RECV_POLICY_CHECK;
} else {
switch (closure->state) {
case RECV_HELLO_INITIAL:
/* Re-use event for the listener. */
close(fd);
if (sudo_ev_set(&closure->ev, closure->listen_sock, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_accept_cb, closure) == -1) {
/* This cannot (currently) fail. */
sudo_warn("%s", U_("unable to add event to queue"));
goto done;
}
if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
rc = sudo_ev_set(&closure->ev, closure->listen_sock,
SUDO_EV_READ|SUDO_EV_PERSIST, intercept_accept_cb, closure);
if (rc == -1 || sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
sudo_warn("%s", U_("unable to add event to queue"));
goto done;
}
@@ -844,12 +872,9 @@ intercept_write(int fd, struct intercept_closure *closure)
break;
case POLICY_ACCEPT:
/* Re-use event to read InterceptHello from sudo_intercept.so ctor. */
if (sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
/* This cannot (currently) fail. */
sudo_warn("%s", U_("unable to add event to queue"));
goto done;
}
if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
rc = sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST,
intercept_cb, closure);
if (rc == -1 || sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
sudo_warn("%s", U_("unable to add event to queue"));
goto done;
}
@@ -859,6 +884,7 @@ intercept_write(int fd, struct intercept_closure *closure)
/* Done with this connection. */
intercept_connection_close(closure);
}
}
ret = true;

View File

@@ -371,10 +371,13 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
/*
* Allocate a socketpair for communicating with sudo_intercept.so.
* This must be inherited across exec, hence no FD_CLOEXEC.
* Check if the kernel supports the seccomp(2) filter "trap" action.
*/
if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) {
if (socketpair(PF_UNIX, SOCK_STREAM, 0, intercept_sv) == -1)
sudo_fatal("%s", U_("unable to create sockets"));
if (have_seccomp_action("trap"))
SET(details->flags, CD_USE_PTRACE);
}
/*
@@ -448,6 +451,11 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
exit(EXIT_FAILURE);
}
if (ISSET(details->flags, CD_USE_PTRACE)) {
/* Seize control of the command using ptrace(2). */
exec_ptrace_seize(ec.cmnd_pid);
}
/* Restore signal mask now that signal handlers are setup. */
sigprocmask(SIG_SETMASK, &oset, NULL);
@@ -487,10 +495,16 @@ static void
handle_sigchld_nopty(struct exec_closure_nopty *ec)
{
pid_t pid;
int status, wflags = WUNTRACED|WNOHANG;
int status, wflags;
char signame[SIG2STR_MAX];
debug_decl(handle_sigchld_nopty, SUDO_DEBUG_EXEC);
#ifdef __WALL
wflags = __WALL|WNOHANG;
#else
wflags = WUNTRACED|WNOHANG;
#endif
/* There may be multiple children in intercept mode. */
for (;;) {
/* Read command status. */
@@ -519,6 +533,12 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: command (%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))
continue;
}
/* Only report status for the main command. */
if (ec->cmnd_pid != pid)
continue;

258
src/exec_ptrace.c Normal file
View File

@@ -0,0 +1,258 @@
/*
* Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
*/
#include <config.h>
#include <sys/wait.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "sudo.h"
#include "sudo_exec.h"
#ifdef HAVE_PTRACE_INTERCEPT
# include <sys/prctl.h>
# include <sys/ptrace.h>
# include <sys/user.h>
# include <asm/unistd.h>
# include <linux/ptrace.h>
# include <linux/seccomp.h>
# include <linux/filter.h>
/*
* Check whether seccomp(2) filtering supports ptrace(2) traps.
* Only supported by Linux 4.14 and higher.
*/
bool
have_seccomp_action(const char *action)
{
char line[LINE_MAX];
bool ret = false;
FILE *fp;
debug_decl(have_seccomp_action, SUDO_DEBUG_EXEC);
fp = fopen("/proc/sys/kernel/seccomp/actions_avail", "r");
if (fp != NULL) {
if (fgets(line, sizeof(line), fp) != NULL) {
char *cp, *last;
for ((cp = strtok_r(line, " \t\n", &last)); cp != NULL;
(cp = strtok_r(NULL, " \t\n", &last))) {
if (strcmp(cp, action) == 0) {
ret = true;
break;
}
}
}
fclose(fp);
}
debug_return_bool(ret);
}
/*
* Intercept execve(2) using seccomp(2) and ptrace(2).
* If no tracer is present, execve(2) will fail with ENOSYS.
* Must be called with CAP_SYS_ADMIN, before privs are dropped.
*/
bool
set_exec_filter(void)
{
struct sock_filter exec_filter[] = {
/* Load syscall number into the accumulator */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
/* Jump to trace for execve(2), else allow. */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 0, 1),
/* Trace execve(2) syscall */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE),
/* Allow non-matching syscalls */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
};
const struct sock_fprog exec_fprog = {
nitems(exec_filter),
exec_filter
};
debug_decl(set_exec_filter, SUDO_DEBUG_UTIL);
/* We must set SECCOMP_MODE_FILTER before dropping privileges. */
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &exec_fprog) == -1) {
sudo_warn("%s", U_("unable to set seccomp filter"));
debug_return_bool(false);
}
debug_return_bool(true);
}
/*
* Seize control of the specified child process which must be in
* ptrace wait. Returns true on success and false on failure.
*/
bool
exec_ptrace_seize(pid_t child)
{
const long ptrace_opts = PTRACE_O_TRACESECCOMP|PTRACE_O_TRACECLONE|
PTRACE_O_TRACEFORK|PTRACE_O_TRACEVFORK;
int status;
pid_t pid;
debug_decl(exec_ptrace_seize, SUDO_DEBUG_UTIL);
/* Seize control of the child process. */
if (ptrace(PTRACE_SEIZE, child, NULL, ptrace_opts) == -1) {
sudo_warn("ptrace(PTRACE_SEIZE, %d, NULL, 0x%lx)", (int)child,
ptrace_opts);
debug_return_bool(false);
}
/* The child will stop itself immediately before execve(2). */
do {
pid = waitpid(child, &status, WUNTRACED);
} while (pid == -1 && errno == EINTR);
if (pid == -1) {
sudo_warn(U_("%s: %s"), __func__, "waitpid");
debug_return_bool(false);
}
if (!WIFSTOPPED(status)) {
sudo_warnx(U_("process %d exited unexpectedly"), (int)child);
debug_return_bool(false);
}
if (ptrace(PTRACE_CONT, child, NULL, NULL) == -1) {
sudo_warn("ptrace(PTRACE_CONT, %d, NULL, NULL)", (int)child);
debug_return_bool(false);
}
debug_return_bool(true);
}
/*
* Handle a process stopped due to ptrace.
* Returns true if the signal was suppressed and false if it was delivered.
*/
bool
exec_ptrace_handled(pid_t pid, int status)
{
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);
if (sigtrap == (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
/* Trapped child exec. */
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d called exec",
__func__, (int)pid);
/*
* XXX
* Get the exec arguments and perform a policy check either over
* the socketpair (pty case) or via a direct function call (no pty).
*/
} else if (sigtrap == (SIGTRAP | (PTRACE_EVENT_CLONE << 8)) ||
sigtrap == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
sigtrap == (SIGTRAP | (PTRACE_EVENT_FORK << 8))) {
unsigned long new_pid;
/* New child process, it will inherit the parent's trace flags. */
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &new_pid) != -1) {
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: %d forked new child %lu", __func__, (int)pid, new_pid);
} else {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"ptrace(PTRACE_GETEVENTMSG, %d, NULL, %p)", (int)pid,
&new_pid);
}
}
} else {
switch (stopsig) {
case SIGSTOP:
case SIGTSTP:
case SIGTTIN:
case SIGTTOU:
/* Is this a group-stop? */
if (status >> 16 == PTRACE_EVENT_STOP) {
/* Group-stop, do not deliver signal. */
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: %d: group-stop signal %d",
__func__, (int)pid, stopsig);
group_stop = true;
} else {
/* Signal-delivery-stop, deliver signal. */
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: %d: signal-delivery-stop signal %d",
__func__, (int)pid, stopsig);
signo = stopsig;
}
break;
default:
/* Not a stop signal so not a group-stop. */
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: %d: signal %d", __func__, (int)pid, stopsig);
break;
}
}
/* 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("ptrace(PTRACE_LISTEN,, %d, NULL, %d", pid, stopsig);
} else {
/* Restart child. */
if (ptrace(PTRACE_CONT, pid, NULL, signo) == -1)
sudo_warn("ptrace(PTRACE_CONT, %d, NULL, %d", pid, stopsig);
}
debug_return_bool(signo == 0);
}
#else
/* STUB */
void
exec_ptrace_enable(void)
{
return;
}
/* STUB */
bool
have_seccomp_action(const char *action)
{
return false;
}
/* STUB */
bool
exec_ptrace_handled(pid_t pid, int status)
{
return false;
}
/* STUB */
bool
exec_ptrace_seize(pid_t child)
{
return true;
}
#endif /* HAVE_PTRACE_INTERCEPT */

View File

@@ -992,6 +992,10 @@ backchannel_cb(int fd, int what, void *v)
ec->cmnd_pid = cstat.val;
sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
ec->details->command, (int)ec->cmnd_pid);
if (ISSET(ec->details->flags, CD_USE_PTRACE)) {
/* Seize control of the command using ptrace(2). */
exec_ptrace_seize(ec->cmnd_pid);
}
break;
case CMD_WSTATUS:
if (WIFSTOPPED(cstat.val)) {
@@ -1041,10 +1045,16 @@ backchannel_cb(int fd, int what, void *v)
static void
handle_sigchld_pty(struct exec_closure_pty *ec)
{
int n, status, wflags = WUNTRACED|WNOHANG;
int n, status, wflags;
pid_t pid;
debug_decl(handle_sigchld_pty, SUDO_DEBUG_EXEC);
#ifdef __WALL
wflags = __WALL|WNOHANG;
#else
wflags = WUNTRACED|WNOHANG;
#endif
/* There may be multiple children in intercept mode. */
for (;;) {
do {
@@ -1077,8 +1087,11 @@ handle_sigchld_pty(struct exec_closure_pty *ec)
if (pid == ec->monitor_pid)
ec->monitor_pid = -1;
} else if (WIFSTOPPED(status)) {
if (pid != ec->monitor_pid)
if (pid != ec->monitor_pid) {
if (ISSET(ec->details->flags, CD_USE_PTRACE))
exec_ptrace_handled(pid, status);
continue;
}
/*
* If the monitor dies we get notified via backchannel_cb().
@@ -1405,10 +1418,13 @@ exec_pty(struct command_details *details, struct command_status *cstat)
/*
* Allocate a socketpair for communicating with sudo_intercept.so.
* This must be inherited across exec, hence no FD_CLOEXEC.
* Check if the kernel supports the seccomp(2) filter "trap" action.
*/
if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) {
if (socketpair(PF_UNIX, SOCK_STREAM, 0, intercept_sv) == -1)
sudo_fatal("%s", U_("unable to create sockets"));
if (have_seccomp_action("trap"))
SET(details->flags, CD_USE_PTRACE);
}
/*

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 1993-1996, 1998-2005, 2007-2021
* Copyright (c) 1993-1996, 1998-2005, 2007-2022
* Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
@@ -140,6 +140,7 @@ struct user_details {
#define CD_LOGIN_SHELL 0x080000
#define CD_OVERRIDE_UMASK 0x100000
#define CD_LOG_SUBCMDS 0x200000
#define CD_USE_PTRACE 0x400000
struct preserved_fd {
TAILQ_ENTRY(preserved_fd) entries;

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2010-2017, 2020-2021 Todd C. Miller <Todd.Miller@sudo.ws>
* Copyright (c) 2010-2017, 2020-2022 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -90,6 +90,18 @@ union sudo_token_un {
#define sudo_token_isset(_t) ((_t).u64[0] || (_t).u64[1])
/*
* Use ptrace-based intercept (using seccomp) on Linux if possible.
* TODO: test other architectures
*/
#if defined(_PATH_SUDO_INTERCEPT) && defined(__linux__)
# if defined(HAVE_DECL_SECCOMP_SET_MODE_FILTER) && HAVE_DECL_SECCOMP_SET_MODE_FILTER
# if defined(__amd64__) || defined(__i386__) || defined(__aarch64__)
# define HAVE_PTRACE_INTERCEPT 1
# endif /* __amd64__ || __i386__ || __aarch64__ */
# endif /* HAVE_DECL_SECCOMP_SET_MODE_FILTER */
#endif /* _PATH_SUDO_INTERCEPT && __linux__ */
/*
* Symbols shared between exec.c, exec_nopty.c, exec_pty.c and exec_monitor.c
*/
@@ -132,4 +144,10 @@ bool utmp_logout(const char *line, int status);
/* exec_preload.c */
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);
bool exec_ptrace_seize(pid_t child);
bool have_seccomp_action(const char *action);
bool set_exec_filter(void);
#endif /* SUDO_EXEC_H */