diff options
Diffstat (limited to 'sandbox')
-rw-r--r-- | sandbox/linux/seccomp/Makefile | 5 | ||||
-rw-r--r-- | sandbox/linux/seccomp/linux_syscall_support.h | 1 | ||||
-rw-r--r-- | sandbox/linux/seccomp/sandbox.cc | 219 | ||||
-rw-r--r-- | sandbox/linux/seccomp/sandbox_impl.h | 26 | ||||
-rw-r--r-- | sandbox/linux/seccomp/securemem.h | 3 | ||||
-rw-r--r-- | sandbox/linux/seccomp/sigaction.cc | 177 | ||||
-rw-r--r-- | sandbox/linux/seccomp/syscall_table.c | 9 | ||||
-rw-r--r-- | sandbox/linux/seccomp/tests/test_syscalls.cc | 248 | ||||
-rw-r--r-- | sandbox/linux/seccomp/trusted_thread.cc | 2 | ||||
-rw-r--r-- | sandbox/sandbox.gyp | 1 |
10 files changed, 625 insertions, 66 deletions
diff --git a/sandbox/linux/seccomp/Makefile b/sandbox/linux/seccomp/Makefile index 5fde2d2..141d8c3 100644 --- a/sandbox/linux/seccomp/Makefile +++ b/sandbox/linux/seccomp/Makefile @@ -12,7 +12,7 @@ CPPFLAGS = MODS := allocator library debug maps x86_decode securemem sandbox \ syscall syscall_table trusted_thread trusted_process \ access exit clone getpid gettid ioctl ipc madvise mmap mprotect \ - munmap open sigprocmask socketcall stat + munmap open sigaction sigprocmask socketcall stat OBJS64 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o64/') OBJS32 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o32/') HEADERS:= $(shell for i in ${MODS}; do [ -r "$$i" ] && echo "$$i"; done) @@ -24,6 +24,9 @@ all: test clean: -rm -f *.o *.o32 *.o64 tests/*.o32 tests/*.o.64 -rm -f core core.* vgcore vgcore.* strace.log* + -rm -f run_tests_32 run_tests_64 + -rm -f tests/test_syscalls.o64 tests/test_syscalls.o32 + -rm -f tests/test-list.h test: run_tests_64 run_tests_32 ./run_tests_64 diff --git a/sandbox/linux/seccomp/linux_syscall_support.h b/sandbox/linux/seccomp/linux_syscall_support.h index a0422d4..2ee0426 100644 --- a/sandbox/linux/seccomp/linux_syscall_support.h +++ b/sandbox/linux/seccomp/linux_syscall_support.h @@ -66,6 +66,7 @@ extern "C" { #include <errno.h> #include <signal.h> #include <stdarg.h> +#include <stddef.h> #include <string.h> #include <sys/ptrace.h> #include <sys/resource.h> diff --git a/sandbox/linux/seccomp/sandbox.cc b/sandbox/linux/seccomp/sandbox.cc index 32480b3..0b09457 100644 --- a/sandbox/linux/seccomp/sandbox.cc +++ b/sandbox/linux/seccomp/sandbox.cc @@ -9,13 +9,14 @@ namespace playground { // Global variables -int Sandbox::proc_self_maps_ = -1; -enum Sandbox::SandboxStatus Sandbox::status_ = STATUS_UNKNOWN; -int Sandbox::pid_; -int Sandbox::processFdPub_; -int Sandbox::cloneFdPub_; -Sandbox::ProtectedMap Sandbox::protectedMap_; -std::vector<SecureMem::Args*> Sandbox::secureMemPool_; +int Sandbox::proc_self_maps_ = -1; +enum Sandbox::SandboxStatus Sandbox::status_ = STATUS_UNKNOWN; +int Sandbox::pid_; +int Sandbox::processFdPub_; +int Sandbox::cloneFdPub_; +Sandbox::SysCalls::kernel_sigaction Sandbox::sa_segv_; +Sandbox::ProtectedMap Sandbox::protectedMap_; +std::vector<SecureMem::Args*> Sandbox::secureMemPool_; bool Sandbox::sendFd(int transport, int fd0, int fd1, const void* buf, size_t len) { @@ -120,10 +121,11 @@ void Sandbox::setupSignalHandlers() { sys.sigaction(SIGCHLD, &sa, NULL); // Set up SEGV handler for dealing with RDTSC instructions, system calls - // that have been rewritten to use INT0, and for sigpending() emulation. + // that have been rewritten to use INT0, for sigprocmask() emulation, for + // the creation of threads, and for user-provided SEGV handlers. sa.sa_sigaction_ = segv(); - sa.sa_flags = SA_SIGINFO; - sys.sigaction(SIGSEGV, &sa, NULL); + sa.sa_flags = SA_SIGINFO | SA_NODEFER; + sys.sigaction(SIGSEGV, &sa, &sa_segv_); // Unblock SIGSEGV and SIGCHLD SysCalls::kernel_sigset_t mask; @@ -263,16 +265,77 @@ void (*Sandbox::segv())(int signo, SysCalls::siginfo *context, void *unused) { "lea playground$syscallWrapper(%%rip), %%rcx\n" "jmp *%%rcx\n" - // This was a genuine segmentation fault. Trigger the kernel's default - // signal disposition. The only way we can do this from seccomp mode - // is by blocking the signal and retriggering it. - "16:mov $2, %%edi\n" // stderr - "lea 300f(%%rip), %%rsi\n" // "Segmentation fault\n" - "mov $301f-300f, %%edx\n" - "mov $1, %%eax\n" // NR_write + // In order to implement SA_NODEFER, we have to keep track of recursive + // calls to SIGSEGV handlers. This means we have to increment a counter + // before calling the user's signal handler, and decrement it on + // leaving the user's signal handler. + // Some signal handlers look at the return address of the signal + // stack, and more importantly "gdb" uses the call to rt_sigreturn() + // as a magic signature when doing stacktraces. So, we have to use + // a little more unusual code to regain control after the user's + // signal handler is done. We adjust the return address to point to + // non-executable memory. And when we trigger another SEGV we pop the + // extraneous signal frame and then call rt_sigreturn(). + // N.B. We currently do not correctly adjust the SEGV counter, if the + // user's signal handler exits in way other than by returning (e.g. by + // directly calling rt_sigreturn(), or by calling siglongjmp()). + "16:lea 22f(%%rip), %%r14\n" + "cmp %%r14, %%r15\n" + "jnz 17f\n" // check if returning from user's handler + "decl %%gs:0x105C-0xE0\n" // decrement SEGV recursion counter + "mov 0xA8(%%rsp), %%rsp\n" // %rsp at time of segmentation fault + "mov $0xF, %%eax\n" // NR_rt_sigreturn "syscall\n" - "orb $4, 0x131(%%rsp)\n" // signal mask at time of segmentation fault + + // This was a genuine segmentation fault. Check Sandbox::sa_segv_ for + // what we are supposed to do. + "17:mov playground$sa_segv@GOTPCREL(%%rip), %%rax\n" + "cmp $0, 0(%%rax)\n" // SIG_DFL + "jz 18f\n" + "cmp $1, 0(%%rax)\n" // SIG_IGN + "jnz 19f\n" // can't really ignore synchronous signals + + // Trigger the kernel's default signal disposition. The only way we can + // do this from seccomp mode is by blocking the signal and retriggering + // it. + "18:orb $4, 0x131(%%rsp)\n" // signal mask at time of segmentation fault "ret\n" + + // Check sa_flags: + // - We can ignore SA_NOCLDSTOP, SA_NOCLDWAIT, and SA_RESTART as they + // do not have any effect for SIGSEGV. + // - On x86-64, we can also ignore SA_SIGINFO, as the calling + // conventions for sa_handler() are a subset of the conventions for + // sa_sigaction(). + // - We have to always register our signal handler with SA_NODEFER so + // that the user's signal handler can make system calls which might + // require additional help from our SEGV handler. + // - If the user's signal handler wasn't supposed to be SA_NODEFER, then + // we emulate this behavior by keeping track of a recursion counter. + // + // TODO(markus): If/when we add support for sigaltstack(), we have to + // handle SA_ONSTACK. + "19:cmpl $0, %%gs:0x105C-0xE0\n"// check if we failed inside of SEGV handler + "jnz 18b\n" // if so, then terminate program + "mov 0(%%rax), %%rbx\n" // sa_segv_.sa_sigaction + "mov 8(%%rax), %%rcx\n" // sa_segv_.sa_flags + "btl $31, %%ecx\n" // SA_RESETHAND + "jnc 20f\n" + "movq $0, 0(%%rax)\n" // set handler to SIG_DFL + "20:btl $30, %%ecx\n" // SA_NODEFER + "jc 21f\n" + "mov %%r14, 0(%%rsp)\n" // trigger a SEGV on return, so that we can + "incl %%gs:0x105C-0xE0\n" // clean up state; incr. recursion counter + "21:jmp *%%rbx\n" // call user's signal handler + + + // Non-executable version of the restorer function. We use this to + // trigger a SEGV upon returning from the user's signal handler, giving + // us an ability to clean up prior to returning from the SEGV handler. + ".pushsection .data\n" // move code into non-executable section + "22:mov $0xF, %%rax\n" // gdb looks for this signature when doing + "syscall\n" // backtraces + ".popsection\n" #elif defined(__i386__) // Inspect instruction at the point where the segmentation fault // happened. If it is RDTSC, forward the request to the trusted @@ -441,16 +504,116 @@ void (*Sandbox::segv())(int signo, SysCalls::siginfo *context, void *unused) { "19:call playground$syscallWrapper\n" "jmp 3b\n" - // This was a genuine segmentation fault. Trigger the kernel's default - // signal disposition. The only way we can do this from seccomp mode - // is by blocking the signal and retriggering it. - "20:mov $2, %%ebx\n" // stderr - "lea 300f, %%ecx\n" // "Segmentation fault\n" - "mov $301f-300f, %%edx\n" - "mov $4, %%eax\n" // NR_write + // In order to implement SA_NODEFER, we have to keep track of recursive + // calls to SIGSEGV handlers. This means we have to increment a counter + // before calling the user's signal handler, and decrement it on + // leaving the user's signal handler. + // Some signal handlers look at the return address of the signal + // stack, and more importantly "gdb" uses the call to {,rt_}sigreturn() + // as a magic signature when doing stacktraces. So, we have to use + // a little more unusual code to regain control after the user's + // signal handler is done. We adjust the return address to point to + // non-executable memory. And when we trigger another SEGV we pop the + // extraneous signal frame and then call sigreturn(). + // N.B. We currently do not correctly adjust the SEGV counter, if the + // user's signal handler exits in way other than by returning (e.g. by + // directly calling {,rt_}sigreturn(), or by calling siglongjmp()). + "20:lea 30f, %%edi\n" // rt-style restorer function + "lea 31f, %%esi\n" // legacy restorer function + "cmp %%ebp, %%edi\n" // check if returning from user's handler + "jnz 21f\n" + "decl %%fs:0x1040-0x58\n" // decrement SEGV recursion counter + "mov 0xC0(%%esp), %%esp\n" // %esp at time of segmentation fault + "jmp 29f\n" + "21:cmp %%ebp, %%esi\n" // check if returning from user's handler + "jnz 22f\n" + "decl %%fs:0x1040-0x58\n" // decrement SEGV recursion counter + "mov 0xC0(%%esp), %%esp\n" // %esp at time of segmentation fault + "jmp 6b\n" + + // This was a genuine segmentation fault. Check Sandbox::sa_segv_ for + // what we are supposed to do. + "22:lea playground$sa_segv, %%eax\n" + "cmp $0, 0(%%eax)\n" // SIG_DFL + "jz 23f\n" + "cmp $1, 0(%%eax)\n" // SIG_IGN + "jnz 24f\n" // can't really ignore synchronous signals + + // Trigger the kernel's default signal disposition. The only way we can + // do this from seccomp mode is by blocking the signal and retriggering + // it. + "23:orb $4, 0xFD(%%esp)\n" // signal mask at time of segmentation fault + "jmp 5b\n" + + // Check sa_flags: + // - We can ignore SA_NOCLDSTOP, SA_NOCLDWAIT, and SA_RESTART as they + // do not have any effect for SIGSEGV. + // - We have to always register our signal handler with SA_NODEFER so + // that the user's signal handler can make system calls which might + // require additional help from our SEGV handler. + // - If the user's signal handler wasn't supposed to be SA_NODEFER, then + // we emulate this behavior by keeping track of a recursion counter. + // + // TODO(markus): If/when we add support for sigaltstack(), we have to + // handle SA_ONSTACK. + "24:cmpl $0, %%fs:0x1040-0x58\n"// check if we failed inside of SEGV handler + "jnz 23b\n" // if so, then terminate program + "mov 0(%%eax), %%ebx\n" // sa_segv_.sa_sigaction + "mov 4(%%eax), %%ecx\n" // sa_segv_.sa_flags + "btl $31, %%ecx\n" // SA_RESETHAND + "jnc 25f\n" + "movl $0, 0(%%eax)\n" // set handler to SIG_DFL + "25:btl $30, %%ecx\n" // SA_NODEFER + "jc 28f\n" + "btl $2, %%ecx\n" // SA_SIGINFO + "jnc 26f\n" + "mov %%edi, 0(%%esp)\n" // trigger a SEGV on return + "incl %%fs:0x1040-0x58\n" // increment recursion counter + "jmp *%%ebx\n" // call user's signal handler + "26:mov %%esi, 0(%%esp)\n" + "incl %%fs:0x1040-0x58\n" // increment recursion counter + + // We always register the signal handler to give us rt-style signal + // frames. But if the user asked for legacy signal frames, we must + // convert the signal frame prior to calling the user's signal handler. + "27:sub $0x1C8, %%esp\n" // a legacy signal stack is much larger + "mov 0x1CC(%%esp), %%eax\n" // push signal number + "push %%eax\n" + "mov 0x1CC(%%esp), %%eax\n" // push restorer function + "push %%eax\n" + "lea 0x274(%%esp), %%esi\n" // copy siginfo register values + "lea 0x8(%%esp), %%edi\n" // into new location + "mov $22, %%ecx\n" + "cld\n" + "rep movsl\n" + "mov 0x2CC(%%esp), %%eax\n" // copy first half of signal mask + "mov %%eax, 0x58(%%esp)\n" + "lea 31f, %%esi\n" + "lea 0x2D4(%%esp), %%edi\n" // patch up retcode magic numbers + "movb $2, %%cl\n" + "rep movsl\n" + "jmp *%%ebx\n" // call user's signal handler + "28:lea 6b, %%eax\n" // set appropriate restorer function + "mov %%eax, 0(%%esp)\n" + "btl $2, %%ecx\n" // SA_SIGINFO + "jnc 27b\n" + "lea 29f, %%eax\n" + "mov %%eax, 0(%%esp)\n" // set appropriate restorer function + "jmp *%%ebx\n" // call user's signal handler + "29:pushl $30f\n" // emulate rt_sigreturn() + "jmp 5b\n" + + // Non-executable versions of the restorer function. We use these to + // trigger a SEGV upon returning from the user's signal handler, giving + // us an ability to clean up prior to returning from the SEGV handler. + ".pushsection .data\n" // move code into non-executable section + "30:mov $173, %%eax\n" // NR_rt_sigreturn + "int $0x80\n" // gdb looks for this signature when doing + ".byte 0\n" // backtraces + "31:pop %%eax\n" + "mov $119, %%eax\n" // NR_sigreturn "int $0x80\n" - "orb $4, 0xFD(%%esp)\n" // signal mask at time of segmentation fault - "jmp 4b\n" + ".popsection\n" #else #error Unsupported target platform #endif @@ -459,8 +622,6 @@ void (*Sandbox::segv())(int signo, SysCalls::siginfo *context, void *unused) { "100:.asciz \"RDTSC(P): Executing handler\\n\"\n" "200:.asciz \"INT $0x0: Executing handler\\n\"\n" #endif - "300:.ascii \"Segmentation fault\\n\"\n" - "301:\n" ".popsection\n" "999:pop %0\n" : "=g"(fnc) diff --git a/sandbox/linux/seccomp/sandbox_impl.h b/sandbox/linux/seccomp/sandbox_impl.h index 9c49ffc..ce2291c 100644 --- a/sandbox/linux/seccomp/sandbox_impl.h +++ b/sandbox/linux/seccomp/sandbox_impl.h @@ -139,6 +139,10 @@ class Sandbox { STATIC ssize_t sandbox_recvmsg(int, struct msghdr*, int) asm("playground$sandbox_recvmsg"); #endif + #if defined(__NR_rt_sigaction) + STATIC long sandbox_rt_sigaction(int, const void*, void*, size_t) + asm("playground$sandbox_rt_sigaction"); + #endif #if defined(__NR_rt_sigprocmask) STATIC long sandbox_rt_sigprocmask(int how, const void*, void*, size_t) asm("playground$sandbox_rt_sigprocmask"); @@ -162,6 +166,14 @@ class Sandbox { STATIC long sandbox_setsockopt(int, int, int, const void*, socklen_t) asm("playground$sandbox_setsockopt"); #endif + #if defined(__NR_sigaction) + STATIC long sandbox_sigaction(int, const void*, void*) + asm("playground$sandbox_sigaction"); + #endif + #if defined(__NR_signal) + STATIC void* sandbox_signal(int, const void*) + asm("playground$sandbox_signal"); + #endif #if defined(__NR_sigprocmask) STATIC long sandbox_sigprocmask(int how, const void*, void*) asm("playground$sandbox_sigprocmask"); @@ -226,6 +238,8 @@ class Sandbox { STATIC bool process_shmget(int, int, int, int, SecureMemArgs*) asm("playground$process_shmget"); #endif + STATIC bool process_sigaction(int, int, int, int, SecureMemArgs*) + asm("playground$process_sigaction"); #if defined(__NR_socketcall) STATIC bool process_socketcall(int, int, int, int, SecureMemArgs*) asm("playground$process_socketcall"); @@ -542,6 +556,14 @@ class Sandbox { int how; } __attribute__((packed)); + struct SigAction { + int sysnum; + int signum; + const SysCalls::kernel_sigaction* action; + const SysCalls::kernel_sigaction* old_action; + size_t sigsetsize; + } __attribute__((packed)); + struct Socket { int domain; int type; @@ -657,6 +679,10 @@ class Sandbox { static const struct SocketCallArgInfo socketCallArgInfo[]; #endif + // We always have to intercept SIGSEGV. If the application wants to set its + // own SEGV handler, we forward to it whenever necessary. + static SysCalls::kernel_sigaction sa_segv_ asm("playground$sa_segv"); + // The syscall_mutex_ can only be directly accessed by the trusted process. // It can be accessed by the trusted thread after fork()ing and calling // mprotect(PROT_READ|PROT_WRITE). The mutex is used for system calls that diff --git a/sandbox/linux/seccomp/securemem.h b/sandbox/linux/seccomp/securemem.h index dc035ff..91283db 100644 --- a/sandbox/linux/seccomp/securemem.h +++ b/sandbox/linux/seccomp/securemem.h @@ -120,6 +120,9 @@ class SecureMem { // Computing the signal mask is expensive. Keep a cached copy. kernel_sigset_t signalMask; + + // Keep track of whether we are in a SEGV handler + int inSegvHandler; } __attribute__((packed)); char scratchPage[4096]; }; diff --git a/sandbox/linux/seccomp/sigaction.cc b/sandbox/linux/seccomp/sigaction.cc new file mode 100644 index 0000000..162416d --- /dev/null +++ b/sandbox/linux/seccomp/sigaction.cc @@ -0,0 +1,177 @@ +// 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. + +// TODO(markus): We currently instrument the restorer functions with calls to +// the syscallWrapper(). This prevents gdb from properly +// creating backtraces of code that is running in signal +// handlers. We might instead want to always override the +// restorer with a function that contains the "magic" signature +// but that is not executable. The SEGV handler can detect this +// and then invoke the appropriate restorer. + +#include "debug.h" +#include "sandbox_impl.h" + +namespace playground { + +#if defined(__NR_sigaction) +long Sandbox::sandbox_sigaction(int signum, const void* a_, void* oa_) { + const SysCalls::kernel_old_sigaction* action = + reinterpret_cast<const SysCalls::kernel_old_sigaction*>(a_); + SysCalls::kernel_old_sigaction* old_action = + reinterpret_cast<SysCalls::kernel_old_sigaction*>(oa_); + + long rc = 0; + long long tm; + Debug::syscall(&tm, __NR_sigaction, "Executing handler"); + if (signum == SIGSEGV) { + if (old_action) { + old_action->sa_handler_ = sa_segv_.sa_handler_; + old_action->sa_mask = sa_segv_.sa_mask.sig[0]; + old_action->sa_flags = sa_segv_.sa_flags; + old_action->sa_restorer = sa_segv_.sa_restorer; + } + if (action) { + sa_segv_.sa_handler_ = action->sa_handler_; + sa_segv_.sa_mask.sig[0] = action->sa_mask; + sa_segv_.sa_flags = action->sa_flags; + sa_segv_.sa_restorer = action->sa_restorer; + } + } else { + struct { + int sysnum; + long long cookie; + SigAction sigaction_req; + } __attribute__((packed)) request; + request.sysnum = __NR_sigaction; + request.cookie = cookie(); + request.sigaction_req.sysnum = __NR_sigaction; + request.sigaction_req.signum = signum; + request.sigaction_req.action = + reinterpret_cast<const SysCalls::kernel_sigaction *>(action); + request.sigaction_req.old_action = + reinterpret_cast<const SysCalls::kernel_sigaction *>(old_action); + request.sigaction_req.sigsetsize = 8; + + SysCalls sys; + if (write(sys, processFdPub(), &request, sizeof(request)) != + sizeof(request) || + read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) { + die("Failed to forward sigaction() request [sandbox]"); + } + } + Debug::elapsed(tm, __NR_sigaction); + return rc; +} +#endif + +#if defined(__NR_rt_sigaction) +#define min(a,b) ({ typeof(a) a_=(a); typeof(b) b_=(b); a_ < b_ ? a_ : b_; }) +#define max(a,b) ({ typeof(a) a_=(a); typeof(b) b_=(b); a_ > b_ ? a_ : b_; }) + +long Sandbox::sandbox_rt_sigaction(int signum, const void* a_, void* oa_, + size_t sigsetsize) { + const SysCalls::kernel_sigaction* action = + reinterpret_cast<const SysCalls::kernel_sigaction*>(a_); + SysCalls::kernel_sigaction* old_action = + reinterpret_cast<SysCalls::kernel_sigaction*>(oa_); + + long rc = 0; + long long tm; + Debug::syscall(&tm, __NR_rt_sigaction, "Executing handler"); + if (signum == SIGSEGV) { + size_t theirSize = offsetof(SysCalls::kernel_sigaction, sa_mask) + + sigsetsize; + if (old_action) { + memcpy(old_action, &sa_segv_, min(sizeof(sa_segv_), theirSize)); + memset(old_action + 1, 0, max(0u, theirSize - sizeof(sa_segv_))); + } + if (action) { + memcpy(&sa_segv_, action, min(sizeof(sa_segv_), theirSize)); + memset(&sa_segv_.sa_mask, 0, max(0u, 8 - sigsetsize)); + } + } else { + struct { + int sysnum; + long long cookie; + SigAction sigaction_req; + } __attribute__((packed)) request; + request.sysnum = __NR_rt_sigaction; + request.cookie = cookie(); + request.sigaction_req.sysnum = __NR_rt_sigaction; + request.sigaction_req.signum = signum; + request.sigaction_req.action = action; + request.sigaction_req.old_action = old_action; + request.sigaction_req.sigsetsize = sigsetsize; + + SysCalls sys; + if (write(sys, processFdPub(), &request, sizeof(request)) != + sizeof(request) || + read(sys, threadFdPub(), &rc, sizeof(rc)) != sizeof(rc)) { + die("Failed to forward rt_sigaction() request [sandbox]"); + } + } + Debug::elapsed(tm, __NR_rt_sigaction); + return rc; +} +#endif + +#if defined(__NR_signal) +void* Sandbox::sandbox_signal(int signum, const void* handler) { + struct kernel_old_sigaction sa, osa; + sa.sa_handler_ = reinterpret_cast<void (*)(int)>(handler); + sa.sa_flags = SA_NODEFER | SA_RESETHAND | SA_RESTORER; + sa.sa_mask = 0; + asm volatile( + "lea 0f, %0\n" + "jmp 1f\n" + "0:pop %%eax\n" + "mov $119, %%eax\n" // __NR_sigreturn + "int $0x80\n" + "1:\n" + : "=r"(sa.sa_restorer)); + long rc = sandbox_sigaction(signum, &sa, &osa); + if (rc < 0) { + return (void *)rc; + } + return reinterpret_cast<void *>(osa.sa_handler_); +} +#endif + +bool Sandbox::process_sigaction(int parentMapsFd, int sandboxFd, + int threadFdPub, int threadFd, + SecureMem::Args* mem) { + // We need to intercept sigaction() in order to properly rewrite calls to + // sigaction(SEGV). While there is no security implication if we didn't do + // so, it would end up preventing the program from running correctly as the + // the sandbox's SEGV handler could accidentally get removed. All of this is + // done in sandbox_{,rt_}sigaction(). But we still bounce through the + // trusted process as that is the only way we can instrument system calls. + // This is somewhat needlessly complicated. But as sigaction() is not a + // performance critical system call, it is easier to do this way than to + // extend the format of the syscall_table so that it could deal with this + // special case. + + // Read request + SigAction sigaction_req; + SysCalls sys; + if (read(sys, sandboxFd, &sigaction_req, sizeof(sigaction_req)) != + sizeof(sigaction_req)) { + die("Failed to read parameters for sigaction() [process]"); + } + if (sigaction_req.signum == SIGSEGV) { + // This should never happen. Something went wrong when intercepting the + // system call. This is not a security problem, but it clearly doesn't + // make sense to let the system call pass. + SecureMem::abandonSystemCall(threadFd, -EINVAL); + return false; + } + SecureMem::sendSystemCall(threadFdPub, false, -1, mem, sigaction_req.sysnum, + sigaction_req.signum, sigaction_req.action, + sigaction_req.old_action, + sigaction_req.sigsetsize); + return true; +} + +} // namespace diff --git a/sandbox/linux/seccomp/syscall_table.c b/sandbox/linux/seccomp/syscall_table.c index 454ffa9..c9dd7a4 100644 --- a/sandbox/linux/seccomp/syscall_table.c +++ b/sandbox/linux/seccomp/syscall_table.c @@ -97,6 +97,9 @@ const struct SyscallTable syscallTable[] __attribute__(( [ __NR_recvfrom ] = { (void*)&sandbox_recvfrom, process_recvfrom }, [ __NR_recvmsg ] = { (void*)&sandbox_recvmsg, process_recvmsg }, #endif + #if defined(__NR_rt_sigaction) + [ __NR_rt_sigaction ] = { (void*)&sandbox_rt_sigaction,process_sigaction}, + #endif #if defined(__NR_rt_sigprocmask) [ __NR_rt_sigprocmask ] = { (void*)&sandbox_rt_sigprocmask, 0 }, #endif @@ -117,6 +120,12 @@ const struct SyscallTable syscallTable[] __attribute__(( #if defined(__NR_shutdown) [ __NR_shutdown ] = { UNRESTRICTED_SYSCALL, 0 }, #endif + #if defined(__NR_sigaction) + [ __NR_sigaction ] = { (void*)&sandbox_sigaction,process_sigaction }, + #endif + #if defined(__NR_signal) + [ __NR_signal ] = { (void*)&sandbox_signal, process_sigaction }, + #endif #if defined(__NR_sigprocmask) [ __NR_sigprocmask ] = { (void*)&sandbox_sigprocmask, 0 }, #endif diff --git a/sandbox/linux/seccomp/tests/test_syscalls.cc b/sandbox/linux/seccomp/tests/test_syscalls.cc index 37764be..e67f27f 100644 --- a/sandbox/linux/seccomp/tests/test_syscalls.cc +++ b/sandbox/linux/seccomp/tests/test_syscalls.cc @@ -11,16 +11,26 @@ #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) { +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 { + } else { + // This prints in cases where we run one test without forking printf("Intending to exit with status %i...\n", val); } } @@ -39,21 +49,23 @@ TEST(test_dup) { } 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); + intend_exit_status(SIGSEGV, true); asm("hlt"); } TEST(test_exit) { - intend_exit_status(123 << 8); + 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. -int count_fds() { +static int count_fds() { DIR *dir = opendir("/proc/self/fd"); assert(dir != NULL); int count = 0; @@ -68,10 +80,10 @@ int count_fds() { return count; } -void *thread_func(void *x) { +static void *thread_func(void *x) { int *ptr = (int *) x; *ptr = 123; - printf("In new thread\n"); + MSG("In new thread\n"); return (void *) 456; } @@ -82,7 +94,7 @@ TEST(test_thread) { int x = 999; void *result; pthread_create(&tid, NULL, thread_func, &x); - printf("Waiting for thread\n"); + MSG("Waiting for thread\n"); pthread_join(tid, &result); assert(result == (void *) 456); assert(x == 123); @@ -91,10 +103,10 @@ TEST(test_thread) { assert(fd_count2 == fd_count1); } -int clone_func(void *x) { +static int clone_func(void *x) { int *ptr = (int *) x; *ptr = 124; - printf("In thread\n"); + 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); @@ -103,14 +115,14 @@ int clone_func(void *x) { } #if defined(__i386__) -int get_gs() { +static int get_gs() { int gs; asm volatile("mov %%gs, %0" : "=r"(gs)); return gs; } #endif -void *get_tls_base() { +static void *get_tls_base() { void *base; #if defined(__x86_64__) asm volatile("mov %%fs:0, %0" : "=r"(base)); @@ -167,7 +179,7 @@ TEST(test_clone) { assert(fd_count2 == fd_count1); } -int uncalled_clone_func(void *x) { +static int uncalled_clone_func(void *x) { printf("In thread func, which shouldn't happen\n"); return 1; } @@ -187,10 +199,10 @@ TEST(test_clone_disallowed_flags) { assert(errno == EPERM); } -void *fp_thread(void *x) { +static void *fp_thread(void *x) { int val; asm("movss %%xmm0, %0" : "=m"(val)); - printf("val=%i\n", val); + MSG("val=%i\n", val); return NULL; } @@ -201,10 +213,10 @@ TEST(test_fp_regs) { pthread_t tid; pthread_create(&tid, NULL, fp_thread, NULL); pthread_join(tid, NULL); - printf("thread done OK\n"); + MSG("thread done OK\n"); } -long long read_tsc() { +static long long read_tsc() { long long rc; asm volatile( "rdtsc\n" @@ -240,7 +252,7 @@ TEST(test_gettid) { assert(tid1 == tid2); } -void *map_something() { +static void *map_something() { void *addr = mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); assert(addr != MAP_FAILED); @@ -298,7 +310,7 @@ TEST(test_mprotect_disallowed) { assert(errno == EINVAL); } -int get_tty_fd() { +static int get_tty_fd() { int master_fd, tty_fd; int rc = openpty(&master_fd, &tty_fd, NULL, NULL, NULL); assert(rc == 0); @@ -365,16 +377,83 @@ TEST(test_stat) { assert(errno == EACCES); } -int g_value; +static int g_value; -void signal_handler(int sig) { +static void signal_handler(int sig) { g_value = 300; - printf("In signal handler\n"); + MSG("In signal handler\n"); } -void sigaction_handler(int sig, siginfo_t *a, void *b) { +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; - printf("In sigaction handler\n"); + 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) { @@ -383,10 +462,8 @@ TEST(test_signal_handler) { StartSeccompSandbox(); - // signal() is not allowed inside the sandbox yet. result = signal(SIGTRAP, signal_handler); - assert(result == SIG_ERR); - assert(errno == ENOSYS); + assert(result != SIG_ERR); g_value = 200; asm("int3"); @@ -403,10 +480,8 @@ TEST(test_sigaction_handler) { StartSeccompSandbox(); - // sigaction() is not allowed inside the sandbox yet. rc = sigaction(SIGTRAP, &act, NULL); - assert(rc == -1); - assert(errno == ENOSYS); + assert(rc == 0); g_value = 200; asm("int3"); @@ -437,7 +512,7 @@ TEST(test_blocked_signal) { assert(sigismember(&sigs, SIGTRAP)); // Check that the signal handler really is blocked. - intend_exit_status(SIGTRAP); + intend_exit_status(SIGTRAP, true); asm("int3"); } @@ -455,6 +530,105 @@ TEST(test_sigaltstack) { 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; @@ -466,7 +640,7 @@ struct testcase all_tests[] = { { NULL, NULL }, }; -int run_test_forked(struct testcase *test) { +static int run_test_forked(struct testcase *test) { printf("** %s\n", test->test_name); int pipe_fds[2]; int rc = pipe(pipe_fds); @@ -478,7 +652,7 @@ int run_test_forked(struct testcase *test) { g_intended_status_fd = pipe_fds[1]; test->test_func(); - intend_exit_status(0); + intend_exit_status(0, false); _exit(0); } rc = close(pipe_fds[1]); @@ -498,7 +672,7 @@ int run_test_forked(struct testcase *test) { printf("Test returned exit status %i\n", status); return 1; } - else if (status != intended_status) { + else if ((status & ~WCOREFLAG) != intended_status) { printf("Test failed with exit status %i, expected %i\n", status, intended_status); return 1; @@ -508,7 +682,7 @@ int run_test_forked(struct testcase *test) { } } -int run_test_by_name(const char *name) { +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) { @@ -523,6 +697,8 @@ int run_test_by_name(const char *name) { } 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]); diff --git a/sandbox/linux/seccomp/trusted_thread.cc b/sandbox/linux/seccomp/trusted_thread.cc index 5819b0a..6d6a3f5 100644 --- a/sandbox/linux/seccomp/trusted_thread.cc +++ b/sandbox/linux/seccomp/trusted_thread.cc @@ -128,6 +128,7 @@ void Sandbox::createTrustedThread(int processFdPub, int cloneFdPub, // 0x4C: number of consecutive calls to a time fnc (not used on x86-64) // 0x50: nesting level of system calls (for debugging purposes only) // 0x54: signal mask + // 0x5C: in SEGV handler // We use the %fs register for accessing the secure read-only page, and // the untrusted scratch space immediately following it. The segment @@ -852,6 +853,7 @@ void Sandbox::createTrustedThread(int processFdPub, int cloneFdPub, // 0x30: number of consecutive calls to a time fnc. (e.g. gettimeofday) // 0x34: nesting level of system calls (for debugging purposes only) // 0x38: signal mask + // 0x40: in SEGV handler "0:xor %%esp, %%esp\n" "mov $2, %%eax\n" // %mm2 = initial sequence number diff --git a/sandbox/sandbox.gyp b/sandbox/sandbox.gyp index 5564fabc..3100509 100644 --- a/sandbox/sandbox.gyp +++ b/sandbox/sandbox.gyp @@ -190,6 +190,7 @@ 'linux/seccomp/sandbox_impl.h', 'linux/seccomp/securemem.cc', 'linux/seccomp/securemem.h', + 'linux/seccomp/sigaction.cc', 'linux/seccomp/sigprocmask.cc', 'linux/seccomp/socketcall.cc', 'linux/seccomp/stat.cc', |