Add test_ptrace program to test ptrace-based intercept support.

This commit is contained in:
Todd C. Miller
2022-05-11 20:07:55 -06:00
parent 532e8218b2
commit 040e75a07b
6 changed files with 267 additions and 8 deletions

View File

@@ -1204,6 +1204,7 @@ src/openbsd.c
src/parse_args.c
src/preload.c
src/preserve_fds.c
src/regress/intercept/test_ptrace.c
src/regress/net_ifs/check_net_ifs.c
src/regress/noexec/check_noexec.c
src/regress/ttyname/check_ttyname.c

View File

@@ -161,6 +161,8 @@ CHECK_NOEXEC_OBJS = check_noexec.o exec_common.o exec_preload.o
CHECK_TTYNAME_OBJS = check_ttyname.o ttyname.o
TEST_PTRACE_OBJS = test_ptrace.o
LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
VERSION = @PACKAGE_VERSION@
@@ -235,6 +237,9 @@ check_noexec: $(CHECK_NOEXEC_OBJS) $(top_builddir)/lib/util/libsudo_util.la sudo
check_ttyname: $(CHECK_TTYNAME_OBJS) $(top_builddir)/lib/util/libsudo_util.la
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_TTYNAME_OBJS) $(TEST_LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LIBS)
test_ptrace: $(TEST_PTRACE_OBJS) $(top_builddir)/lib/util/libsudo_util.la
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(TEST_PTRACE_OBJS) $(TEST_LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(HARDENING_LDFLAGS) $(TEST_LIBS)
pre-install:
install: install-binaries install-rc @INSTALL_INTERCEPT@ @INSTALL_NOEXEC@
@@ -966,6 +971,28 @@ tcsetpgrp_nobg.i: $(srcdir)/tcsetpgrp_nobg.c $(incdir)/compat/stdbool.h \
$(CC) -E -o $@ $(CPPFLAGS) $<
tcsetpgrp_nobg.plog: tcsetpgrp_nobg.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/tcsetpgrp_nobg.c --i-file $< --output-file $@
test_ptrace.o: $(srcdir)/regress/intercept/test_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)/exec_intercept.h $(srcdir)/exec_ptrace.c \
$(srcdir)/exec_ptrace.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)/regress/intercept/test_ptrace.c
test_ptrace.i: $(srcdir)/regress/intercept/test_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)/exec_intercept.h $(srcdir)/exec_ptrace.c \
$(srcdir)/exec_ptrace.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
$(top_builddir)/config.h $(top_builddir)/pathnames.h
$(CC) -E -o $@ $(CPPFLAGS) $<
test_ptrace.plog: test_ptrace.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/intercept/test_ptrace.c --i-file $< --output-file $@
tgetpass.o: $(srcdir)/tgetpass.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \

View File

@@ -27,6 +27,7 @@ enum intercept_state {
RECV_CONNECTION,
POLICY_ACCEPT,
POLICY_REJECT,
POLICY_TEST,
POLICY_ERROR
};

View File

