summaryrefslogtreecommitdiffstats
path: root/sandbox
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox')
-rw-r--r--sandbox/linux/seccomp/Makefile5
-rw-r--r--sandbox/linux/seccomp/linux_syscall_support.h1
-rw-r--r--sandbox/linux/seccomp/sandbox.cc219
-rw-r--r--sandbox/linux/seccomp/sandbox_impl.h26
-rw-r--r--sandbox/linux/seccomp/securemem.h3
-rw-r--r--sandbox/linux/seccomp/sigaction.cc177
-rw-r--r--sandbox/linux/seccomp/syscall_table.c9
-rw-r--r--sandbox/linux/seccomp/tests/test_syscalls.cc248
-rw-r--r--sandbox/linux/seccomp/trusted_thread.cc2
-rw-r--r--sandbox/sandbox.gyp1
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',