Add test_ptrace program to test ptrace-based intercept support.
This commit is contained in:
1
MANIFEST
1
MANIFEST
@@ -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
|
||||
|
@@ -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 \
|
||||
|
@@ -27,6 +27,7 @@ enum intercept_state {
|
||||
RECV_CONNECTION,
|
||||
POLICY_ACCEPT,
|
||||
POLICY_REJECT,
|
||||
POLICY_TEST,
|
||||
POLICY_ERROR
|
||||
};
|
||||
|
||||
|
@@ -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, ®s, EACCES);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
215
src/regress/intercept/test_ptrace.c
Normal file
215
src/regress/intercept/test_ptrace.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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__)
|
||||
# define HAVE_PTRACE_INTERCEPT 1
|
||||
# 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__ */
|
||||
|
Reference in New Issue
Block a user