summaryrefslogtreecommitdiffstats
path: root/sandbox/linux/seccomp/tests/test_syscalls.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox/linux/seccomp/tests/test_syscalls.cc')
-rw-r--r--sandbox/linux/seccomp/tests/test_syscalls.cc758
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;
- }
- }
-}