summaryrefslogtreecommitdiffstats
path: root/sandbox
diff options
context:
space:
mode:
authormarkus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-18 20:54:46 +0000
committermarkus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-18 20:54:46 +0000
commit8a93e3822b23897bde15cf0e2a7cdee5973fd5a2 (patch)
tree554c22cd6c5a41b159a82edceb853d405e806ab7 /sandbox
parent5f74571613bed65f1bc527289ddfd8fbaee2bc89 (diff)
downloadchromium_src-8a93e3822b23897bde15cf0e2a7cdee5973fd5a2.zip
chromium_src-8a93e3822b23897bde15cf0e2a7cdee5973fd5a2.tar.gz
chromium_src-8a93e3822b23897bde15cf0e2a7cdee5973fd5a2.tar.bz2
Add support for calling {rt_,}sigaction(), and for invoking signal handlers
from within the sandbox. Added tests for the new functionality and merged the tests for sigreturn() that had previously been committed to the standalone version of the sandbox (on Google Code) TEST=run "make test" BUG=37728 Review URL: http://codereview.chromium.org/2074003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47561 0039d316-1c4b-4281-b951-d872f2087c98
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',