diff options
Diffstat (limited to '')
-rw-r--r-- | src/regress/intercept/test_ptrace.c | 242 | ||||
-rw-r--r-- | src/regress/net_ifs/check_net_ifs.c | 87 | ||||
-rw-r--r-- | src/regress/noexec/check_noexec.c | 232 | ||||
-rw-r--r-- | src/regress/ttyname/check_ttyname.c | 122 |
4 files changed, 683 insertions, 0 deletions
diff --git a/src/regress/intercept/test_ptrace.c b/src/regress/intercept/test_ptrace.c new file mode 100644 index 0000000..72d4b2c --- /dev/null +++ b/src/regress/intercept/test_ptrace.c @@ -0,0 +1,242 @@ +/* + * 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 [-d 1-3] [command] + */ + +/* Ignore architecture restrictions and define this unilaterally. */ +#define HAVE_PTRACE_INTERCEPT +#include "exec_ptrace.c" + +static sig_atomic_t got_sigchld; +static int debug; +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, int *oldcwd, 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; + } + *oldcwd = -1; + + 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 (debug) { + case 0: + debug_return; + case 1: + file->debug_flags = (char *)"exec@diag"; + break; + case 2: + file->debug_flags = (char *)"exec@info"; + break; + default: + file->debug_flags = (char *)"exec@debug"; + break; + } + file->debug_file = (char *)"/dev/stderr"; + TAILQ_INSERT_HEAD(file_list, file, entries); + + debug_return; +} + +int +sudo_sigaction(int signo, struct sigaction *sa, struct sigaction *osa) +{ + return sigaction(signo, sa, osa); +} + +/* STUB */ +void +log_suspend(struct exec_closure *ec, int signo) +{ + 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 }; + const char *errstr; + sigset_t blocked, empty; + struct sigaction sa; + pid_t child, my_pid, pid, my_pgrp; + int ch, status; + debug_decl_vars(main, SUDO_DEBUG_MAIN); + + initprogname(argc > 0 ? argv[0] : "test_ptrace"); + + if (!have_seccomp_action("trap")) + sudo_fatalx("SECCOMP_MODE_FILTER not available in this kernel"); + + while ((ch = getopt(argc, argv, "d:")) != -1) { + switch (ch) { + case 'd': + debug = sudo_strtonum(optarg, 1, INT_MAX, &errstr); + if (errstr != NULL) + sudo_fatalx(U_("%s: %s"), optarg, U_(errstr)); + break; + default: + fprintf(stderr, "usage: %s [-d 1-3] [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 debug 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. */ + my_pid = getpid(); + my_pgrp = getpgrp(); + 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)) { + if (exec_ptrace_stopped(pid, status, &closure)) { + if (pid == child) { + suspend_sudo_nopty(NULL, WSTOPSIG(status), my_pid, + my_pgrp, child); + if (kill(child, SIGCONT) != 0) + sudo_warn("kill(%d, SIGCONT)", (int)child); + } + } + } else { + sudo_fatalx("%d: unknown status 0x%x", pid, status); + } + } + } +} diff --git a/src/regress/net_ifs/check_net_ifs.c b/src/regress/net_ifs/check_net_ifs.c new file mode 100644 index 0000000..f1551c4 --- /dev/null +++ b/src/regress/net_ifs/check_net_ifs.c @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: ISC + * + * 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 + * 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. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> + +#include "sudo_compat.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +extern int get_net_ifs(char **addrinfo); + +int +main(int argc, char *argv[]) +{ + int ch, ninterfaces, errors = 0, ntests = 1; + char *interfaces = NULL; + bool verbose = false; + + initprogname(argc > 0 ? argv[0] : "check_net_ifs"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + verbose = true; + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + + ninterfaces = get_net_ifs(&interfaces); + switch (ninterfaces) { + case -1: + printf("FAIL: unable to get network interfaces\n"); + errors++; + break; + case 0: + /* no interfaces or STUB_LOAD_INTERFACES defined. */ + if (verbose) + printf("OK: (0 interfaces)\n"); + break; + default: + if (verbose) { + printf("OK: (%d interface%s, %s)\n", ninterfaces, + ninterfaces > 1 ? "s" : "", interfaces); + } + break; + } + free(interfaces); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} diff --git a/src/regress/noexec/check_noexec.c b/src/regress/noexec/check_noexec.c new file mode 100644 index 0000000..bad25e0 --- /dev/null +++ b/src/regress/noexec/check_noexec.c @@ -0,0 +1,232 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2016, 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. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/wait.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#include <string.h> +#ifdef HAVE_WORDEXP_H +# include <wordexp.h> +#endif +#include <signal.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" +#include "sudo_queue.h" +#include "sudo_exec.h" + +static bool verbose; + +sudo_dso_public int main(int argc, char *argv[], char *envp[]); + +static bool +report_status(int status, const char *what) +{ + bool ret = false; + + /* system() returns -1 for exec failure. */ + if (status == -1) { + if (verbose) + printf("%s: OK (%s)\n", getprogname(), what); + return true; + } + + /* check exit value, expecting 127 for failure */ + if (WIFEXITED(status)) { + int exitval = WEXITSTATUS(status); + if (exitval == 127) { + if (verbose) + printf("%s: OK (%s)\n", getprogname(), what); + ret = true; + } else { + printf("%s: FAIL (%s) [%d]\n", getprogname(), what, exitval); + } + } else if (WIFSIGNALED(status)) { + printf("%s: FAIL (%s) [signal %d]\n", getprogname(), what, + WTERMSIG(status)); + } else { + /* should not happen */ + printf("%s: FAIL (%s) [status %d]\n", getprogname(), what, status); + } + + return ret; +} + +static int +try_execl(void) +{ + pid_t child, pid; + int status; + + child = fork(); + switch (child) { + case -1: + sudo_fatal_nodebug("fork"); + case 0: + /* child */ + /* Try to exec /bin/true, else exit with value 127. */ + execl("/bin/true", "true", (char *)0); + _exit(127); + default: + /* parent */ + do { + pid = waitpid(child, &status, 0); + } while (pid == -1 && errno == EINTR); + if (pid == -1) + sudo_fatal_nodebug("waitpid"); + + if (report_status(status, "execl")) + return 0; + return 1; + } +} + +static int +try_system(void) +{ + int status; + + /* Try to run /bin/true, system() returns 127 on exec failure. */ + status = system("/bin/true > /dev/null 2>&1"); + + if (report_status(status, "system")) + return 0; + return 1; +} + +#ifdef HAVE_WORDEXP_H +static int +try_wordexp(void) +{ + wordexp_t we; + int rc, ret = 1; + + /* + * sudo_noexec.so prevents command substitution via the WRDE_NOCMD flag + * where possible. + */ + rc = wordexp("$(/bin/echo foo)", &we, 0); + switch (rc) { + case -1: + /* sudo's wordexp() wrapper returns -1 if RTLD_NEXT is not supported. */ + case 127: + /* Solaris 10 wordexp() returns 127 for execve() failure. */ +#ifdef WRDE_ERRNO + case WRDE_ERRNO: + /* Solaris 11 wordexp() returns WRDE_ERRNO for execve() failure. */ +#endif + if (verbose) + printf("%s: OK (wordexp) [%d]\n", getprogname(), rc); + ret = 0; + break; + case WRDE_SYNTAX: + /* FreeBSD returns WRDE_SYNTAX if it can't write to the shell process */ + if (verbose) + printf("%s: OK (wordexp) [WRDE_SYNTAX]\n", getprogname()); + ret = 0; + break; + case WRDE_CMDSUB: + if (verbose) + printf("%s: OK (wordexp) [WRDE_CMDSUB]\n", getprogname()); + ret = 0; + break; + case 0: + /* + * On HP-UX 11.00 we don't seem to be able to add WRDE_NOCMD + * but the execve() wrapper prevents the command substitution. + */ + if (we.we_wordc == 0) { + if (verbose) + printf("%s: OK (wordexp) [%d]\n", getprogname(), rc); + wordfree(&we); + ret = 0; + break; + } + wordfree(&we); + FALLTHROUGH; + default: + printf("%s: FAIL (wordexp) [%d]\n", getprogname(), rc); + break; + } + return ret; +} +#endif + +sudo_noreturn static void +usage(void) +{ + fprintf(stderr, "usage: %s [-v] rexec | /path/to/sudo_noexec.so\n", + getprogname()); + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[], char *envp[]) +{ + int ch, errors = 0, ntests = 0; + + initprogname(argc > 0 ? argv[0] : "check_noexec"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + verbose = true; + break; + default: + usage(); + } + } + + if (argc - optind != 1) + usage(); + + /* Disable execution for post-exec and re-exec ourself. */ + if (strcmp(argv[optind], "rexec") != 0) { + const char *noexec = argv[optind]; + argv[optind] = (char *)"rexec"; + execve(argv[0], argv, disable_execute(envp, noexec)); + sudo_fatalx_nodebug("execve"); + } + + ntests++; + errors += try_execl(); + ntests++; + errors += try_system(); +#ifdef HAVE_WORDEXP_H + ntests++; + errors += try_wordexp(); +#endif + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} diff --git a/src/regress/ttyname/check_ttyname.c b/src/regress/ttyname/check_ttyname.c new file mode 100644 index 0000000..7efcd06 --- /dev/null +++ b/src/regress/ttyname/check_ttyname.c @@ -0,0 +1,122 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2013-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 + * 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. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" +#include "sudo_debug.h" + +sudo_dso_public int main(int argc, char *argv[]); + +int sudo_debug_instance = SUDO_DEBUG_INSTANCE_INITIALIZER; +extern char *get_process_ttyname(char *name, size_t namelen); + +static int +match_ttys(const char *tty1, const char *tty2) +{ + struct stat sb1, sb2; + + if (tty1 != NULL && tty2 != NULL) { + if (strcmp(tty1, tty2) == 0) + return 0; + /* Could be the same device with a different name. */ + if (stat(tty1, &sb1) == 0 && S_ISCHR(sb1.st_mode) && + stat(tty2, &sb2) == 0 && S_ISCHR(sb2.st_mode)) { + if (sb1.st_rdev == sb2.st_rdev) + return 0; + } + } else if (tty1 == NULL && tty2 == NULL) { + return 0; + } + + return 1; +} + +int +main(int argc, char *argv[]) +{ + char *tty_libc = NULL, *tty_sudo = NULL; + char pathbuf[PATH_MAX]; + bool verbose = false; + int ch, errors = 0, ntests = 1; + + initprogname(argc > 0 ? argv[0] : "check_ttyname"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + verbose = true; + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + + /* Lookup tty name using kernel info if possible. */ + if (get_process_ttyname(pathbuf, sizeof(pathbuf)) != NULL) + tty_sudo = pathbuf; + +#if defined(HAVE_KINFO_PROC2_NETBSD) || \ + defined(HAVE_KINFO_PROC_OPENBSD) || \ + defined(HAVE_KINFO_PROC_FREEBSD) || \ + defined(HAVE_KINFO_PROC_DFLY) || \ + defined(HAVE_KINFO_PROC_44BSD) || \ + defined(HAVE__TTYNAME_DEV) || defined(HAVE_STRUCT_PSINFO_PR_TTYDEV) || \ + defined(HAVE_PSTAT_GETPROC) || defined(__linux__) + + /* Lookup tty name attached to stdin via libc. */ + tty_libc = ttyname(STDIN_FILENO); +#endif + + /* Compare libc and kernel ttys. */ + if (match_ttys(tty_libc, tty_sudo) == 0) { + if (verbose) + printf("%s: OK (%s)\n", getprogname(), tty_sudo ? tty_sudo : "none"); + } else if (tty_libc == NULL) { + if (verbose) + printf("%s: SKIP (%s)\n", getprogname(), tty_sudo ? tty_sudo : "none"); + ntests = 0; + } else { + printf("%s: FAIL %s (sudo) vs. %s (libc)\n", getprogname(), + tty_sudo ? tty_sudo : "none", tty_libc ? tty_libc : "none"); + errors++; + } + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} |