diff options
Diffstat (limited to 'sandbox/linux/seccomp/tests/test_syscalls.cc')
-rw-r--r-- | sandbox/linux/seccomp/tests/test_syscalls.cc | 758 |
1 files changed, 0 insertions, 758 deletions
diff --git a/sandbox/linux/seccomp/tests/test_syscalls.cc b/sandbox/linux/seccomp/tests/test_syscalls.cc deleted file mode 100644 index 3e6acd5..0000000 --- a/sandbox/linux/seccomp/tests/test_syscalls.cc +++ /dev/null @@ -1,758 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <assert.h> -#include <dirent.h> -#include <pthread.h> -#include <pty.h> -#include <sys/types.h> -#include <sys/wait.h> - -#include "sandbox_impl.h" - -#ifdef DEBUG -#define MSG(fmt, ...) printf(fmt, ##__VA_ARGS__) -#else -#define MSG(fmt, ...) do { } while (0) -#endif - -int g_intended_status_fd = -1; - -// Declares the wait() status that the test subprocess intends to exit with. -void intend_exit_status(int val, bool is_signal) { - if (is_signal) { - val = W_EXITCODE(0, val); - } else { - val = W_EXITCODE(val, 0); - } - if (g_intended_status_fd != -1) { - int sent = write(g_intended_status_fd, &val, sizeof(val)); - assert(sent == sizeof(val)); - } else { - // This prints in cases where we run one test without forking - printf("Intending to exit with status %i...\n", val); - } -} - - -// This is basically a marker to grep for. -#define TEST(name) void name() - -TEST(test_dup) { - StartSeccompSandbox(); - // Test a simple syscall that is marked as UNRESTRICTED_SYSCALL. - int fd = dup(1); - assert(fd >= 0); - int rc = close(fd); - assert(rc == 0); -} - -TEST(test_segfault) { - StartSeccompSandbox(); - // Check that the sandbox's SIGSEGV handler does not stop the - // process from dying cleanly in the event of a real segfault. - intend_exit_status(SIGSEGV, true); - asm("hlt"); -} - -TEST(test_exit) { - StartSeccompSandbox(); - intend_exit_status(123, false); - _exit(123); -} - -// This has an off-by-three error because it counts ".", "..", and the -// FD for the /proc/self/fd directory. This doesn't matter because it -// is only used to check for differences in the number of open FDs. -static int count_fds() { - DIR *dir = opendir("/proc/self/fd"); - assert(dir != NULL); - int count = 0; - while (1) { - struct dirent *d = readdir(dir); - if (d == NULL) - break; - count++; - } - int rc = closedir(dir); - assert(rc == 0); - return count; -} - -static void *thread_func(void *x) { - int *ptr = (int *) x; - *ptr = 123; - MSG("In new thread\n"); - return (void *) 456; -} - -TEST(test_thread) { - playground::g_policy.allow_file_namespace = true; // To allow count_fds() - StartSeccompSandbox(); - int fd_count1 = count_fds(); - pthread_t tid; - int x = 999; - void *result; - pthread_create(&tid, NULL, thread_func, &x); - MSG("Waiting for thread\n"); - pthread_join(tid, &result); - assert(result == (void *) 456); - assert(x == 123); - // Check that the process has not leaked FDs. - int fd_count2 = count_fds(); - assert(fd_count2 == fd_count1); -} - -static int clone_func(void *x) { - int *ptr = (int *) x; - *ptr = 124; - MSG("In thread\n"); - // On x86-64, returning from this function calls the __NR_exit_group - // syscall instead of __NR_exit. - syscall(__NR_exit, 100); - // Not reached. - return 200; -} - -#if defined(__i386__) -static int get_gs() { - int gs; - asm volatile("mov %%gs, %0" : "=r"(gs)); - return gs; -} -#endif - -static void *get_tls_base() { - void *base; -#if defined(__x86_64__) - asm volatile("mov %%fs:0, %0" : "=r"(base)); -#elif defined(__i386__) - asm volatile("mov %%gs:0, %0" : "=r"(base)); -#else -#error Unsupported target platform -#endif - return base; -} - -TEST(test_clone) { - playground::g_policy.allow_file_namespace = true; // To allow count_fds() - StartSeccompSandbox(); - int fd_count1 = count_fds(); - int stack_size = 0x1000; - char *stack = (char *) malloc(stack_size); - assert(stack != NULL); - int flags = CLONE_VM | CLONE_FS | CLONE_FILES | - CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | - CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; - int tid = -1; - int x = 999; - - // The sandbox requires us to pass CLONE_TLS. Pass settings that - // are enough to copy the parent thread's TLS setup. This allows us - // to invoke libc in the child thread. -#if defined(__x86_64__) - void *tls = get_tls_base(); -#elif defined(__i386__) - struct user_desc tls_desc, *tls = &tls_desc; - tls_desc.entry_number = get_gs() >> 3; - tls_desc.base_addr = (long) get_tls_base(); - tls_desc.limit = 0xfffff; - tls_desc.seg_32bit = 1; - tls_desc.contents = 0; - tls_desc.read_exec_only = 0; - tls_desc.limit_in_pages = 1; - tls_desc.seg_not_present = 0; - tls_desc.useable = 1; -#else -#error Unsupported target platform -#endif - - int rc = clone(clone_func, (void *) (stack + stack_size), flags, &x, - &tid, tls, &tid); - assert(rc > 0); - while (tid == rc) { - syscall(__NR_futex, &tid, FUTEX_WAIT, rc, NULL); - } - assert(tid == 0); - assert(x == 124); - // Check that the process has not leaked FDs. - int fd_count2 = count_fds(); - assert(fd_count2 == fd_count1); -} - -static int uncalled_clone_func(void *x) { - printf("In thread func, which shouldn't happen\n"); - return 1; -} - -TEST(test_clone_disallowed_flags) { - StartSeccompSandbox(); - int stack_size = 4096; - char *stack = (char *) malloc(stack_size); - assert(stack != NULL); - /* We omit the flags CLONE_SETTLS, CLONE_PARENT_SETTID and - CLONE_CHILD_CLEARTID, which is disallowed by the sandbox. */ - int flags = CLONE_VM | CLONE_FS | CLONE_FILES | - CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM; - int rc = clone(uncalled_clone_func, (void *) (stack + stack_size), - flags, NULL, NULL, NULL, NULL); - assert(rc == -1); - assert(errno == EPERM); -} - -static void *fp_thread(void *x) { - int val; - asm("movss %%xmm0, %0" : "=m"(val)); - MSG("val=%i\n", val); - return NULL; -} - -TEST(test_fp_regs) { - StartSeccompSandbox(); - int val = 1234; - asm("movss %0, %%xmm0" : "=m"(val)); - pthread_t tid; - pthread_create(&tid, NULL, fp_thread, NULL); - pthread_join(tid, NULL); - MSG("thread done OK\n"); -} - -static long long read_tsc() { - long long rc; - asm volatile( - "rdtsc\n" - "mov %%eax, (%0)\n" - "mov %%edx, 4(%0)\n" - : - : "c"(&rc), "a"(-1), "d"(-1)); - return rc; -} - -TEST(test_rdtsc) { - StartSeccompSandbox(); - // Just check that we can do the instruction. - read_tsc(); -} - -TEST(test_getpid) { - int pid1 = getpid(); - StartSeccompSandbox(); - int pid2 = getpid(); - assert(pid1 == pid2); - // Bypass any caching that glibc's getpid() wrapper might do. - int pid3 = syscall(__NR_getpid); - assert(pid1 == pid3); -} - -TEST(test_gettid) { - // glibc doesn't provide a gettid() wrapper. - int tid1 = syscall(__NR_gettid); - assert(tid1 > 0); - StartSeccompSandbox(); - int tid2 = syscall(__NR_gettid); - assert(tid1 == tid2); -} - -static void *map_something() { - void *addr = mmap(NULL, 0x1000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - assert(addr != MAP_FAILED); - return addr; -} - -TEST(test_mmap_disallows_remapping) { - void *addr = map_something(); - StartSeccompSandbox(); - // Overwriting a mapping that was created before the sandbox was - // enabled is not allowed. - void *result = mmap(addr, 0x1000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); - assert(result == MAP_FAILED); - assert(errno == EINVAL); -} - -TEST(test_mmap_disallows_low_address) { - StartSeccompSandbox(); - // Mapping pages at low addresses is not allowed because this helps - // with exploiting buggy kernels. - void *result = mmap(NULL, 0x1000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); - assert(result == MAP_FAILED); - assert(errno == EINVAL); -} - -TEST(test_munmap_allowed) { - StartSeccompSandbox(); - void *addr = map_something(); - int result = munmap(addr, 0x1000); - assert(result == 0); -} - -TEST(test_munmap_disallowed) { - void *addr = map_something(); - StartSeccompSandbox(); - int result = munmap(addr, 0x1000); - assert(result == -1); - assert(errno == EINVAL); -} - -TEST(test_mprotect_allowed) { - StartSeccompSandbox(); - void *addr = map_something(); - int result = mprotect(addr, 0x1000, PROT_READ | PROT_WRITE); - assert(result == 0); -} - -TEST(test_mprotect_disallowed) { - void *addr = map_something(); - StartSeccompSandbox(); - int result = mprotect(addr, 0x1000, PROT_READ | PROT_WRITE); - assert(result == -1); - assert(errno == EINVAL); -} - -static int get_tty_fd() { - int master_fd, tty_fd; - int rc = openpty(&master_fd, &tty_fd, NULL, NULL, NULL); - assert(rc == 0); - return tty_fd; -} - -TEST(test_ioctl_tiocgwinsz_allowed) { - int tty_fd = get_tty_fd(); - StartSeccompSandbox(); - int size[2]; - // Get terminal width and height. - int result = ioctl(tty_fd, TIOCGWINSZ, size); - assert(result == 0); -} - -TEST(test_ioctl_disallowed) { - int tty_fd = get_tty_fd(); - StartSeccompSandbox(); - // This ioctl call inserts a character into the tty's input queue, - // which provides a way to send commands to an interactive shell. - char c = 'x'; - int result = ioctl(tty_fd, TIOCSTI, &c); - assert(result == -1); - assert(errno == EINVAL); -} - -TEST(test_socket) { - StartSeccompSandbox(); - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - assert(fd == -1); - // TODO: Make it consistent between i386 and x86-64. - assert(errno == EINVAL || errno == ENOSYS); -} - -TEST(test_open_disabled) { - StartSeccompSandbox(); - int fd = open("/dev/null", O_RDONLY); - assert(fd == -1); - assert(errno == EACCES); - - // Writing to the policy flag does not change this. - playground::g_policy.allow_file_namespace = true; - fd = open("/dev/null", O_RDONLY); - assert(fd == -1); - assert(errno == EACCES); -} - -TEST(test_open_enabled) { - playground::g_policy.allow_file_namespace = true; - StartSeccompSandbox(); - int fd = open("/dev/null", O_RDONLY); - assert(fd >= 0); - int rc = close(fd); - assert(rc == 0); - fd = open("/dev/null", O_WRONLY); - assert(fd == -1); - assert(errno == EACCES); -} - -TEST(test_access_disabled) { - StartSeccompSandbox(); - int rc = access("/dev/null", R_OK); - assert(rc == -1); - assert(errno == EACCES); -} - -TEST(test_access_enabled) { - playground::g_policy.allow_file_namespace = true; - StartSeccompSandbox(); - int rc = access("/dev/null", R_OK); - assert(rc == 0); - rc = access("path-that-does-not-exist", R_OK); - assert(rc == -1); - assert(errno == ENOENT); -} - -TEST(test_stat_disabled) { - StartSeccompSandbox(); - struct stat st; - int rc = stat("/dev/null", &st); - assert(rc == -1); - assert(errno == EACCES); -} - -TEST(test_stat_enabled) { - playground::g_policy.allow_file_namespace = true; - StartSeccompSandbox(); - struct stat st; - int rc = stat("/dev/null", &st); - assert(rc == 0); - rc = stat("path-that-does-not-exist", &st); - assert(rc == -1); - assert(errno == ENOENT); -} - -static int g_value; - -static void signal_handler(int sig) { - g_value = 300; - MSG("In signal handler\n"); -} - -static void sigaction_handler(int sig, siginfo_t *a, void *b) { - g_value = 300; - MSG("In sigaction handler\n"); -} - -static void (*g_sig_handler_ptr)(int sig, void *addr) asm("g_sig_handler_ptr"); - -static void non_fatal_sig_handler(int sig, void *addr) { - g_value = 300; - MSG("Caught signal %d at %p\n", sig, addr); -} - -static void fatal_sig_handler(int sig, void *addr) { - // Recursively trigger another segmentation fault while already in the SEGV - // handler. This should terminate the program if SIGSEGV is marked as a - // deferred signal. - // Only do this on the first entry to this function. Otherwise, the signal - // handler was probably marked as SA_NODEFER and we want to continue - // execution. - if (!g_value++) { - MSG("Caught signal %d at %p\n", sig, addr); - if (sig == SIGSEGV) { - asm volatile("hlt"); - } else { - asm volatile("int3"); - } - } -} - -static void (*generic_signal_handler(void)) - (int signo, siginfo_t *info, void *context) { - void (*hdl)(int, siginfo_t *, void *); - asm volatile( - "lea 0f, %0\n" - "jmp 999f\n" - "0:\n" - -#if defined(__x86_64__) - "mov 0xB0(%%rsp), %%rsi\n" // Pass original %rip to signal handler - "cmpb $0xF4, 0(%%rsi)\n" // hlt - "jnz 1f\n" - "addq $1, 0xB0(%%rsp)\n" // Adjust %eip past failing instruction - "1:jmp *g_sig_handler_ptr\n" // Call actual signal handler -#elif defined(__i386__) - // TODO(markus): We currently don't guarantee that signal handlers always - // have the correct "magic" restorer function. If we fix - // this, we should add a test for it (both for SEGV and - // non-SEGV). - "cmpw $0, 0xA(%%esp)\n" - "lea 0x40(%%esp), %%eax\n" // %eip at time of exception - "jz 1f\n" - "add $0x9C, %%eax\n" // %eip at time of exception - "1:mov 0(%%eax), %%ecx\n" - "cmpb $0xF4, 0(%%ecx)\n" // hlt - "jnz 2f\n" - "addl $1, 0(%%eax)\n" // Adjust %eip past failing instruction - "2:push %%ecx\n" // Pass original %eip to signal handler - "mov 8(%%esp), %%eax\n" - "push %%eax\n" // Pass signal number to signal handler - "call *g_sig_handler_ptr\n" // Call actual signal handler - "pop %%eax\n" - "pop %%ecx\n" - "ret\n" -#else -#error Unsupported target platform -#endif - -"999:\n" - : "=r"(hdl)); - return hdl; -} - -TEST(test_signal_handler) { - sighandler_t result = signal(SIGTRAP, signal_handler); - assert(result != SIG_ERR); - - StartSeccompSandbox(); - - result = signal(SIGTRAP, signal_handler); - assert(result != SIG_ERR); - - g_value = 200; - asm("int3"); - assert(g_value == 300); -} - -TEST(test_sigaction_handler) { - struct sigaction act; - act.sa_sigaction = sigaction_handler; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_SIGINFO; - int rc = sigaction(SIGTRAP, &act, NULL); - assert(rc == 0); - - StartSeccompSandbox(); - - rc = sigaction(SIGTRAP, &act, NULL); - assert(rc == 0); - - g_value = 200; - asm("int3"); - assert(g_value == 300); -} - -TEST(test_blocked_signal) { - sighandler_t result = signal(SIGTRAP, signal_handler); - assert(result != SIG_ERR); - StartSeccompSandbox(); - - // Initially the signal should not be blocked. - sigset_t sigs; - sigfillset(&sigs); - int rc = sigprocmask(0, NULL, &sigs); - assert(rc == 0); - assert(!sigismember(&sigs, SIGTRAP)); - - sigemptyset(&sigs); - sigaddset(&sigs, SIGTRAP); - rc = sigprocmask(SIG_BLOCK, &sigs, NULL); - assert(rc == 0); - - // Check that we can read back the blocked status. - sigemptyset(&sigs); - rc = sigprocmask(0, NULL, &sigs); - assert(rc == 0); - assert(sigismember(&sigs, SIGTRAP)); - - // Check that the signal handler really is blocked. - intend_exit_status(SIGTRAP, true); - asm("int3"); -} - -TEST(test_sigaltstack) { - // The sandbox does not support sigaltstack() yet. Just test that - // it returns an error. - StartSeccompSandbox(); - stack_t st; - st.ss_size = 0x4000; - st.ss_sp = malloc(st.ss_size); - assert(st.ss_sp != NULL); - st.ss_flags = 0; - int rc = sigaltstack(&st, NULL); - assert(rc == -1); - assert(errno == ENOSYS); -} - -TEST(test_sa_flags) { - StartSeccompSandbox(); - int flags[4] = { 0, SA_NODEFER, SA_SIGINFO, SA_SIGINFO | SA_NODEFER }; - for (int i = 0; i < 4; ++i) { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = generic_signal_handler(); - g_sig_handler_ptr = non_fatal_sig_handler; - sa.sa_flags = flags[i]; - - // Test SEGV handling - g_value = 200; - sigaction(SIGSEGV, &sa, NULL); - asm volatile("hlt"); - assert(g_value == 300); - - // Test non-SEGV handling - g_value = 200; - sigaction(SIGTRAP, &sa, NULL); - asm volatile("int3"); - assert(g_value == 300); - } -} - -TEST(test_segv_defer) { - StartSeccompSandbox(); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = generic_signal_handler(); - g_sig_handler_ptr = fatal_sig_handler; - - // Test non-deferred SEGV (should continue execution) - sa.sa_flags = SA_NODEFER; - sigaction(SIGSEGV, &sa, NULL); - g_value = 0; - asm volatile("hlt"); - - // Test deferred SEGV (should terminate program) - sa.sa_flags = 0; - sigaction(SIGSEGV, &sa, NULL); - g_value = 0; - intend_exit_status(SIGSEGV, true); - asm volatile("hlt"); -} - -TEST(test_trap_defer) { - StartSeccompSandbox(); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = generic_signal_handler(); - g_sig_handler_ptr = fatal_sig_handler; - - // Test non-deferred TRAP (should continue execution) - sa.sa_flags = SA_NODEFER; - sigaction(SIGTRAP, &sa, NULL); - g_value = 0; - asm volatile("int3"); - - // Test deferred TRAP (should terminate program) - sa.sa_flags = 0; - sigaction(SIGTRAP, &sa, NULL); - g_value = 0; - intend_exit_status(SIGTRAP, true); - asm volatile("int3"); -} - -TEST(test_segv_resethand) { - StartSeccompSandbox(); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = generic_signal_handler(); - g_sig_handler_ptr = non_fatal_sig_handler; - sa.sa_flags = SA_RESETHAND; - sigaction(SIGSEGV, &sa, NULL); - - // Test first invocation of signal handler (should continue execution) - asm volatile("hlt"); - - // Test second invocation of signal handler (should terminate program) - intend_exit_status(SIGSEGV, true); - asm volatile("hlt"); -} - -TEST(test_trap_resethand) { - StartSeccompSandbox(); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = generic_signal_handler(); - g_sig_handler_ptr = non_fatal_sig_handler; - sa.sa_flags = SA_RESETHAND; - sigaction(SIGTRAP, &sa, NULL); - - // Test first invocation of signal handler (should continue execution) - asm volatile("int3"); - - // Test second invocation of signal handler (should terminate program) - intend_exit_status(SIGTRAP, true); - asm volatile("int3"); -} - -struct testcase { - const char *test_name; - void (*test_func)(); -}; - -struct testcase all_tests[] = { -#include "test-list.h" - { NULL, NULL }, -}; - -static int run_test_forked(struct testcase *test) { - printf("** %s\n", test->test_name); - int pipe_fds[2]; - int rc = pipe(pipe_fds); - assert(rc == 0); - int pid = fork(); - if (pid == 0) { - rc = close(pipe_fds[0]); - assert(rc == 0); - g_intended_status_fd = pipe_fds[1]; - - test->test_func(); - intend_exit_status(0, false); - _exit(0); - } - rc = close(pipe_fds[1]); - assert(rc == 0); - - int intended_status; - int got = read(pipe_fds[0], &intended_status, sizeof(intended_status)); - bool got_intended_status = got == sizeof(intended_status); - if (!got_intended_status) { - printf("Test runner: Did not receive intended status\n"); - } - - int status; - int pid2 = waitpid(pid, &status, 0); - assert(pid2 == pid); - if (!got_intended_status) { - printf("Test returned exit status %i\n", status); - return 1; - } - else if ((status & ~WCOREFLAG) != intended_status) { - printf("Test failed with exit status %i, expected %i\n", - status, intended_status); - return 1; - } - else { - return 0; - } -} - -static int run_test_by_name(const char *name) { - struct testcase *test; - for (test = all_tests; test->test_name != NULL; test++) { - if (strcmp(name, test->test_name) == 0) { - printf("Running test %s...\n", name); - test->test_func(); - printf("OK\n"); - return 0; - } - } - fprintf(stderr, "Test '%s' not found\n", name); - return 1; -} - -int main(int argc, char **argv) { - setvbuf(stdout, NULL, _IONBF, 0); - setvbuf(stderr, NULL, _IONBF, 0); - if (argc == 2) { - // Run one test without forking, to aid debugging. - return run_test_by_name(argv[1]); - } - else if (argc > 2) { - // TODO: run multiple tests. - fprintf(stderr, "Too many arguments\n"); - return 1; - } - else { - // Run all tests. - struct testcase *test; - int failures = 0; - for (test = all_tests; test->test_name != NULL; test++) { - failures += run_test_forked(test); - } - if (failures == 0) { - printf("OK\n"); - return 0; - } - else { - printf("%i FAILURE(S)\n", failures); - return 1; - } - } -} |