@@ -790,10 +790,13 @@ ptrace_intercept_execve(pid_t pid, struct intercept_closure *closure)
char *pathname, **argv, **envp, *buf;
int argc, envc, syscallno;
struct sudo_ptrace_regs regs;
bool path_mismatch = false;
bool argv_mismatch = false;
char cwd[PATH_MAX];
unsigned long msg;
bool ret = false;
struct stat sb;
int i;
debug_decl(ptrace_intercept_execve, SUDO_DEBUG_UTIL);
/* Do not check the policy if we are executing the initial command. */
@@ -888,21 +891,29 @@ ptrace_intercept_execve(pid_t pid, struct intercept_closure *closure)
sudo_warnx("%s", U_(closure->errstr));
}
if (closure->state == POLICY_ACCEPT) {
switch (closure->state) {
case POLICY_TEST:
path_mismatch = true;
argv_mismatch = true;
if (closure->command == NULL)
closure->command = pathname;
if (closure->run_argv == NULL)
closure->run_argv = argv;
FALLTHROUGH;
case POLICY_ACCEPT:
/*
* Update pathname and argv if the policy modified it.
* We don't currently ever modify envp.
*/
bool path_mismatch = strcmp(pathname, closure->command) != 0;
bool argv_mismatch = false;
int i;
if (strcmp(pathname, closure->command) != 0)
path_mismatch = true;
for (i = 0; closure->run_argv[i] != NULL && argv[i] != NULL; i++) {
if (strcmp(closure->run_argv[i], argv[i]) != 0) {
argv_mismatch = true;
break;
}
}
if (path_mismatch || argv_mismatch) {
/*
* Need to rewrite pathname and/or argv.
@@ -993,9 +1004,11 @@ ptrace_intercept_execve(pid_t pid, struct intercept_closure *closure)
goto done;
}
}
} else {
/* If denied, fake the syscall and set return to EACCES */
break;
default:
/* If rejected, fake the syscall and set return to EACCES */
ptrace_fail_syscall(pid, &regs, EACCES);
break;
}
ret = true;

View File

@@ -0,0 +1,215 @@
/*
* 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
*/
/*
* Test program to exercise seccomp(2) and ptrace(2) intercept code.
*
* Usage: test_ptrace [-v] [command]
*/
/* Ignore architecture restrictions and define this unilaterally. */
#define HAVE_PTRACE_INTERCEPT
#include "exec_ptrace.c"
static sig_atomic_t got_sigchld;
static int verbose;
int sudo_debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER;
sudo_dso_public int main(int argc, char *argv[]);
static void
handler(int signo)
{
if (signo == SIGCHLD)
got_sigchld = 1;
}
void
intercept_closure_reset(struct intercept_closure *closure)
{
memset(closure, 0, sizeof(*closure));
}
bool
intercept_check_policy(const char *command, int argc, char **argv, int envc,
char **envp, const char *runcwd, void *v)
{
struct intercept_closure *closure = v;
struct stat sb1, sb2;
bool is_denied;
debug_decl(intercept_check_policy, SUDO_DEBUG_EXEC);
/* Fake policy decisions. */
is_denied = stat(command, &sb1) == 0 && stat("/usr/bin/who", &sb2) == 0 &&
sb1.st_ino == sb2.st_ino && sb1.st_dev == sb2.st_dev;
if (is_denied) {
sudo_debug_printf(SUDO_DEBUG_DIAG, "denied %s", command);
closure->state = POLICY_REJECT;
} else {
sudo_debug_printf(SUDO_DEBUG_DIAG, "allowed %s", command);
closure->state = POLICY_TEST;
}
debug_return_bool(true);
}
static void
init_debug_files(struct sudo_conf_debug_file_list *file_list,
struct sudo_debug_file *file)
{
debug_decl(init_debug_files, SUDO_DEBUG_EXEC);
TAILQ_INIT(file_list);
switch (verbose) {
case 0:
file->debug_flags = NULL;
break;
case 1:
file->debug_flags = "exec@diag";
break;
case 2:
file->debug_flags = "exec@info";
break;
default:
file->debug_flags = "exec@debug";
break;
}
file->debug_file = "/dev/stderr";
TAILQ_INSERT_HEAD(file_list, file, entries);
debug_return;
}
int
main(int argc, char *argv[])
{
struct sudo_conf_debug_file_list debug_files;
struct sudo_debug_file debug_file;
const char *base, *shell = _PATH_SUDO_BSHELL;
struct intercept_closure closure = { 0 };
sigset_t blocked, empty;
struct sigaction sa;
pid_t child, pid;
int ch, status;
debug_decl_vars(main, SUDO_DEBUG_MAIN);
initprogname(argc > 0 ? argv[0] : "test_ptrace");
while ((ch = getopt(argc, argv, "v")) != -1) {
switch (ch) {
case 'v':
verbose++;
break;
default:
fprintf(stderr, "usage: %s [-v] [command]\n", getprogname());
return EXIT_FAILURE;
}
}
argc -= optind;
argv += optind;
if (argc > 0)
shell = argv[0];
base = strrchr(shell, '/');
base = base ? base + 1 : shell;
/* Set debug level based on the verbose flag. */
init_debug_files(&debug_files, &debug_file);
sudo_debug_instance = sudo_debug_register(getprogname(),
NULL, NULL, &debug_files, -1);
if (sudo_debug_instance == SUDO_DEBUG_INSTANCE_ERROR)
return EXIT_FAILURE;
/* Block SIGCHLD and SIGUSR during critical section. */
sigemptyset(&empty);
sigemptyset(&blocked);
sigaddset(&blocked, SIGCHLD);
sigaddset(&blocked, SIGUSR1);
sigprocmask(SIG_BLOCK, &blocked, NULL);
/* Signal handler sets a flag for SIGCHLD, nothing for SIGUSR1. */
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = handler;
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
/* Fork a shell. */
child = fork();
switch (child) {
case -1:
sudo_fatal("fork");
case 0:
/* child */
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
sudo_fatal("%s", "unable to set no_new_privs bit");
if (!set_exec_filter())
_exit(EXIT_FAILURE);
/* Suspend child until tracer seizes control and sends SIGUSR1. */
sigsuspend(&empty);
execl(shell, base, NULL);
sudo_fatal("execl");
default:
/* Parent attaches to child and allows it to continue. */
if (exec_ptrace_seize(child) == -1)
return EXIT_FAILURE;
break;
}
/* Wait for SIGCHLD. */
for (;;) {
sigsuspend(&empty);
if (!got_sigchld)
continue;
got_sigchld = 0;
for (;;) {
do {
pid = waitpid(-1, &status, __WALL|WNOHANG);
} while (pid == -1 && errno == EINTR);
if (pid <= 0) {
if (pid == -1 && errno != ECHILD)
sudo_fatal("waitpid");
/* No child to wait for. */
break;
}
if (WIFEXITED(status)) {
sudo_debug_printf(SUDO_DEBUG_DIAG, "%d: exited %d",
pid, WEXITSTATUS(status));
if (pid == child)
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
sudo_debug_printf(SUDO_DEBUG_DIAG, "%d: killed by signal %d",
pid, WTERMSIG(status));
if (pid == child)
return WTERMSIG(status) | 128;
} else if (WIFSTOPPED(status)) {
/* XXX - verbose info to stderr, not just debug log */
exec_ptrace_handled(pid, status, &closure);
} else {
sudo_fatalx("%d: unknown status 0x%x", pid, status);
}
}
}
}

View File

@@ -97,7 +97,9 @@ union sudo_token_un {
#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__)
# ifndef HAVE_PTRACE_INTERCEPT
# define HAVE_PTRACE_INTERCEPT 1
# endif /* HAVE_PTRACE_INTERCEPT */
# endif /* __amd64__ || __i386__ || __aarch64__ */
# endif /* HAVE_DECL_SECCOMP_SET_MODE_FILTER */
#endif /* _PATH_SUDO_INTERCEPT && __linux__ */