summaryrefslogtreecommitdiffstats
path: root/sandbox
diff options
context:
space:
mode:
authormarkus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-04 22:36:14 +0000
committermarkus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-04 22:36:14 +0000
commita443435bd5c482361da9e88486d3f2f20ffd8fed (patch)
tree49b77388b26efe2ecc6cdd275f841b38a1f7e8bd /sandbox
parent40c9e6d376323eb9da3bd965e8f22772eafb196a (diff)
downloadchromium_src-a443435bd5c482361da9e88486d3f2f20ffd8fed.zip
chromium_src-a443435bd5c482361da9e88486d3f2f20ffd8fed.tar.gz
chromium_src-a443435bd5c482361da9e88486d3f2f20ffd8fed.tar.bz2
Initial snapshot of the new BPF-enabled seccomp sandbox. This code is
still quite incomplete. In fact, it barely even compiles. You can use the Makefile to experiment with it, but we deliberately have not integrated it with the Chrome build system at this time. The main intention for checking in the code at this point is to give others a chance to take a look at the API. We made a few changes already, and I want to make sure I give everybody an opportunity to speak up, if they still want further revisions of the publicly exposed API. BUG=130662 TEST=build with Makefile, then run demo32 and demo64 Review URL: https://chromiumcodereview.appspot.com/10458040 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@140407 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox')
-rw-r--r--sandbox/linux/seccomp-bpf/Makefile31
-rw-r--r--sandbox/linux/seccomp-bpf/demo.cc312
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.cc319
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.h244
-rw-r--r--sandbox/linux/seccomp-bpf/util.cc162
-rw-r--r--sandbox/linux/seccomp-bpf/util.h19
6 files changed, 1087 insertions, 0 deletions
diff --git a/sandbox/linux/seccomp-bpf/Makefile b/sandbox/linux/seccomp-bpf/Makefile
new file mode 100644
index 0000000..222af2a
--- /dev/null
+++ b/sandbox/linux/seccomp-bpf/Makefile
@@ -0,0 +1,31 @@
+CFLAGS = -g -O3 -Wall -Werror -Wextra -Wno-missing-field-initializers \
+ -Wno-unused-parameter -Wno-unused-value -Wno-array-bounds -fPIC -I.
+CPPFLAGS = -D_GNU_SOURCE -DSECCOMP_BPF_STANDALONE -iquote ../../..
+LDFLAGS = -g -lpthread
+DEPFLAGS = -MMD -MF .$@.d
+MODS := demo sandbox_bpf util
+OBJS64 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o64/')
+OBJS32 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o32/')
+ALL_OBJS = $(OBJS32) $(OBJS64)
+DEP_FILES = $(wildcard $(foreach f,$(ALL_OBJS),.$(f).d))
+
+.SUFFIXES: .o64 .o32
+
+all: demo32 demo64
+
+clean:
+ $(RM) demo32 demo64
+ $(RM) *.o *.o32 *.o64 .*.d
+ $(RM) core core.* vgcore vgcore.* strace.log*
+
+-include $(DEP_FILES)
+
+demo32: ${OBJS32}
+ ${CXX} -m32 -o $@ $+ ${LDFLAGS}
+demo64: ${OBJS64}
+ ${CXX} -m64 -o $@ $+ ${LDFLAGS}
+
+.cc.o32:
+ ${CXX} -m32 ${CFLAGS} ${CPPFLAGS} ${DEPFLAGS} -c -o $@ $<
+.cc.o64:
+ ${CXX} -m64 ${CFLAGS} ${CPPFLAGS} ${DEPFLAGS} -c -o $@ $<
diff --git a/sandbox/linux/seccomp-bpf/demo.cc b/sandbox/linux/seccomp-bpf/demo.cc
new file mode 100644
index 0000000..5fb0d1ac
--- /dev/null
+++ b/sandbox/linux/seccomp-bpf/demo.cc
@@ -0,0 +1,312 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/unistd.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/ipc.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/shm.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "sandbox/linux/seccomp_bpf/sandbox_bpf.h"
+#include "sandbox/linux/seccomp_bpf/util.h"
+
+#define ERR EPERM
+
+// We don't expect our sandbox to do anything useful yet. So, we will fail
+// almost immediately. For now, force the code to continue running. The
+// following line should be removed as soon as the sandbox is starting to
+// actually enforce restrictions in a meaningful way:
+#define _exit(x) do { } while (0)
+
+static playground2::Sandbox::ErrorCode evaluator(int sysno) {
+ switch (sysno) {
+ #if defined(__NR_accept)
+ case __NR_accept: case __NR_accept4:
+#endif
+ case __NR_alarm:
+ case __NR_brk:
+ case __NR_clock_gettime:
+ case __NR_close:
+ case __NR_dup: case __NR_dup2:
+ case __NR_epoll_create: case __NR_epoll_ctl: case __NR_epoll_wait:
+ case __NR_exit: case __NR_exit_group:
+ case __NR_fcntl:
+#if defined(__NR_fcntl64)
+ case __NR_fcntl64:
+#endif
+ case __NR_fdatasync:
+ case __NR_fstat:
+#if defined(__NR_fstat64)
+ case __NR_fstat64:
+#endif
+ case __NR_ftruncate:
+ case __NR_futex:
+ case __NR_getdents: case __NR_getdents64:
+ case __NR_getegid:
+#if defined(__NR_getegid32)
+ case __NR_getegid32:
+#endif
+ case __NR_geteuid:
+#if defined(__NR_geteuid32)
+ case __NR_geteuid32:
+#endif
+ case __NR_getgid:
+#if defined(__NR_getgid32)
+ case __NR_getgid32:
+#endif
+ case __NR_getitimer: case __NR_setitimer:
+#if defined(__NR_getpeername)
+ case __NR_getpeername:
+#endif
+ case __NR_getpid: case __NR_gettid:
+#if defined(__NR_getsockname)
+ case __NR_getsockname:
+#endif
+ case __NR_gettimeofday:
+ case __NR_getuid:
+#if defined(__NR_getuid32)
+ case __NR_getuid32:
+#endif
+#if defined(__NR__llseek)
+ case __NR__llseek:
+#endif
+ case __NR_lseek:
+ case __NR_nanosleep:
+ case __NR_pipe: case __NR_pipe2:
+ case __NR_poll:
+ case __NR_pread64: case __NR_preadv:
+ case __NR_pwrite64: case __NR_pwritev:
+ case __NR_read: case __NR_readv:
+ case __NR_restart_syscall:
+ case __NR_set_robust_list:
+ case __NR_rt_sigaction:
+#if defined(__NR_sigaction)
+ case __NR_sigaction:
+#endif
+#if defined(__NR_signal)
+ case __NR_signal:
+#endif
+ case __NR_rt_sigprocmask:
+#if defined(__NR_sigprocmask)
+ case __NR_sigprocmask:
+#endif
+#if defined(__NR_shutdown)
+ case __NR_shutdown:
+#endif
+ case __NR_rt_sigreturn:
+#if defined(__NR_sigreturn)
+ case __NR_sigreturn:
+#endif
+#if defined(__NR_socketpair)
+ case __NR_socketpair:
+#endif
+ case __NR_time:
+ case __NR_uname:
+ case __NR_write: case __NR_writev:
+ return playground2::Sandbox::SB_ALLOWED;
+
+ // The following system calls are temporarily permitted. This must be
+ // tightened later. But we currently don't implement enough of the sandboxing
+ // API to do so.
+ // As is, this sandbox isn't exactly safe :-/
+#if defined(__NR_sendmsg)
+ case __NR_sendmsg: case __NR_sendto:
+ case __NR_recvmsg: case __NR_recvfrom:
+ case __NR_getsockopt: case __NR_setsockopt:
+#elif defined(__NR_socketcall)
+ case __NR_socketcall:
+#endif
+#if defined(__NR_shmat)
+ case __NR_shmat: case __NR_shmctl: case __NR_shmdt: case __NR_shmget:
+#elif defined(__NR_ipc)
+ case __NR_ipc:
+#endif
+#if defined(__NR_mmap2)
+ case __NR_mmap2:
+#else
+ case __NR_mmap:
+#endif
+#if defined(__NR_ugetrlimit)
+ case __NR_ugetrlimit:
+#endif
+ case __NR_getrlimit:
+ case __NR_ioctl:
+ case __NR_prctl:
+ case __NR_clone:
+ case __NR_munmap: case __NR_mprotect: case __NR_madvise:
+ case __NR_remap_file_pages:
+ return playground2::Sandbox::SB_ALLOWED;
+
+ // Everything that isn't explicitly allowed is denied.
+ default:
+ return (playground2::Sandbox::ErrorCode)ERR;
+ }
+}
+
+static void *threadFnc(void *arg) {
+ return arg;
+}
+
+static void *sendmsgStressThreadFnc(void *arg) {
+ static const int repetitions = 100;
+ static const int kNumFds = 3;
+ for (int rep = 0; rep < repetitions; ++rep) {
+ int fds[2 + kNumFds];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) {
+ perror("socketpair()");
+ _exit(1);
+ }
+ size_t len = 4;
+ char buf[4];
+ if (!playground2::Util::sendFds(fds[0], "test", 4,
+ fds[1], fds[1], fds[1], -1) ||
+ !playground2::Util::getFds(fds[1], buf, &len,
+ fds+2, fds+3, fds+4, NULL) ||
+ len != 4 ||
+ memcmp(buf, "test", len) ||
+ write(fds[2], "demo", 4) != 4 ||
+ read(fds[0], buf, 4) != 4 ||
+ memcmp(buf, "demo", 4)) {
+ perror("sending/receiving of fds");
+ _exit(1);
+ }
+ for (int i = 0; i < 2+kNumFds; ++i) {
+ if (close(fds[i])) {
+ perror("close");
+ _exit(1);
+ }
+ }
+ }
+ return NULL;
+}
+
+int main(int argc, char *argv[]) {
+ int proc_fd = open("/proc", O_RDONLY|O_DIRECTORY);
+ if (playground2::Sandbox::supportsSeccompSandbox(proc_fd) !=
+ playground2::Sandbox::STATUS_AVAILABLE) {
+ perror("sandbox");
+ _exit(1);
+ }
+ playground2::Sandbox::setProcFd(proc_fd);
+ playground2::Sandbox::setSandboxPolicy(evaluator, NULL);
+ playground2::Sandbox::startSandbox();
+
+ // Check that we can create threads
+ pthread_t thr;
+ if (!pthread_create(&thr, NULL, threadFnc,
+ reinterpret_cast<void *>(0x1234))) {
+ void *ret;
+ pthread_join(thr, &ret);
+ if (ret != reinterpret_cast<void *>(0x1234)) {
+ perror("clone() failed");
+ _exit(1);
+ }
+ } else {
+ perror("clone() failed");
+ _exit(1);
+ }
+
+ // Check that we handle restart_syscall() without dieing. This is a little
+ // tricky to trigger. And I can't think of a good way to verify whether it
+ // actually executed.
+ signal(SIGALRM, SIG_IGN);
+ const struct itimerval tv = { { 0, 0 }, { 0, 5*1000 } };
+ const struct timespec tmo = { 0, 100*1000*1000 };
+ setitimer(ITIMER_REAL, &tv, NULL);
+ nanosleep(&tmo, NULL);
+
+ // Check that we can query the size of the stack, but that all other
+ // calls to getrlimit() fail.
+ if (((errno = 0), !getrlimit(RLIMIT_STACK, NULL)) || errno != EFAULT ||
+ ((errno = 0), !getrlimit(RLIMIT_CORE, NULL)) || errno != ERR) {
+ perror("getrlimit()");
+ _exit(1);
+ }
+
+ // Check that we can query TCGETS and TIOCGWINSZ, but no other ioctls().
+ if (((errno = 0), !ioctl(2, TCGETS, NULL)) || errno != EFAULT ||
+ ((errno = 0), !ioctl(2, TIOCGWINSZ, NULL)) || errno != EFAULT ||
+ ((errno = 0), !ioctl(2, TCSETS, NULL)) || errno != ERR) {
+ perror("ioctl()");
+ _exit(1);
+ }
+
+ // Check that prctl() can manipulate the dumpable flag, but nothing else.
+ if (((errno = 0), !prctl(PR_GET_DUMPABLE)) || errno ||
+ ((errno = 0), prctl(PR_SET_DUMPABLE, 1)) || errno ||
+ ((errno = 0), !prctl(PR_SET_SECCOMP, 0)) || errno != ERR) {
+ perror("prctl()");
+ _exit(1);
+ }
+
+ // Check that we can send and receive file handles.
+ int fds[3];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) {
+ perror("socketpair()");
+ _exit(1);
+ }
+ size_t len = 4;
+ char buf[4];
+ if (!playground2::Util::sendFds(fds[0], "test", 4, fds[1], -1) ||
+ !playground2::Util::getFds(fds[1], buf, &len, fds+2, NULL) ||
+ len != 4 ||
+ memcmp(buf, "test", len) ||
+ write(fds[2], "demo", 4) != 4 ||
+ read(fds[0], buf, 4) != 4 ||
+ memcmp(buf, "demo", 4) ||
+ close(fds[0]) ||
+ close(fds[1]) ||
+ close(fds[2])) {
+ perror("sending/receiving of fds");
+ _exit(1);
+ }
+
+ // Check whether SysV IPC works.
+ int shmid;
+ void *addr;
+ if ((shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT|0600)) < 0 ||
+ (addr = shmat(shmid, NULL, 0)) == reinterpret_cast<void *>(-1) ||
+ shmdt(addr) ||
+ shmctl(shmid, IPC_RMID, NULL)) {
+ perror("sysv IPC");
+ _exit(1);
+ }
+
+ // Print a message so that the user can see the sandbox is activated.
+ time_t tm = time(NULL);
+ printf("Sandbox has been started at %s", ctime(&tm));
+
+ // Stress-test the sendmsg() code
+ static const int kSendmsgStressNumThreads = 10;
+ pthread_t sendmsgStressThreads[kSendmsgStressNumThreads];
+ for (int i = 0; i < kSendmsgStressNumThreads; ++i) {
+ if (pthread_create(sendmsgStressThreads + i, NULL,
+ sendmsgStressThreadFnc, NULL)) {
+ perror("pthread_create");
+ _exit(1);
+ }
+ }
+ for (int i = 0; i < kSendmsgStressNumThreads; ++i) {
+ pthread_join(sendmsgStressThreads[i], NULL);
+ }
+
+ return 0;
+}
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
new file mode 100644
index 0000000..f4b6ea0
--- /dev/null
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
@@ -0,0 +1,319 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sandbox/linux/seccomp_bpf/sandbox_bpf.h"
+
+// The kernel gives us a sandbox, we turn it into a playground :-)
+// This is version 2 of the playground; version 1 was built on top of
+// pre-BPF seccomp mode.
+namespace playground2 {
+
+Sandbox::ErrorCode Sandbox::probeEvaluator(int signo) {
+ switch (signo) {
+ case __NR_getpid:
+ // Return EPERM so that we can check that the filter actually ran.
+ return (ErrorCode)EPERM;
+ case __NR_exit_group:
+ // Allow exit() with a non-default return code.
+ return SB_ALLOWED;
+ default:
+ // Make everything else fail in an easily recognizable way.
+ return (ErrorCode)EINVAL;
+ }
+}
+
+bool Sandbox::kernelSupportSeccompBPF(int proc_fd) {
+ // Block all signals before forking a child process. This prevents an
+ // attacker from manipulating our test by sending us an unexpected signal.
+ sigset_t oldMask, newMask;
+ if (sigfillset(&newMask) ||
+ sigprocmask(SIG_BLOCK, &newMask, &oldMask)) {
+ die("sigprocmask() failed");
+ }
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ // Die if we cannot fork(). We would probably fail a little later
+ // anyway, as the machine is likely very close to running out of
+ // memory.
+ // But what we don't want to do is return "false", as a crafty
+ // attacker might cause fork() to fail at will and could trick us
+ // into running without a sandbox.
+ sigprocmask(SIG_SETMASK, &oldMask, NULL); // OK, if it fails
+ die("fork() failed unexpectedly");
+ }
+
+ // In the child process
+ if (!pid) {
+ // Test a very simple sandbox policy to verify that we can
+ // successfully turn on sandboxing.
+ suppressLogging_ = true;
+ evaluators_.clear();
+ setSandboxPolicy(probeEvaluator, NULL);
+ setProcFd(proc_fd);
+ startSandbox();
+ if (syscall(__NR_getpid) < 0 && errno == EPERM) {
+ syscall(__NR_exit_group, (intptr_t)100);
+ }
+ die(NULL);
+ }
+
+ // In the parent process
+ if (sigprocmask(SIG_SETMASK, &oldMask, NULL)) {
+ die("sigprocmask() failed");
+ }
+ int status;
+ if (HANDLE_EINTR(waitpid(pid, &status, 0)) != pid) {
+ die("waitpid() failed unexpectedly");
+ }
+ return WIFEXITED(status) && WEXITSTATUS(status) == 100;
+}
+
+Sandbox::SandboxStatus Sandbox::supportsSeccompSandbox(int proc_fd) {
+ // It the sandbox is currently active, we clearly must have support for
+ // sandboxing.
+ if (status_ == STATUS_ENABLED) {
+ return status_;
+ }
+
+ // Even if the sandbox was previously available, something might have
+ // changed in our run-time environment. Check one more time.
+ if (status_ == STATUS_AVAILABLE) {
+ if (!isSingleThreaded(proc_fd)) {
+ status_ = STATUS_UNAVAILABLE;
+ }
+ return status_;
+ }
+
+ if (status_ == STATUS_UNAVAILABLE && isSingleThreaded(proc_fd)) {
+ // All state transitions resulting in STATUS_UNAVAILABLE are immediately
+ // preceded by STATUS_AVAILABLE. Furthermore, these transitions all
+ // happen, if and only if they are triggered by the process being multi-
+ // threaded.
+ // In other words, if a single-threaded process is currently in the
+ // STATUS_UNAVAILABLE state, it is safe to assume that sandboxing is
+ // actually available.
+ status_ == STATUS_AVAILABLE;
+ return status_;
+ }
+
+ // If we have not previously checked for availability of the sandbox or if
+ // we otherwise don't believe to have a good cached value, we have to
+ // perform a thorough check now.
+ if (status_ == STATUS_UNKNOWN) {
+ status_ = kernelSupportSeccompBPF(proc_fd)
+ ? STATUS_AVAILABLE : STATUS_UNSUPPORTED;
+
+ // As we are performing our tests from a child process, the run-time
+ // environment that is visible to the sandbox is always guaranteed to be
+ // single-threaded. Let's check here whether the caller is single-
+ // threaded. Otherwise, we mark the sandbox as temporarily unavailable.
+ if (status_ == STATUS_AVAILABLE && !isSingleThreaded(proc_fd)) {
+ status_ = STATUS_UNAVAILABLE;
+ }
+ }
+ return status_;
+}
+
+void Sandbox::setProcFd(int proc_fd) {
+ proc_fd_ = proc_fd;
+}
+
+void Sandbox::startSandbox() {
+ if (status_ == STATUS_UNSUPPORTED || status_ == STATUS_UNAVAILABLE) {
+ die("Trying to start sandbox, even though it is known to be unavailable");
+ } else if (status_ == STATUS_ENABLED) {
+ die("Cannot start sandbox recursively. Use multiple calls to "
+ "setSandboxPolicy() to stack policies instead");
+ }
+ if (proc_fd_ < 0) {
+ proc_fd_ = open("/proc", O_RDONLY|O_DIRECTORY);
+ }
+ if (proc_fd_ < 0) {
+ // For now, continue in degraded mode, if we can't access /proc.
+ // In the future, we might want to tighten this requirement.
+ }
+ if (!isSingleThreaded(proc_fd_)) {
+ die("Cannot start sandbox, if process is already multi-threaded");
+ }
+
+ // We no longer need access to any files in /proc. We want to do this
+ // before installing the filters, just in case that our policy denies
+ // close().
+ if (proc_fd_ >= 0) {
+ if (HANDLE_EINTR(close(proc_fd_))) {
+ die("Failed to close file descriptor for /proc");
+ }
+ proc_fd_ = -1;
+ }
+
+ // Install the filters.
+ installFilter();
+
+ // We are now inside the sandbox.
+ status_ = STATUS_ENABLED;
+}
+
+bool Sandbox::isSingleThreaded(int proc_fd) {
+ if (proc_fd < 0) {
+ // Cannot determine whether program is single-threaded. Hope for
+ // the best...
+ return true;
+ }
+
+ struct stat sb;
+ int task = -1;
+ if ((task = openat(proc_fd, "self/task", O_RDONLY|O_DIRECTORY)) < 0 ||
+ fstat(task, &sb) != 0 ||
+ sb.st_nlink != 3 ||
+ HANDLE_EINTR(close(task))) {
+ if (task >= 0) {
+ HANDLE_EINTR(close(task));
+ }
+ return false;
+ }
+ return true;
+}
+
+void Sandbox::setSandboxPolicy(EvaluateSyscall syscallEvaluator,
+ EvaluateArguments argumentEvaluator) {
+ evaluators_.push_back(std::make_pair(syscallEvaluator, argumentEvaluator));
+}
+
+void Sandbox::installFilter() {
+ // Verify that the user pushed a policy.
+ if (evaluators_.empty()) {
+ filter_failed:
+ die("Failed to configure system call filters");
+ }
+
+ // Set new SIGSYS handler
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = &sigSys;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSYS, &sa, NULL) < 0) {
+ goto filter_failed;
+ }
+
+ // Unmask SIGSYS
+ sigset_t mask;
+ if (sigemptyset(&mask) ||
+ sigaddset(&mask, SIGSYS) ||
+ sigprocmask(SIG_UNBLOCK, &mask, NULL)) {
+ goto filter_failed;
+ }
+
+ // We can't handle stacked evaluators, yet. We'll get there eventually
+ // though. Hang tight.
+ if (evaluators_.size() != 1) {
+ die("Not implemented");
+ }
+
+ // If the architecture doesn't match SECCOMP_ARCH, disallow the
+ // system call.
+ std::vector<struct sock_filter> program;
+ program.push_back((struct sock_filter)
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
+ offsetof(struct arch_seccomp_data, arch)));
+ program.push_back((struct sock_filter)
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_ARCH, 1, 0));
+ program.push_back((struct sock_filter)
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO + SECCOMP_DENY_ERRNO));
+
+ // Grab the system call number, so that we can implement jump tables.
+ program.push_back((struct sock_filter)
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct arch_seccomp_data, nr)));
+
+ // Evaluate all possible system calls and depending on their
+ // exit codes generate a BPF filter.
+ // This is very inefficient right now. We need to be much smarter
+ // eventually.
+ // We currently incur a O(N) overhead on each system call, with N
+ // being the number of system calls. It is easy to get this down to
+ // O(log_2(M)) with M being the number of system calls that need special
+ // treatment.
+ EvaluateSyscall evaluateSyscall = evaluators_.begin()->first;
+ for (int sysnum = MIN_SYSCALL; sysnum <= MAX_SYSCALL; ++sysnum) {
+ ErrorCode err = evaluateSyscall(sysnum);
+ int ret;
+ switch (err) {
+ case SB_INSPECT_ARG_1...SB_INSPECT_ARG_6:
+ die("Not implemented");
+ case SB_TRAP:
+ ret = SECCOMP_RET_TRAP;
+ break;
+ case SB_ALLOWED:
+ ret = SECCOMP_RET_ALLOW;
+ break;
+ default:
+ if (err >= static_cast<ErrorCode>(1) &&
+ err <= static_cast<ErrorCode>(4096)) {
+ // We limit errno values to a reasonable range. In fact, the Linux ABI
+ // doesn't support errno values outside of this range.
+ ret = SECCOMP_RET_ERRNO + err;
+ } else {
+ die("Invalid ErrorCode reported by sandbox system call evaluator");
+ }
+ break;
+ }
+ program.push_back((struct sock_filter)
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, sysnum, 0, 1));
+ program.push_back((struct sock_filter)
+ BPF_STMT(BPF_RET+BPF_K, ret));
+ }
+
+ // Everything that isn't allowed is forbidden. Eventually, we would
+ // like to have a way to log forbidden calls, when in debug mode.
+ program.push_back((struct sock_filter)
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO + SECCOMP_DENY_ERRNO));
+
+ // Install BPF filter program
+ const struct sock_fprog prog = { program.size(), &program[0] };
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
+ prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
+ goto filter_failed;
+ }
+
+ return;
+}
+
+void Sandbox::sigSys(int nr, siginfo_t *info, void *void_context) {
+ if (nr != SIGSYS || info->si_code != SYS_SECCOMP || !void_context) {
+ // die() can call LOG(FATAL). This is not normally async-signal safe
+ // and can lead to bugs. We should eventually implement a different
+ // logging and reporting mechanism that is safe to be called from
+ // the sigSys() handler.
+ die("Unexpected SIGSYS received");
+ }
+ ucontext_t *ctx = reinterpret_cast<ucontext_t *>(void_context);
+ int old_errno = errno;
+
+ // In case of error, set the REG_RESULT CPU register to the default
+ // errno value (i.e. EPERM).
+ // We need to be very careful when doing this, as some of our target
+ // platforms have pointer types and CPU registers that are wider than
+ // ints. Furthermore, the kernel ABI requires us to return a negative
+ // value, but errno values are usually positive. And in fact, it would
+ // be perfectly reasonable for somebody to have defined them as unsigned
+ // properties. This makes the correct incantation of type casts rather
+ // subtle. Sometimes, C++ is just too smart for its own good.
+ void *rc = (void *)(intptr_t)-(int)SECCOMP_DENY_ERRNO;
+
+ // This is where we can add extra code to handle complex system calls.
+ // ...
+
+ ctx->uc_mcontext.gregs[REG_RESULT] = reinterpret_cast<greg_t>(rc);
+ errno = old_errno;
+ return;
+}
+
+
+bool Sandbox::suppressLogging_ = false;
+Sandbox::SandboxStatus Sandbox::status_ = STATUS_UNKNOWN;
+int Sandbox::proc_fd_ = -1;
+std::vector<std::pair<Sandbox::EvaluateSyscall,
+ Sandbox::EvaluateArguments> > Sandbox::evaluators_;
+
+} // namespace
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h
new file mode 100644
index 0000000..c3f504a
--- /dev/null
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h
@@ -0,0 +1,244 @@
+// Copyright (c) 2012 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.
+
+#ifndef SANDBOX_BPF_H__
+#define SANDBOX_BPF_H__
+
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/audit.h>
+#include <linux/filter.h>
+// #include <linux/seccomp.h>
+#include <linux/unistd.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <sched.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/ipc.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/shm.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#ifndef SECCOMP_BPF_STANDALONE
+#include "base/basictypes.h"
+#include "base/eintr_wrapper.h"
+#include "base/logging.h"
+#endif
+
+// The Seccomp2 kernel ABI is not part of older versions of glibc.
+// As we can't break compilation with these versions of the library,
+// we explicitly define all missing symbols.
+
+#ifndef PR_SET_NO_NEW_PRIVS
+#define PR_SET_NO_NEW_PRIVS 38
+#define PR_GET_NO_NEW_PRIVS 39
+#endif
+#ifndef IPC_64
+#define IPC_64 0x0100
+#endif
+#ifndef SECCOMP_MODE_FILTER
+#define SECCOMP_MODE_DISABLED 0
+#define SECCOMP_MODE_STRICT 1
+#define SECCOMP_MODE_FILTER 2 // User user-supplied filter
+#define SECCOMP_RET_KILL 0x00000000U // Kill the task immediately
+#define SECCOMP_RET_TRAP 0x00030000U // Disallow and force a SIGSYS
+#define SECCOMP_RET_ERRNO 0x00050000U // Returns an errno
+#define SECCOMP_RET_TRACE 0x7ff00000U // Pass to a tracer or disallow
+#define SECCOMP_RET_ALLOW 0x7fff0000U // Allow
+#define SECCOMP_RET_ACTION 0xffff0000U // Masks for the return value
+#define SECCOMP_RET_DATA 0x0000ffffU // sections
+#endif
+#define SECCOMP_DENY_ERRNO EPERM
+#ifndef SYS_SECCOMP
+#define SYS_SECCOMP 1
+#endif
+
+#if defined(__i386__)
+#define MIN_SYSCALL 0
+#define MAX_SYSCALL 512
+#define SECCOMP_ARCH AUDIT_ARCH_I386
+#define REG_RESULT REG_EAX
+#define REG_SYSCALL REG_EAX
+#define REG_PARM1 REG_EBX
+#define REG_PARM2 REG_ECX
+#define REG_PARM3 REG_EDX
+#define REG_PARM4 REG_ESI
+#define REG_PARM5 REG_EDI
+#define REG_PARM6 REG_EBP
+#elif defined(__x86_64__)
+#define MIN_SYSCALL 0
+#define MAX_SYSCALL 512
+#define SECCOMP_ARCH AUDIT_ARCH_X86_64
+#define REG_RESULT REG_RAX
+#define REG_SYSCALL REG_RAX
+#define REG_PARM1 REG_RDI
+#define REG_PARM2 REG_RSI
+#define REG_PARM3 REG_RDX
+#define REG_PARM4 REG_R10
+#define REG_PARM5 REG_R8
+#define REG_PARM6 REG_R9
+#else
+#error Unsupported target platform
+#endif
+
+struct arch_seccomp_data {
+ int nr;
+ uint32_t arch;
+ uint64_t instruction_pointer;
+ uint64_t args[6];
+};
+
+#ifdef SECCOMP_BPF_STANDALONE
+#define arraysize(x) sizeof(x)/sizeof(*(x)))
+#define HANDLE_EINTR TEMP_FAILURE_RETRY
+#endif
+
+
+namespace playground2 {
+
+class Sandbox {
+ friend class Util;
+
+ public:
+ enum SandboxStatus {
+ STATUS_UNKNOWN, // Status prior to calling supportsSeccompSandbox()
+ STATUS_UNSUPPORTED, // The kernel does not appear to support sandboxing
+ STATUS_UNAVAILABLE, // Currently unavailable but might work again later
+ STATUS_AVAILABLE, // Sandboxing is available but not currently active
+ STATUS_ENABLED // The sandbox is now active
+ };
+
+ enum ErrorCode {
+ SB_TRAP = -1,
+ SB_ALLOWED = 0x0000,
+ SB_INSPECT_ARG_1 = 0x8001,
+ SB_INSPECT_ARG_2 = 0x8002,
+ SB_INSPECT_ARG_3 = 0x8004,
+ SB_INSPECT_ARG_4 = 0x8008,
+ SB_INSPECT_ARG_5 = 0x8010,
+ SB_INSPECT_ARG_6 = 0x8020
+ };
+
+ enum Operation {
+ OP_NOP, OP_EQUAL, OP_NOTEQUAL, OP_LESS,
+ OP_LESS_EQUAL, OP_GREATER, OP_GREATER_EQUAL,
+ OP_HAS_BITS, OP_DOES_NOT_HAVE_BITS
+ };
+
+ struct Constraint {
+ bool is32bit;
+ Operation op;
+ uint32_t value;
+ ErrorCode passed;
+ ErrorCode failed;
+ };
+
+ typedef ErrorCode (*EvaluateSyscall)(int sysno);
+ typedef int (*EvaluateArguments)(int sysno, int arg,
+ Constraint *constraint);
+
+ // There are a lot of reasons why the Seccomp sandbox might not be available.
+ // This could be because the kernel does not support Seccomp mode, or it
+ // could be because another sandbox is already active.
+ // "proc_fd" should be a file descriptor for "/proc", or -1 if not
+ // provided by the caller.
+ static SandboxStatus supportsSeccompSandbox(int proc_fd);
+
+ // The sandbox needs to be able to access files in "/proc/self". If this
+ // directory is not accessible when "startSandbox()" gets called, the caller
+ // can provide an already opened file descriptor by calling "setProcFd()".
+ // The sandbox becomes the new owner of this file descriptor and will
+ // eventually close it when "startSandbox()" executes.
+ static void setProcFd(int proc_fd);
+
+ // The system call evaluator function is called with the system
+ // call number. It can decide to allow the system call unconditionally
+ // by returning "0"; it can deny the system call unconditionally by
+ // returning an appropriate "errno" value; or it can request inspection
+ // of system call argument(s) by returning a suitable combination of
+ // SB_INSPECT_ARG_x bits.
+ // The system argument evaluator is called (if needed) to query additional
+ // constraints for the system call arguments. In the vast majority of
+ // cases, it will set a "Constraint" that forces a new "errno" value.
+ // But for more complex filters, it is possible to return another mask
+ // of SB_INSPECT_ARG_x bits.
+ static void setSandboxPolicy(EvaluateSyscall syscallEvaluator,
+ EvaluateArguments argumentEvaluator);
+
+ // This is the main public entry point. It finds all system calls that
+ // need rewriting, sets up the resources needed by the sandbox, and
+ // enters Seccomp mode.
+ static void startSandbox();
+
+ protected:
+ // Print an error message and terminate the program. Used for fatal errors.
+ static void die(const char *msg) __attribute__((noreturn)) {
+ if (!suppressLogging_) {
+ if (msg) {
+#ifdef SECCOMP_BPF_STANDALONE
+ HANDLE_EINTR(write(2, msg, strlen(msg)));
+ HANDLE_EINTR(write(2, "\n", 1));
+#else
+ // LOG(FATAL) is not neccessarily async-signal safe. It would be
+ // better to always use the code for the SECCOMP_BPF_STANDALONE case.
+ // But that prevents the logging and reporting infrastructure from
+ // picking up sandbox related crashes.
+ // For now, in picking between two evils, we decided in favor of
+ // LOG(FATAL). In the long run, we probably want to rewrite this code
+ // to be async-signal safe.
+ LOG(FATAL) << msg;
+#endif
+ }
+ }
+ for (;;) {
+ // exit_group() should exit our program. After all, it is defined as a
+ // function that doesn't return. But things can theoretically go wrong.
+ // Especially, since we are dealing with system call filters. Continuing
+ // execution would be very bad in most cases where die() gets called.
+ // So, if there is no way for us to ask for the program to exit, the next
+ // best thing we can do is to loop indefinitely. Maybe, somebody will
+ // notice and file a bug...
+ syscall(__NR_exit_group, 1);
+ _exit(1);
+ }
+ }
+
+ // Get a file descriptor pointing to "/proc", if currently available.
+ static int getProcFd() { return proc_fd_; }
+
+ private:
+ static ErrorCode probeEvaluator(int signo);
+ static bool kernelSupportSeccompBPF(int proc_fd);
+
+ static bool isSingleThreaded(int proc_fd);
+ static bool disableFilesystem();
+ static void installFilter();
+ static void sigSys(int nr, siginfo_t *info, void *void_context);
+
+ static bool suppressLogging_;
+ static SandboxStatus status_;
+ static int proc_fd_;
+ static std::vector<std::pair<EvaluateSyscall,
+ EvaluateArguments> > evaluators_;
+};
+
+} // namespace
+
+#endif // SANDBOX_BPF_H__
diff --git a/sandbox/linux/seccomp-bpf/util.cc b/sandbox/linux/seccomp-bpf/util.cc
new file mode 100644
index 0000000..6a94591
--- /dev/null
+++ b/sandbox/linux/seccomp-bpf/util.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sandbox/linux/seccomp_bpf/sandbox_bpf.h"
+#include "sandbox/linux/seccomp_bpf/util.h"
+
+namespace playground2 {
+
+bool Util::sendFds(int transport, const void *buf, size_t len, ...) {
+ int count = 0;
+ va_list ap;
+ va_start(ap, len);
+ while (va_arg(ap, int) >= 0) {
+ ++count;
+ }
+ va_end(ap);
+ if (!count) {
+ return false;
+ }
+ char cmsg_buf[CMSG_SPACE(count*sizeof(int))];
+ memset(cmsg_buf, 0, sizeof(cmsg_buf));
+ struct iovec iov[2] = { { 0 } };
+ struct msghdr msg = { 0 };
+ int dummy = 0;
+ iov[0].iov_base = &dummy;
+ iov[0].iov_len = sizeof(dummy);
+ if (buf && len > 0) {
+ iov[1].iov_base = const_cast<void *>(buf);
+ iov[1].iov_len = len;
+ }
+ msg.msg_iov = iov;
+ msg.msg_iovlen = (buf && len > 0) ? 2 : 1;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = CMSG_LEN(count*sizeof(int));
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(count*sizeof(int));
+ va_start(ap, len);
+ for (int i = 0, fd; (fd = va_arg(ap, int)) >= 0; ++i) {
+ (reinterpret_cast<int *>(CMSG_DATA(cmsg)))[i] = fd;
+ }
+ return sendmsg(transport, &msg, 0) ==
+ static_cast<ssize_t>(sizeof(dummy) + ((buf && len > 0) ? len : 0));
+}
+
+bool Util::getFds(int transport, void *buf, size_t *len, ...) {
+ int count = 0;
+ va_list ap;
+ va_start(ap, len);
+ for (int *fd; (fd = va_arg(ap, int *)) != NULL; ++count) {
+ *fd = -1;
+ }
+ va_end(ap);
+ if (!count) {
+ return false;
+ }
+ char cmsg_buf[CMSG_SPACE(count*sizeof(int))];
+ memset(cmsg_buf, 0, sizeof(cmsg_buf));
+ struct iovec iov[2] = { { 0 } };
+ struct msghdr msg = { 0 };
+ int err;
+ iov[0].iov_base = &err;
+ iov[0].iov_len = sizeof(int);
+ if (buf && len && *len > 0) {
+ iov[1].iov_base = buf;
+ iov[1].iov_len = *len;
+ }
+ msg.msg_iov = iov;
+ msg.msg_iovlen = (buf && len && *len > 0) ? 2 : 1;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = CMSG_LEN(count*sizeof(int));
+ ssize_t bytes = recvmsg(transport, &msg, 0);
+ if (len) {
+ *len = bytes > static_cast<int>(sizeof(int)) ? bytes - sizeof(int) : 0;
+ }
+ if (bytes != static_cast<ssize_t>(sizeof(int) + iov[1].iov_len)) {
+ if (bytes >= 0) {
+ errno = 0;
+ }
+ return false;
+ }
+ if (err) {
+ // "err" is the first four bytes of the payload. If these are non-zero,
+ // the sender on the other side of the socketpair sent us an errno value.
+ // We don't expect to get any file handles in this case.
+ errno = err;
+ return false;
+ }
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+ if ((msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) ||
+ !cmsg ||
+ cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS ||
+ cmsg->cmsg_len != CMSG_LEN(count*sizeof(int))) {
+ errno = EBADF;
+ return false;
+ }
+ va_start(ap, len);
+ for (int *fd, i = 0; (fd = va_arg(ap, int *)) != NULL; ++i) {
+ *fd = (reinterpret_cast<int *>(CMSG_DATA(cmsg)))[i];
+ }
+ va_end(ap);
+ return true;
+}
+
+void Util::closeAllBut(int fd, ...) {
+ int proc_fd;
+ int fdir;
+ if ((proc_fd = Sandbox::getProcFd()) < 0 ||
+ (fdir = openat(proc_fd, "self/fd", O_RDONLY|O_DIRECTORY)) < 0) {
+ Sandbox::die("Cannot access \"/proc/self/fd\"");
+ }
+ int dev_null = open("/dev/null", O_RDWR);
+ DIR *dir = fdopendir(fdir);
+ struct dirent de, *res;
+ while (!readdir_r(dir, &de, &res) && res) {
+ if (res->d_name[0] < '0') {
+ continue;
+ }
+ int i = atoi(res->d_name);
+ if (i >= 0 && i != dirfd(dir) && i != dev_null) {
+ va_list ap;
+ va_start(ap, fd);
+ for (int f = fd;; f = va_arg(ap, int)) {
+ if (f < 0) {
+ if (i <= 2) {
+ // Never ever close 0..2. If we cannot redirect to /dev/null,
+ // then we are better off leaving the standard descriptors open.
+ if (dev_null >= 0) {
+ HANDLE_EINTR(dup2(dev_null, i));
+ }
+ } else {
+ HANDLE_EINTR(close(i));
+ }
+ break;
+ } else if (i == f) {
+ break;
+ }
+ }
+ va_end(ap);
+ }
+ }
+ closedir(dir);
+ if (dev_null >= 0) {
+ HANDLE_EINTR(close(dev_null));
+ }
+ return;
+}
+
+} // namespace
diff --git a/sandbox/linux/seccomp-bpf/util.h b/sandbox/linux/seccomp-bpf/util.h
new file mode 100644
index 0000000..25e7e42
--- /dev/null
+++ b/sandbox/linux/seccomp-bpf/util.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 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.
+
+#ifndef UTIL_H__
+#define UTIL_H__
+
+namespace playground2 {
+
+class Util {
+ public:
+ static bool sendFds(int transport, const void *buf, size_t len, ...);
+ static bool getFds(int transport, void *buf, size_t *len, ...);
+ static void closeAllBut(int fd, ...);
+};
+
+} // namespace
+
+#endif // UTIL_H__