summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux
diff options
context:
space:
mode:
Diffstat (limited to 'security/sandbox/linux')
-rw-r--r--security/sandbox/linux/LinuxCapabilities.cpp28
-rw-r--r--security/sandbox/linux/LinuxCapabilities.h128
-rw-r--r--security/sandbox/linux/LinuxSched.h35
-rw-r--r--security/sandbox/linux/Sandbox.cpp693
-rw-r--r--security/sandbox/linux/Sandbox.h39
-rw-r--r--security/sandbox/linux/SandboxBrokerClient.cpp247
-rw-r--r--security/sandbox/linux/SandboxBrokerClient.h58
-rw-r--r--security/sandbox/linux/SandboxChroot.cpp212
-rw-r--r--security/sandbox/linux/SandboxChroot.h66
-rw-r--r--security/sandbox/linux/SandboxFilter.cpp961
-rw-r--r--security/sandbox/linux/SandboxFilter.h38
-rw-r--r--security/sandbox/linux/SandboxFilterUtil.cpp121
-rw-r--r--security/sandbox/linux/SandboxFilterUtil.h148
-rw-r--r--security/sandbox/linux/SandboxHooks.cpp72
-rw-r--r--security/sandbox/linux/SandboxInfo.cpp272
-rw-r--r--security/sandbox/linux/SandboxInfo.h81
-rw-r--r--security/sandbox/linux/SandboxInternal.h28
-rw-r--r--security/sandbox/linux/SandboxLogging.cpp60
-rw-r--r--security/sandbox/linux/SandboxLogging.h52
-rw-r--r--security/sandbox/linux/SandboxUtil.cpp106
-rw-r--r--security/sandbox/linux/SandboxUtil.h21
-rw-r--r--security/sandbox/linux/broker/SandboxBroker.cpp731
-rw-r--r--security/sandbox/linux/broker/SandboxBroker.h132
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerCommon.cpp120
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerCommon.h72
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp194
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h32
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerUtils.h30
-rw-r--r--security/sandbox/linux/broker/moz.build37
-rw-r--r--security/sandbox/linux/glue/SandboxCrash.cpp137
-rw-r--r--security/sandbox/linux/glue/moz.build29
-rw-r--r--security/sandbox/linux/gtest/TestBroker.cpp626
-rw-r--r--security/sandbox/linux/gtest/TestBrokerPolicy.cpp98
-rw-r--r--security/sandbox/linux/gtest/TestSandboxUtil.cpp44
-rw-r--r--security/sandbox/linux/gtest/moz.build27
-rw-r--r--security/sandbox/linux/moz.build116
36 files changed, 5891 insertions, 0 deletions
diff --git a/security/sandbox/linux/LinuxCapabilities.cpp b/security/sandbox/linux/LinuxCapabilities.cpp
new file mode 100644
index 000000000..87e24a009
--- /dev/null
+++ b/security/sandbox/linux/LinuxCapabilities.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LinuxCapabilities.h"
+
+#include <unistd.h>
+#include <sys/syscall.h>
+
+namespace mozilla {
+
+bool
+LinuxCapabilities::GetCurrent() {
+ __user_cap_header_struct header = { _LINUX_CAPABILITY_VERSION_3, 0 };
+ return syscall(__NR_capget, &header, &mBits) == 0
+ && header.version == _LINUX_CAPABILITY_VERSION_3;
+}
+
+bool
+LinuxCapabilities::SetCurrentRaw() const {
+ __user_cap_header_struct header = { _LINUX_CAPABILITY_VERSION_3, 0 };
+ return syscall(__NR_capset, &header, &mBits) == 0
+ && header.version == _LINUX_CAPABILITY_VERSION_3;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/LinuxCapabilities.h b/security/sandbox/linux/LinuxCapabilities.h
new file mode 100644
index 000000000..9d3220841
--- /dev/null
+++ b/security/sandbox/linux/LinuxCapabilities.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LinuxCapabilities_h
+#define mozilla_LinuxCapabilities_h
+
+#include <linux/capability.h>
+#include <stdint.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/PodOperations.h"
+
+// This class is a relatively simple interface to manipulating the
+// capabilities of a Linux process/thread; see the capabilities(7) man
+// page for background information.
+
+// Unfortunately, Android's kernel headers omit some definitions
+// needed for the low-level capability interface. They're part of the
+// stable syscall ABI, so it's safe to include them here.
+#ifndef _LINUX_CAPABILITY_VERSION_3
+#define _LINUX_CAPABILITY_VERSION_3 0x20080522
+#define _LINUX_CAPABILITY_U32S_3 2
+#endif
+#ifndef CAP_TO_INDEX
+#define CAP_TO_INDEX(x) ((x) >> 5)
+#define CAP_TO_MASK(x) (1 << ((x) & 31))
+#endif
+
+namespace mozilla {
+
+class LinuxCapabilities final
+{
+public:
+ // A class to represent a bit within the capability sets as an lvalue.
+ class BitRef {
+ __u32& mWord;
+ __u32 mMask;
+ friend class LinuxCapabilities;
+ BitRef(__u32& aWord, uint32_t aMask) : mWord(aWord), mMask(aMask) { }
+ BitRef(const BitRef& aBit) : mWord(aBit.mWord), mMask(aBit.mMask) { }
+ public:
+ MOZ_IMPLICIT operator bool() const {
+ return mWord & mMask;
+ }
+ BitRef& operator=(bool aSetTo) {
+ if (aSetTo) {
+ mWord |= mMask;
+ } else {
+ mWord &= mMask;
+ }
+ return *this;
+ }
+ };
+
+ // The default value is the empty set.
+ LinuxCapabilities() { PodArrayZero(mBits); }
+
+ // Get the current thread's capability sets and assign them to this
+ // object. Returns whether it succeeded and sets errno on failure.
+ // Shouldn't fail unless the kernel is very old.
+ bool GetCurrent();
+
+ // Try to set the current thread's capability sets to those
+ // specified in this object. Returns whether it succeeded and sets
+ // errno on failure.
+ bool SetCurrentRaw() const;
+
+ // The capability model requires that the permitted set always be a
+ // superset of the effective and inheritable sets. This method
+ // expands the permitted set as needed and then sets the current
+ // thread's capabilities, as described above.
+ bool SetCurrent() {
+ Normalize();
+ return SetCurrentRaw();
+ }
+
+ void Normalize() {
+ for (size_t i = 0; i < _LINUX_CAPABILITY_U32S_3; ++i) {
+ mBits[i].permitted |= mBits[i].effective | mBits[i].inheritable;
+ }
+ }
+
+ bool AnyEffective() const {
+ for (size_t i = 0; i < _LINUX_CAPABILITY_U32S_3; ++i) {
+ if (mBits[i].effective != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // These three methods expose individual bits in the three
+ // capability sets as objects that can be used as bool lvalues.
+ // The argument is the capability number, as defined in
+ // the <linux/capability.h> header.
+ BitRef Effective(unsigned aCap)
+ {
+ return GenericBitRef(&__user_cap_data_struct::effective, aCap);
+ }
+
+ BitRef Permitted(unsigned aCap)
+ {
+ return GenericBitRef(&__user_cap_data_struct::permitted, aCap);
+ }
+
+ BitRef Inheritable(unsigned aCap)
+ {
+ return GenericBitRef(&__user_cap_data_struct::inheritable, aCap);
+ }
+
+private:
+ __user_cap_data_struct mBits[_LINUX_CAPABILITY_U32S_3];
+
+ BitRef GenericBitRef(__u32 __user_cap_data_struct::* aField, unsigned aCap)
+ {
+ // Please don't pass untrusted data as the capability number.
+ MOZ_ASSERT(CAP_TO_INDEX(aCap) < _LINUX_CAPABILITY_U32S_3);
+ return BitRef(mBits[CAP_TO_INDEX(aCap)].*aField, CAP_TO_MASK(aCap));
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_LinuxCapabilities_h
diff --git a/security/sandbox/linux/LinuxSched.h b/security/sandbox/linux/LinuxSched.h
new file mode 100644
index 000000000..148067d34
--- /dev/null
+++ b/security/sandbox/linux/LinuxSched.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LinuxSched_h
+#define mozilla_LinuxSched_h
+
+#include <linux/sched.h>
+
+// Some build environments, in particular the Android NDK, don't
+// define some of the newer clone/unshare flags ("newer" relatively
+// speaking; CLONE_NEWUTS is present since kernel 2.6.19 in 2006).
+
+#ifndef CLONE_NEWUTS
+#define CLONE_NEWUTS 0x04000000
+#endif
+#ifndef CLONE_NEWIPC
+#define CLONE_NEWIPC 0x08000000
+#endif
+#ifndef CLONE_NEWUSER
+#define CLONE_NEWUSER 0x10000000
+#endif
+#ifndef CLONE_NEWPID
+#define CLONE_NEWPID 0x20000000
+#endif
+#ifndef CLONE_NEWNET
+#define CLONE_NEWNET 0x40000000
+#endif
+#ifndef CLONE_IO
+#define CLONE_IO 0x80000000
+#endif
+
+#endif
diff --git a/security/sandbox/linux/Sandbox.cpp b/security/sandbox/linux/Sandbox.cpp
new file mode 100644
index 000000000..7f1182be9
--- /dev/null
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -0,0 +1,693 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Sandbox.h"
+
+#include "LinuxCapabilities.h"
+#include "LinuxSched.h"
+#include "SandboxBrokerClient.h"
+#include "SandboxChroot.h"
+#include "SandboxFilter.h"
+#include "SandboxInternal.h"
+#include "SandboxLogging.h"
+#include "SandboxUtil.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/futex.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/SandboxInfo.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "sandbox/linux/bpf_dsl/codegen.h"
+#include "sandbox/linux/bpf_dsl/dump_bpf.h"
+#include "sandbox/linux/bpf_dsl/policy.h"
+#include "sandbox/linux/bpf_dsl/policy_compiler.h"
+#include "sandbox/linux/bpf_dsl/seccomp_macros.h"
+#include "sandbox/linux/seccomp-bpf/trap.h"
+#include "sandbox/linux/system_headers/linux_filter.h"
+#include "sandbox/linux/system_headers/linux_seccomp.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+#if defined(ANDROID)
+#include "sandbox/linux/system_headers/linux_ucontext.h"
+#endif
+
+#ifdef MOZ_ASAN
+// Copy libsanitizer declarations to avoid depending on ASAN headers.
+// See also bug 1081242 comment #4.
+extern "C" {
+namespace __sanitizer {
+// Win64 uses long long, but this is Linux.
+typedef signed long sptr;
+} // namespace __sanitizer
+
+typedef struct {
+ int coverage_sandboxed;
+ __sanitizer::sptr coverage_fd;
+ unsigned int coverage_max_block_size;
+} __sanitizer_sandbox_arguments;
+
+MOZ_IMPORT_API void
+__sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args);
+} // extern "C"
+#endif // MOZ_ASAN
+
+// Signal number used to enable seccomp on each thread.
+int gSeccompTsyncBroadcastSignum = 0;
+
+namespace mozilla {
+
+// This is initialized by SandboxSetCrashFunc().
+SandboxCrashFunc gSandboxCrashFunc;
+
+#ifdef MOZ_GMP_SANDBOX
+// For media plugins, we can start the sandbox before we dlopen the
+// module, so we have to pre-open the file and simulate the sandboxed
+// open().
+static SandboxOpenedFile gMediaPluginFile;
+#endif
+
+static UniquePtr<SandboxChroot> gChrootHelper;
+static void (*gChromiumSigSysHandler)(int, siginfo_t*, void*);
+
+// Test whether a ucontext, interpreted as the state after a syscall,
+// indicates the given error. See also sandbox::Syscall::PutValueInUcontext.
+static bool
+ContextIsError(const ucontext_t *aContext, int aError)
+{
+ // Avoid integer promotion warnings. (The unary addition makes
+ // the decltype not evaluate to a reference type.)
+ typedef decltype(+SECCOMP_RESULT(aContext)) reg_t;
+
+#ifdef __mips__
+ return SECCOMP_PARM4(aContext) != 0
+ && SECCOMP_RESULT(aContext) == static_cast<reg_t>(aError);
+#else
+ return SECCOMP_RESULT(aContext) == static_cast<reg_t>(-aError);
+#endif
+}
+
+/**
+ * This is the SIGSYS handler function. It delegates to the Chromium
+ * TrapRegistry handler (see InstallSigSysHandler, below) and, if the
+ * trap handler installed by the policy would fail with ENOSYS,
+ * crashes the process. This allows unintentional policy failures to
+ * be reported as crash dumps and fixed. It also logs information
+ * about the failed system call.
+ *
+ * Note that this could be invoked in parallel on multiple threads and
+ * that it could be in async signal context (e.g., intercepting an
+ * open() called from an async signal handler).
+ */
+static void
+SigSysHandler(int nr, siginfo_t *info, void *void_context)
+{
+ ucontext_t *ctx = static_cast<ucontext_t*>(void_context);
+ // This shouldn't ever be null, but the Chromium handler checks for
+ // that and refrains from crashing, so let's not crash release builds:
+ MOZ_DIAGNOSTIC_ASSERT(ctx);
+ if (!ctx) {
+ return;
+ }
+
+ // Save a copy of the context before invoking the trap handler,
+ // which will overwrite one or more registers with the return value.
+ ucontext_t savedCtx = *ctx;
+
+ gChromiumSigSysHandler(nr, info, ctx);
+ if (!ContextIsError(ctx, ENOSYS)) {
+ return;
+ }
+
+ pid_t pid = getpid();
+ unsigned long syscall_nr = SECCOMP_SYSCALL(&savedCtx);
+ unsigned long args[6];
+ args[0] = SECCOMP_PARM1(&savedCtx);
+ args[1] = SECCOMP_PARM2(&savedCtx);
+ args[2] = SECCOMP_PARM3(&savedCtx);
+ args[3] = SECCOMP_PARM4(&savedCtx);
+ args[4] = SECCOMP_PARM5(&savedCtx);
+ args[5] = SECCOMP_PARM6(&savedCtx);
+
+ // TODO, someday when this is enabled on MIPS: include the two extra
+ // args in the error message.
+ SANDBOX_LOG_ERROR("seccomp sandbox violation: pid %d, syscall %d,"
+ " args %d %d %d %d %d %d. Killing process.",
+ pid, syscall_nr,
+ args[0], args[1], args[2], args[3], args[4], args[5]);
+
+ // Bug 1017393: record syscall number somewhere useful.
+ info->si_addr = reinterpret_cast<void*>(syscall_nr);
+
+ gSandboxCrashFunc(nr, info, &savedCtx);
+ _exit(127);
+}
+
+/**
+ * This function installs the SIGSYS handler. This is slightly
+ * complicated because we want to use Chromium's handler to dispatch
+ * to specific trap handlers defined in the policy, but we also need
+ * the full original signal context to give to Breakpad for crash
+ * dumps. So we install Chromium's handler first, then retrieve its
+ * address so our replacement can delegate to it.
+ */
+static void
+InstallSigSysHandler(void)
+{
+ struct sigaction act;
+
+ // Ensure that the Chromium handler is installed.
+ Unused << sandbox::Trap::Registry();
+
+ // If the signal handling state isn't as expected, crash now instead
+ // of crashing later (and more confusingly) when SIGSYS happens.
+
+ if (sigaction(SIGSYS, nullptr, &act) != 0) {
+ MOZ_CRASH("Couldn't read old SIGSYS disposition");
+ }
+ if ((act.sa_flags & SA_SIGINFO) != SA_SIGINFO) {
+ MOZ_CRASH("SIGSYS not already set to a siginfo handler?");
+ }
+ MOZ_RELEASE_ASSERT(act.sa_sigaction);
+ gChromiumSigSysHandler = act.sa_sigaction;
+ act.sa_sigaction = SigSysHandler;
+ // Currently, SA_NODEFER should already be set by the Chromium code,
+ // but it's harmless to ensure that it's set:
+ MOZ_ASSERT(act.sa_flags & SA_NODEFER);
+ act.sa_flags |= SA_NODEFER;
+ if (sigaction(SIGSYS, &act, nullptr) < 0) {
+ MOZ_CRASH("Couldn't change SIGSYS disposition");
+ }
+}
+
+/**
+ * This function installs the syscall filter, a.k.a. seccomp. The
+ * aUseTSync flag indicates whether this should apply to all threads
+ * in the process -- which will fail if the kernel doesn't support
+ * that -- or only the current thread.
+ *
+ * SECCOMP_MODE_FILTER is the "bpf" mode of seccomp which allows
+ * to pass a bpf program (in our case, it contains a syscall
+ * whitelist).
+ *
+ * PR_SET_NO_NEW_PRIVS ensures that it is impossible to grant more
+ * syscalls to the process beyond this point (even after fork()), and
+ * prevents gaining capabilities (e.g., by exec'ing a setuid root
+ * program). The kernel won't allow seccomp-bpf without doing this,
+ * because otherwise it could be used for privilege escalation attacks.
+ *
+ * Returns false if the filter was already installed (see the
+ * PR_SET_NO_NEW_PRIVS rule in SandboxFilter.cpp). Crashes on any
+ * other error condition.
+ *
+ * @see SandboxInfo
+ * @see BroadcastSetThreadSandbox
+ */
+static bool MOZ_MUST_USE
+InstallSyscallFilter(const sock_fprog *aProg, bool aUseTSync)
+{
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ if (!aUseTSync && errno == ETXTBSY) {
+ return false;
+ }
+ SANDBOX_LOG_ERROR("prctl(PR_SET_NO_NEW_PRIVS) failed: %s", strerror(errno));
+ MOZ_CRASH("prctl(PR_SET_NO_NEW_PRIVS)");
+ }
+
+ if (aUseTSync) {
+ if (syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
+ SECCOMP_FILTER_FLAG_TSYNC, aProg) != 0) {
+ SANDBOX_LOG_ERROR("thread-synchronized seccomp failed: %s",
+ strerror(errno));
+ MOZ_CRASH("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)");
+ }
+ } else {
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)aProg, 0, 0)) {
+ SANDBOX_LOG_ERROR("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER) failed: %s",
+ strerror(errno));
+ MOZ_CRASH("seccomp+tsync failed, but kernel supports tsync");
+ }
+ }
+ return true;
+}
+
+// Use signals for permissions that need to be set per-thread.
+// The communication channel from the signal handler back to the main thread.
+static mozilla::Atomic<int> gSetSandboxDone;
+// Pass the filter itself through a global.
+const sock_fprog* gSetSandboxFilter;
+
+// We have to dynamically allocate the signal number; see bug 1038900.
+// This function returns the first realtime signal currently set to
+// default handling (i.e., not in use), or 0 if none could be found.
+//
+// WARNING: if this function or anything similar to it (including in
+// external libraries) is used on multiple threads concurrently, there
+// will be a race condition.
+static int
+FindFreeSignalNumber()
+{
+ for (int signum = SIGRTMAX; signum >= SIGRTMIN; --signum) {
+ struct sigaction sa;
+
+ if (sigaction(signum, nullptr, &sa) == 0 &&
+ (sa.sa_flags & SA_SIGINFO) == 0 &&
+ sa.sa_handler == SIG_DFL) {
+ return signum;
+ }
+ }
+ return 0;
+}
+
+// Returns true if sandboxing was enabled, or false if sandboxing
+// already was enabled. Crashes if sandboxing could not be enabled.
+static bool
+SetThreadSandbox()
+{
+ return InstallSyscallFilter(gSetSandboxFilter, false);
+}
+
+static void
+SetThreadSandboxHandler(int signum)
+{
+ // The non-zero number sent back to the main thread indicates
+ // whether action was taken.
+ if (SetThreadSandbox()) {
+ gSetSandboxDone = 2;
+ } else {
+ gSetSandboxDone = 1;
+ }
+ // Wake up the main thread. See the FUTEX_WAIT call, below, for an
+ // explanation.
+ syscall(__NR_futex, reinterpret_cast<int*>(&gSetSandboxDone),
+ FUTEX_WAKE, 1);
+}
+
+static void
+EnterChroot()
+{
+ if (gChrootHelper) {
+ gChrootHelper->Invoke();
+ gChrootHelper = nullptr;
+ }
+}
+
+static void
+BroadcastSetThreadSandbox(const sock_fprog* aFilter)
+{
+ pid_t pid, tid, myTid;
+ DIR *taskdp;
+ struct dirent *de;
+
+ // This function does not own *aFilter, so this global needs to
+ // always be zeroed before returning.
+ gSetSandboxFilter = aFilter;
+
+ static_assert(sizeof(mozilla::Atomic<int>) == sizeof(int),
+ "mozilla::Atomic<int> isn't represented by an int");
+ pid = getpid();
+ myTid = syscall(__NR_gettid);
+ taskdp = opendir("/proc/self/task");
+ if (taskdp == nullptr) {
+ SANDBOX_LOG_ERROR("opendir /proc/self/task: %s\n", strerror(errno));
+ MOZ_CRASH();
+ }
+
+ EnterChroot();
+
+ // In case this races with a not-yet-deprivileged thread cloning
+ // itself, repeat iterating over all threads until we find none
+ // that are still privileged.
+ bool sandboxProgress;
+ do {
+ sandboxProgress = false;
+ // For each thread...
+ while ((de = readdir(taskdp))) {
+ char *endptr;
+ tid = strtol(de->d_name, &endptr, 10);
+ if (*endptr != '\0' || tid <= 0) {
+ // Not a task ID.
+ continue;
+ }
+ if (tid == myTid) {
+ // Drop this thread's privileges last, below, so we can
+ // continue to signal other threads.
+ continue;
+ }
+
+ MOZ_RELEASE_ASSERT(gSeccompTsyncBroadcastSignum != 0);
+
+ // Reset the futex cell and signal.
+ gSetSandboxDone = 0;
+ if (syscall(__NR_tgkill, pid, tid, gSeccompTsyncBroadcastSignum) != 0) {
+ if (errno == ESRCH) {
+ SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid);
+ // Rescan threads, in case it forked before exiting.
+ sandboxProgress = true;
+ continue;
+ }
+ SANDBOX_LOG_ERROR("tgkill(%d,%d): %s\n", pid, tid, strerror(errno));
+ MOZ_CRASH();
+ }
+ // It's unlikely, but if the thread somehow manages to exit
+ // after receiving the signal but before entering the signal
+ // handler, we need to avoid blocking forever.
+ //
+ // Using futex directly lets the signal handler send the wakeup
+ // from an async signal handler (pthread mutex/condvar calls
+ // aren't allowed), and to use a relative timeout that isn't
+ // affected by changes to the system clock (not possible with
+ // POSIX semaphores).
+ //
+ // If a thread doesn't respond within a reasonable amount of
+ // time, but still exists, we crash -- the alternative is either
+ // blocking forever or silently losing security, and it
+ // shouldn't actually happen.
+ static const int crashDelay = 10; // seconds
+ struct timespec timeLimit;
+ clock_gettime(CLOCK_MONOTONIC, &timeLimit);
+ timeLimit.tv_sec += crashDelay;
+ while (true) {
+ static const struct timespec futexTimeout = { 0, 10*1000*1000 }; // 10ms
+ // Atomically: if gSetSandboxDone == 0, then sleep.
+ if (syscall(__NR_futex, reinterpret_cast<int*>(&gSetSandboxDone),
+ FUTEX_WAIT, 0, &futexTimeout) != 0) {
+ if (errno != EWOULDBLOCK && errno != ETIMEDOUT && errno != EINTR) {
+ SANDBOX_LOG_ERROR("FUTEX_WAIT: %s\n", strerror(errno));
+ MOZ_CRASH();
+ }
+ }
+ // Did the handler finish?
+ if (gSetSandboxDone > 0) {
+ if (gSetSandboxDone == 2) {
+ sandboxProgress = true;
+ }
+ break;
+ }
+ // Has the thread ceased to exist?
+ if (syscall(__NR_tgkill, pid, tid, 0) != 0) {
+ if (errno == ESRCH) {
+ SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid);
+ }
+ // Rescan threads, in case it forked before exiting.
+ // Also, if it somehow failed in a way that wasn't ESRCH,
+ // and still exists, that will be handled on the next pass.
+ sandboxProgress = true;
+ break;
+ }
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (now.tv_sec > timeLimit.tv_sec ||
+ (now.tv_sec == timeLimit.tv_sec &&
+ now.tv_nsec > timeLimit.tv_nsec)) {
+ SANDBOX_LOG_ERROR("Thread %d unresponsive for %d seconds."
+ " Killing process.",
+ tid, crashDelay);
+ MOZ_CRASH();
+ }
+ }
+ }
+ rewinddir(taskdp);
+ } while (sandboxProgress);
+
+ void (*oldHandler)(int);
+ oldHandler = signal(gSeccompTsyncBroadcastSignum, SIG_DFL);
+ gSeccompTsyncBroadcastSignum = 0;
+ if (oldHandler != SetThreadSandboxHandler) {
+ // See the comment on FindFreeSignalNumber about race conditions.
+ SANDBOX_LOG_ERROR("handler for signal %d was changed to %p!",
+ gSeccompTsyncBroadcastSignum, oldHandler);
+ MOZ_CRASH();
+ }
+ Unused << closedir(taskdp);
+ // And now, deprivilege the main thread:
+ SetThreadSandbox();
+ gSetSandboxFilter = nullptr;
+}
+
+static void
+ApplySandboxWithTSync(sock_fprog* aFilter)
+{
+ EnterChroot();
+ // At this point we're committed to using tsync, because the signal
+ // broadcast workaround needs to access procfs. (Unless chroot
+ // isn't used... but this failure shouldn't happen in the first
+ // place, so let's not make extra special cases for it.)
+ if (!InstallSyscallFilter(aFilter, true)) {
+ MOZ_CRASH();
+ }
+}
+
+// Common code for sandbox startup.
+static void
+SetCurrentProcessSandbox(UniquePtr<sandbox::bpf_dsl::Policy> aPolicy)
+{
+ MOZ_ASSERT(gSandboxCrashFunc);
+
+ // Note: PolicyCompiler borrows the policy and registry for its
+ // lifetime, but does not take ownership of them.
+ sandbox::bpf_dsl::PolicyCompiler compiler(aPolicy.get(),
+ sandbox::Trap::Registry());
+ sandbox::CodeGen::Program program = compiler.Compile();
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ sandbox::bpf_dsl::DumpBPF::PrintProgram(program);
+ }
+
+ InstallSigSysHandler();
+
+#ifdef MOZ_ASAN
+ __sanitizer_sandbox_arguments asanArgs;
+ asanArgs.coverage_sandboxed = 1;
+ asanArgs.coverage_fd = -1;
+ asanArgs.coverage_max_block_size = 0;
+ __sanitizer_sandbox_on_notify(&asanArgs);
+#endif
+
+ // The syscall takes a C-style array, so copy the vector into one.
+ size_t programLen = program.size();
+ UniquePtr<sock_filter[]> flatProgram(new sock_filter[programLen]);
+ for (auto i = program.begin(); i != program.end(); ++i) {
+ flatProgram[i - program.begin()] = *i;
+ }
+
+ sock_fprog fprog;
+ fprog.filter = flatProgram.get();
+ fprog.len = static_cast<unsigned short>(programLen);
+ MOZ_RELEASE_ASSERT(static_cast<size_t>(fprog.len) == programLen);
+
+ const SandboxInfo info = SandboxInfo::Get();
+ if (info.Test(SandboxInfo::kHasSeccompTSync)) {
+ if (info.Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("using seccomp tsync");
+ }
+ ApplySandboxWithTSync(&fprog);
+ } else {
+ if (info.Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("no tsync support; using signal broadcast");
+ }
+ BroadcastSetThreadSandbox(&fprog);
+ }
+ MOZ_RELEASE_ASSERT(!gChrootHelper, "forgot to chroot");
+}
+
+void
+SandboxEarlyInit(GeckoProcessType aType)
+{
+ const SandboxInfo info = SandboxInfo::Get();
+ if (info.Test(SandboxInfo::kUnexpectedThreads)) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(IsSingleThreaded());
+
+ // Which kinds of resource isolation (of those that need to be set
+ // up at this point) can be used by this process?
+ bool canChroot = false;
+ bool canUnshareNet = false;
+ bool canUnshareIPC = false;
+
+ switch (aType) {
+ case GeckoProcessType_Default:
+ MOZ_ASSERT(false, "SandboxEarlyInit in parent process");
+ return;
+#ifdef MOZ_GMP_SANDBOX
+ case GeckoProcessType_GMPlugin:
+ if (!info.Test(SandboxInfo::kEnabledForMedia)) {
+ break;
+ }
+ canUnshareNet = true;
+ canUnshareIPC = true;
+ // Need seccomp-bpf to intercept open().
+ canChroot = info.Test(SandboxInfo::kHasSeccompBPF);
+ break;
+#endif
+ // In the future, content processes will be able to use some of
+ // these.
+ default:
+ // Other cases intentionally left blank.
+ break;
+ }
+
+ // If TSYNC is not supported, set up signal handler
+ // used to enable seccomp on each thread.
+ if (!info.Test(SandboxInfo::kHasSeccompTSync)) {
+ gSeccompTsyncBroadcastSignum = FindFreeSignalNumber();
+ if (gSeccompTsyncBroadcastSignum == 0) {
+ SANDBOX_LOG_ERROR("No available signal numbers!");
+ MOZ_CRASH();
+ }
+
+ void (*oldHandler)(int);
+ oldHandler = signal(gSeccompTsyncBroadcastSignum, SetThreadSandboxHandler);
+ if (oldHandler != SIG_DFL) {
+ // See the comment on FindFreeSignalNumber about race conditions.
+ SANDBOX_LOG_ERROR("signal %d in use by handler %p!\n",
+ gSeccompTsyncBroadcastSignum, oldHandler);
+ MOZ_CRASH();
+ }
+ }
+
+ // If there's nothing to do, then we're done.
+ if (!canChroot && !canUnshareNet && !canUnshareIPC) {
+ return;
+ }
+
+ {
+ LinuxCapabilities existingCaps;
+ if (existingCaps.GetCurrent() && existingCaps.AnyEffective()) {
+ SANDBOX_LOG_ERROR("PLEASE DO NOT RUN THIS AS ROOT. Strange things may"
+ " happen when capabilities are dropped.");
+ }
+ }
+
+ // If capabilities can't be gained, then nothing can be done.
+ if (!info.Test(SandboxInfo::kHasUserNamespaces)) {
+ // Drop any existing capabilities; unsharing the user namespace
+ // would implicitly drop them, so if we're running in a broken
+ // configuration where that would matter (e.g., running as root
+ // from a non-root-owned mode-0700 directory) this means it will
+ // break the same way on all kernels and be easier to troubleshoot.
+ LinuxCapabilities().SetCurrent();
+ return;
+ }
+
+ // The failure cases for the various unshares, and setting up the
+ // chroot helper, don't strictly need to be fatal -- but they also
+ // shouldn't fail on any reasonable system, so let's take the small
+ // risk of breakage over the small risk of quietly providing less
+ // security than we expect. (Unlike in SandboxInfo, this is in the
+ // child process, so crashing here isn't as severe a response to the
+ // unexpected.)
+ if (!UnshareUserNamespace()) {
+ SANDBOX_LOG_ERROR("unshare(CLONE_NEWUSER): %s", strerror(errno));
+ // If CanCreateUserNamespace (SandboxInfo.cpp) returns true, then
+ // the unshare shouldn't have failed.
+ MOZ_CRASH("unshare(CLONE_NEWUSER)");
+ }
+ // No early returns after this point! We need to drop the
+ // capabilities that were gained by unsharing the user namesapce.
+
+ if (canUnshareIPC && syscall(__NR_unshare, CLONE_NEWIPC) != 0) {
+ SANDBOX_LOG_ERROR("unshare(CLONE_NEWIPC): %s", strerror(errno));
+ MOZ_CRASH("unshare(CLONE_NEWIPC)");
+ }
+
+ if (canUnshareNet && syscall(__NR_unshare, CLONE_NEWNET) != 0) {
+ SANDBOX_LOG_ERROR("unshare(CLONE_NEWNET): %s", strerror(errno));
+ MOZ_CRASH("unshare(CLONE_NEWNET)");
+ }
+
+ if (canChroot) {
+ gChrootHelper = MakeUnique<SandboxChroot>();
+ if (!gChrootHelper->Prepare()) {
+ SANDBOX_LOG_ERROR("failed to set up chroot helper");
+ MOZ_CRASH("SandboxChroot::Prepare");
+ }
+ }
+
+ if (!LinuxCapabilities().SetCurrent()) {
+ SANDBOX_LOG_ERROR("dropping capabilities: %s", strerror(errno));
+ MOZ_CRASH("can't drop capabilities");
+ }
+}
+
+#ifdef MOZ_CONTENT_SANDBOX
+/**
+ * Starts the seccomp sandbox for a content process. Should be called
+ * only once, and before any potentially harmful content is loaded.
+ *
+ * Will normally make the process exit on failure.
+*/
+bool
+SetContentProcessSandbox(int aBrokerFd)
+{
+ if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForContent)) {
+ if (aBrokerFd >= 0) {
+ close(aBrokerFd);
+ }
+ return false;
+ }
+
+ // This needs to live until the process exits.
+ static Maybe<SandboxBrokerClient> sBroker;
+ if (aBrokerFd >= 0) {
+ sBroker.emplace(aBrokerFd);
+ }
+
+ SetCurrentProcessSandbox(GetContentSandboxPolicy(sBroker.ptrOr(nullptr)));
+ return true;
+}
+#endif // MOZ_CONTENT_SANDBOX
+
+#ifdef MOZ_GMP_SANDBOX
+/**
+ * Starts the seccomp sandbox for a media plugin process. Should be
+ * called only once, and before any potentially harmful content is
+ * loaded -- including the plugin itself, if it's considered untrusted.
+ *
+ * The file indicated by aFilePath, if non-null, can be open()ed
+ * read-only, once, after the sandbox starts; it should be the .so
+ * file implementing the not-yet-loaded plugin.
+ *
+ * Will normally make the process exit on failure.
+*/
+void
+SetMediaPluginSandbox(const char *aFilePath)
+{
+ if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForMedia)) {
+ return;
+ }
+
+ MOZ_ASSERT(!gMediaPluginFile.mPath);
+ if (aFilePath) {
+ gMediaPluginFile.mPath = strdup(aFilePath);
+ gMediaPluginFile.mFd = open(aFilePath, O_RDONLY | O_CLOEXEC);
+ if (gMediaPluginFile.mFd == -1) {
+ SANDBOX_LOG_ERROR("failed to open plugin file %s: %s",
+ aFilePath, strerror(errno));
+ MOZ_CRASH();
+ }
+ } else {
+ gMediaPluginFile.mFd = -1;
+ }
+ // Finally, start the sandbox.
+ SetCurrentProcessSandbox(GetMediaSandboxPolicy(&gMediaPluginFile));
+}
+#endif // MOZ_GMP_SANDBOX
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/Sandbox.h b/security/sandbox/linux/Sandbox.h
new file mode 100644
index 000000000..94b26e25b
--- /dev/null
+++ b/security/sandbox/linux/Sandbox.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Sandbox_h
+#define mozilla_Sandbox_h
+
+#include "mozilla/Types.h"
+#include "nsXULAppAPI.h"
+
+// This defines the entry points for a content process to start
+// sandboxing itself. See also SandboxInfo.h for what parts of
+// sandboxing are enabled/supported.
+
+namespace mozilla {
+
+// This must be called early, while the process is still single-threaded.
+MOZ_EXPORT void SandboxEarlyInit(GeckoProcessType aType);
+
+#ifdef MOZ_CONTENT_SANDBOX
+// Call only if SandboxInfo::CanSandboxContent() returns true.
+// (No-op if MOZ_DISABLE_CONTENT_SANDBOX is set.)
+// aBrokerFd is the filesystem broker client file descriptor,
+// or -1 to allow direct filesystem access.
+MOZ_EXPORT bool SetContentProcessSandbox(int aBrokerFd);
+#endif
+
+#ifdef MOZ_GMP_SANDBOX
+// Call only if SandboxInfo::CanSandboxMedia() returns true.
+// (No-op if MOZ_DISABLE_GMP_SANDBOX is set.)
+// aFilePath is the path to the plugin file.
+MOZ_EXPORT void SetMediaPluginSandbox(const char *aFilePath);
+#endif
+
+} // namespace mozilla
+
+#endif // mozilla_Sandbox_h
diff --git a/security/sandbox/linux/SandboxBrokerClient.cpp b/security/sandbox/linux/SandboxBrokerClient.cpp
new file mode 100644
index 000000000..68744ad02
--- /dev/null
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxBrokerClient.h"
+#include "SandboxInfo.h"
+#include "SandboxLogging.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/NullPtr.h"
+#include "base/strings/safe_sprintf.h"
+
+namespace mozilla {
+
+SandboxBrokerClient::SandboxBrokerClient(int aFd)
+: mFileDesc(aFd)
+{
+}
+
+SandboxBrokerClient::~SandboxBrokerClient()
+{
+ close(mFileDesc);
+}
+
+int
+SandboxBrokerClient::DoCall(const Request* aReq, const char* aPath,
+ const char* aPath2, void* aResponseBuff,
+ bool expectFd)
+{
+ // Remap /proc/self to the actual pid, so that the broker can open
+ // it. This happens here instead of in the broker to follow the
+ // principle of least privilege and keep the broker as simple as
+ // possible. (Note: when pid namespaces happen, this will also need
+ // to remap the inner pid to the outer pid.)
+ // We only remap the first path.
+ static const char kProcSelf[] = "/proc/self/";
+ static const size_t kProcSelfLen = sizeof(kProcSelf) - 1;
+ const char* path = aPath;
+ // This buffer just needs to be large enough for any such path that
+ // the policy would actually allow. sizeof("/proc/2147483647/") == 18.
+ char rewrittenPath[64];
+ if (strncmp(aPath, kProcSelf, kProcSelfLen) == 0) {
+ ssize_t len =
+ base::strings::SafeSPrintf(rewrittenPath, "/proc/%d/%s",
+ getpid(), aPath + kProcSelfLen);
+ if (static_cast<size_t>(len) < sizeof(rewrittenPath)) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("rewriting %s -> %s", aPath, rewrittenPath);
+ }
+ path = rewrittenPath;
+ } else {
+ SANDBOX_LOG_ERROR("not rewriting unexpectedly long path %s", aPath);
+ }
+ }
+
+ struct iovec ios[3];
+ int respFds[2];
+
+ // Set up iovecs for request + path.
+ ios[0].iov_base = const_cast<Request*>(aReq);
+ ios[0].iov_len = sizeof(*aReq);
+ ios[1].iov_base = const_cast<char*>(path);
+ ios[1].iov_len = strlen(path) + 1;
+ if (aPath2 != nullptr) {
+ ios[2].iov_base = const_cast<char*>(aPath2);
+ ios[2].iov_len = strlen(aPath2) + 1;
+ } else {
+ ios[2].iov_base = 0;
+ ios[2].iov_len = 0;
+ }
+ if (ios[1].iov_len > kMaxPathLen) {
+ return -ENAMETOOLONG;
+ }
+ if (ios[2].iov_len > kMaxPathLen) {
+ return -ENAMETOOLONG;
+ }
+
+ // Create response socket and send request.
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, respFds) < 0) {
+ return -errno;
+ }
+ const ssize_t sent = SendWithFd(mFileDesc, ios, 3, respFds[1]);
+ const int sendErrno = errno;
+ MOZ_ASSERT(sent < 0 ||
+ static_cast<size_t>(sent) == ios[0].iov_len
+ + ios[1].iov_len
+ + ios[2].iov_len);
+ close(respFds[1]);
+ if (sent < 0) {
+ close(respFds[0]);
+ return -sendErrno;
+ }
+
+ // Set up iovecs for response.
+ Response resp;
+ ios[0].iov_base = &resp;
+ ios[0].iov_len = sizeof(resp);
+ if (aResponseBuff) {
+ ios[1].iov_base = aResponseBuff;
+ ios[1].iov_len = aReq->mBufSize;
+ } else {
+ ios[1].iov_base = nullptr;
+ ios[1].iov_len = 0;
+ }
+
+ // Wait for response and return appropriately.
+ int openedFd = -1;
+ const ssize_t recvd = RecvWithFd(respFds[0], ios, aResponseBuff ? 2 : 1,
+ expectFd ? &openedFd : nullptr);
+ const int recvErrno = errno;
+ close(respFds[0]);
+ if (recvd < 0) {
+ return -recvErrno;
+ }
+ if (recvd == 0) {
+ SANDBOX_LOG_ERROR("Unexpected EOF, op %d flags 0%o path %s",
+ aReq->mOp, aReq->mFlags, path);
+ return -EIO;
+ }
+ MOZ_ASSERT(static_cast<size_t>(recvd) <= ios[0].iov_len + ios[1].iov_len);
+ // Some calls such as readlink return a size if successful
+ if (resp.mError >= 0) {
+ // Success!
+ if (expectFd) {
+ MOZ_ASSERT(openedFd >= 0);
+ return openedFd;
+ }
+ return resp.mError;
+ }
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ // Keep in mind that "rejected" files can include ones that don't
+ // actually exist, if it's something that's optional or part of a
+ // search path (e.g., shared libraries). In those cases, this
+ // error message is expected.
+ SANDBOX_LOG_ERROR("Rejected errno %d op %d flags 0%o path %s",
+ resp.mError, aReq->mOp, aReq->mFlags, path);
+ }
+ if (openedFd >= 0) {
+ close(openedFd);
+ }
+ return resp.mError;
+}
+
+int
+SandboxBrokerClient::Open(const char* aPath, int aFlags)
+{
+ Request req = { SANDBOX_FILE_OPEN, aFlags, 0 };
+ int maybeFd = DoCall(&req, aPath, nullptr, nullptr, true);
+ if (maybeFd >= 0) {
+ // NSPR has opinions about file flags. Fix O_CLOEXEC.
+ if ((aFlags & O_CLOEXEC) == 0) {
+ fcntl(maybeFd, F_SETFD, 0);
+ }
+ }
+ return maybeFd;
+}
+
+int
+SandboxBrokerClient::Access(const char* aPath, int aMode)
+{
+ Request req = { SANDBOX_FILE_ACCESS, aMode, 0 };
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Stat(const char* aPath, statstruct* aStat)
+{
+ Request req = { SANDBOX_FILE_STAT, 0, sizeof(statstruct) };
+ return DoCall(&req, aPath, nullptr, (void*)aStat, false);
+}
+
+int
+SandboxBrokerClient::LStat(const char* aPath, statstruct* aStat)
+{
+ Request req = { SANDBOX_FILE_STAT, O_NOFOLLOW, sizeof(statstruct) };
+ return DoCall(&req, aPath, nullptr, (void*)aStat, false);
+}
+
+int
+SandboxBrokerClient::Chmod(const char* aPath, int aMode)
+{
+ Request req = {SANDBOX_FILE_CHMOD, aMode, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Link(const char* aOldPath, const char* aNewPath)
+{
+ Request req = {SANDBOX_FILE_LINK, 0, 0};
+ return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Symlink(const char* aOldPath, const char* aNewPath)
+{
+ Request req = {SANDBOX_FILE_SYMLINK, 0, 0};
+ return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Rename(const char* aOldPath, const char* aNewPath)
+{
+ Request req = {SANDBOX_FILE_RENAME, 0, 0};
+ return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Mkdir(const char* aPath, int aMode)
+{
+ Request req = {SANDBOX_FILE_MKDIR, aMode, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Unlink(const char* aPath)
+{
+ Request req = {SANDBOX_FILE_UNLINK, 0, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Rmdir(const char* aPath)
+{
+ Request req = {SANDBOX_FILE_RMDIR, 0, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Readlink(const char* aPath, void* aBuff, size_t aSize)
+{
+ Request req = {SANDBOX_FILE_READLINK, 0, aSize};
+ return DoCall(&req, aPath, nullptr, aBuff, false);
+}
+
+} // namespace mozilla
+
diff --git a/security/sandbox/linux/SandboxBrokerClient.h b/security/sandbox/linux/SandboxBrokerClient.h
new file mode 100644
index 000000000..06db2f183
--- /dev/null
+++ b/security/sandbox/linux/SandboxBrokerClient.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxBrokerClient_h
+#define mozilla_SandboxBrokerClient_h
+
+#include "broker/SandboxBrokerCommon.h"
+#include "broker/SandboxBrokerUtils.h"
+
+#include "mozilla/Attributes.h"
+
+// This is the client for the sandbox broker described in
+// broker/SandboxBroker.h; its constructor takes the file descriptor
+// returned by SandboxBroker::Create, passed to the child over IPC.
+//
+// The operations exposed here can be called from any thread and in
+// async signal handlers, like the corresponding system calls. The
+// intended use is from a seccomp-bpf SIGSYS handler, to transparently
+// replace those syscalls, but they could also be used directly.
+
+struct stat;
+
+namespace mozilla {
+
+class SandboxBrokerClient final : private SandboxBrokerCommon {
+ public:
+ explicit SandboxBrokerClient(int aFd);
+ ~SandboxBrokerClient();
+
+ int Open(const char* aPath, int aFlags);
+ int Access(const char* aPath, int aMode);
+ int Stat(const char* aPath, statstruct* aStat);
+ int LStat(const char* aPath, statstruct* aStat);
+ int Chmod(const char* aPath, int aMode);
+ int Link(const char* aPath, const char* aPath2);
+ int Mkdir(const char* aPath, int aMode);
+ int Symlink(const char* aOldPath, const char* aNewPath);
+ int Rename(const char* aOldPath, const char* aNewPath);
+ int Unlink(const char* aPath);
+ int Rmdir(const char* aPath);
+ int Readlink(const char* aPath, void* aBuf, size_t aBufSize);
+
+ private:
+ int mFileDesc;
+
+ int DoCall(const Request* aReq,
+ const char* aPath,
+ const char* aPath2,
+ void *aReponseBuff,
+ bool expectFd);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxBrokerClient_h
diff --git a/security/sandbox/linux/SandboxChroot.cpp b/security/sandbox/linux/SandboxChroot.cpp
new file mode 100644
index 000000000..2091bfe05
--- /dev/null
+++ b/security/sandbox/linux/SandboxChroot.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxChroot.h"
+
+#include "SandboxLogging.h"
+#include "LinuxCapabilities.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/NullPtr.h"
+
+#define MOZ_ALWAYS_ZERO(e) MOZ_ALWAYS_TRUE((e) == 0)
+
+namespace mozilla {
+
+SandboxChroot::SandboxChroot()
+{
+ pthread_mutexattr_t attr;
+ MOZ_ALWAYS_ZERO(pthread_mutexattr_init(&attr));
+ MOZ_ALWAYS_ZERO(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK));
+ MOZ_ALWAYS_ZERO(pthread_mutex_init(&mMutex, &attr));
+ MOZ_ALWAYS_ZERO(pthread_cond_init(&mWakeup, nullptr));
+ mCommand = NO_THREAD;
+}
+
+SandboxChroot::~SandboxChroot()
+{
+ SendCommand(JUST_EXIT);
+ MOZ_ALWAYS_ZERO(pthread_mutex_destroy(&mMutex));
+ MOZ_ALWAYS_ZERO(pthread_cond_destroy(&mWakeup));
+}
+
+bool
+SandboxChroot::SendCommand(Command aComm)
+{
+ MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex));
+ if (mCommand == NO_THREAD) {
+ MOZ_RELEASE_ASSERT(aComm == JUST_EXIT);
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ return false;
+ } else {
+ MOZ_ASSERT(mCommand == NO_COMMAND);
+ mCommand = aComm;
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ MOZ_ALWAYS_ZERO(pthread_cond_signal(&mWakeup));
+ void *retval;
+ if (pthread_join(mThread, &retval) != 0 || retval != nullptr) {
+ MOZ_CRASH("Failed to stop privileged chroot thread");
+ }
+ MOZ_ASSERT(mCommand == NO_THREAD);
+ }
+ return true;
+}
+
+static void
+AlwaysClose(int fd)
+{
+ if (IGNORE_EINTR(close(fd)) != 0) {
+ SANDBOX_LOG_ERROR("close: %s", strerror(errno));
+ MOZ_CRASH("failed to close()");
+ }
+}
+
+static int
+OpenDeletedDirectory()
+{
+ // We don't need this directory to persist between invocations of
+ // the program (nor need it to be cleaned up if something goes wrong
+ // here, because mkdtemp will choose a fresh name), so /tmp as
+ // specified by FHS is adequate.
+ //
+ // However, this needs a filesystem where a deleted directory can
+ // still be used, and /tmp is sometimes not that; e.g., aufs(5),
+ // often used for containers, will cause the chroot() to fail with
+ // ESTALE (bug 1162965). So this uses /dev/shm if possible instead.
+ char tmpPath[] = "/tmp/mozsandbox.XXXXXX";
+ char shmPath[] = "/dev/shm/mozsandbox.XXXXXX";
+ char* path;
+ if (mkdtemp(shmPath)) {
+ path = shmPath;
+ } else if (mkdtemp(tmpPath)) {
+ path = tmpPath;
+ } else {
+ SANDBOX_LOG_ERROR("mkdtemp: %s", strerror(errno));
+ return -1;
+ }
+ int fd = HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY));
+ if (fd < 0) {
+ SANDBOX_LOG_ERROR("open %s: %s", path, strerror(errno));
+ // Try to clean up. Shouldn't fail, but livable if it does.
+ DebugOnly<bool> ok = HANDLE_EINTR(rmdir(path)) == 0;
+ MOZ_ASSERT(ok);
+ return -1;
+ }
+ if (HANDLE_EINTR(rmdir(path)) != 0) {
+ SANDBOX_LOG_ERROR("rmdir %s: %s", path, strerror(errno));
+ AlwaysClose(fd);
+ return -1;
+ }
+ return fd;
+}
+
+bool
+SandboxChroot::Prepare()
+{
+ LinuxCapabilities caps;
+ if (!caps.GetCurrent() || !caps.Effective(CAP_SYS_CHROOT)) {
+ SANDBOX_LOG_ERROR("don't have permission to chroot");
+ return false;
+ }
+ mFd = OpenDeletedDirectory();
+ if (mFd < 0) {
+ SANDBOX_LOG_ERROR("failed to create empty directory for chroot");
+ return false;
+ }
+ MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex));
+ MOZ_ASSERT(mCommand == NO_THREAD);
+ if (pthread_create(&mThread, nullptr, StaticThreadMain, this) != 0) {
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ SANDBOX_LOG_ERROR("pthread_create: %s", strerror(errno));
+ return false;
+ }
+ while (mCommand != NO_COMMAND) {
+ MOZ_ASSERT(mCommand == NO_THREAD);
+ MOZ_ALWAYS_ZERO(pthread_cond_wait(&mWakeup, &mMutex));
+ }
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ return true;
+}
+
+void
+SandboxChroot::Invoke()
+{
+ MOZ_ALWAYS_TRUE(SendCommand(DO_CHROOT));
+}
+
+static bool
+ChrootToFileDesc(int fd)
+{
+ if (fchdir(fd) != 0) {
+ SANDBOX_LOG_ERROR("fchdir: %s", strerror(errno));
+ return false;
+ }
+ if (chroot(".") != 0) {
+ SANDBOX_LOG_ERROR("chroot: %s", strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+/* static */ void*
+SandboxChroot::StaticThreadMain(void* aVoidPtr)
+{
+ static_cast<SandboxChroot*>(aVoidPtr)->ThreadMain();
+ return nullptr;
+}
+
+void
+SandboxChroot::ThreadMain()
+{
+ // First, drop everything that isn't CAP_SYS_CHROOT. (This code
+ // assumes that this thread already has effective CAP_SYS_CHROOT,
+ // because Prepare() checked for it before creating this thread.)
+ LinuxCapabilities caps;
+ caps.Effective(CAP_SYS_CHROOT) = true;
+ if (!caps.SetCurrent()) {
+ SANDBOX_LOG_ERROR("capset: %s", strerror(errno));
+ MOZ_CRASH("Can't limit chroot thread's capabilities");
+ }
+
+ MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex));
+ MOZ_ASSERT(mCommand == NO_THREAD);
+ mCommand = NO_COMMAND;
+ MOZ_ALWAYS_ZERO(pthread_cond_signal(&mWakeup));
+ while (mCommand == NO_COMMAND) {
+ MOZ_ALWAYS_ZERO(pthread_cond_wait(&mWakeup, &mMutex));
+ }
+ if (mCommand == DO_CHROOT) {
+ MOZ_ASSERT(mFd >= 0);
+ if (!ChrootToFileDesc(mFd)) {
+ MOZ_CRASH("Failed to chroot");
+ }
+ } else {
+ MOZ_ASSERT(mCommand == JUST_EXIT);
+ }
+ if (mFd >= 0) {
+ AlwaysClose(mFd);
+ mFd = -1;
+ }
+ mCommand = NO_THREAD;
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ // Drop the remaining capabilities; see note in SandboxChroot.h
+ // about the potential unreliability of pthread_join.
+ if (!LinuxCapabilities().SetCurrent()) {
+ MOZ_CRASH("can't drop capabilities");
+ }
+}
+
+} // namespace mozilla
+
+#undef MOZ_ALWAYS_ZERO
diff --git a/security/sandbox/linux/SandboxChroot.h b/security/sandbox/linux/SandboxChroot.h
new file mode 100644
index 000000000..3ad89b732
--- /dev/null
+++ b/security/sandbox/linux/SandboxChroot.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxChroot_h
+#define mozilla_SandboxChroot_h
+
+#include <pthread.h>
+
+#include "mozilla/Attributes.h"
+
+// This class uses the chroot(2) system call and Linux namespaces to
+// revoke the process's access to the filesystem. It requires that
+// the process be able to create user namespaces; this is the
+// kHasUserNamespaces in SandboxInfo.h.
+//
+// Usage: call Prepare() from a thread with CAP_SYS_CHROOT in its
+// effective capability set, then later call Invoke() when ready to
+// drop filesystem access. Prepare() creates a thread to do the
+// chrooting, so the caller can (and should!) drop its own
+// capabilities afterwards. When Invoke() returns, the thread will
+// have exited.
+//
+// (Exception: on Android/B2G <= KitKat, because of how pthread_join
+// is implemented, the thread may still exist, but it will not have
+// capabilities. Accordingly, on such systems, be careful about
+// namespaces or other resources the thread might have inherited.)
+//
+// Prepare() can fail (return false); for example, if it doesn't have
+// CAP_SYS_CHROOT or if it can't create a directory to chroot into.
+//
+// The root directory will be empty and deleted, so the process will
+// not be able to create new entries in it regardless of permissions.
+
+namespace mozilla {
+
+class SandboxChroot final {
+public:
+ SandboxChroot();
+ ~SandboxChroot();
+ bool Prepare();
+ void Invoke();
+private:
+ enum Command {
+ NO_THREAD,
+ NO_COMMAND,
+ DO_CHROOT,
+ JUST_EXIT,
+ };
+
+ pthread_t mThread;
+ pthread_mutex_t mMutex;
+ pthread_cond_t mWakeup;
+ Command mCommand;
+ int mFd;
+
+ void ThreadMain();
+ static void* StaticThreadMain(void* aVoidPtr);
+ bool SendCommand(Command aComm);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxChroot_h
diff --git a/security/sandbox/linux/SandboxFilter.cpp b/security/sandbox/linux/SandboxFilter.cpp
new file mode 100644
index 000000000..f8db9dc80
--- /dev/null
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -0,0 +1,961 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxFilter.h"
+#include "SandboxFilterUtil.h"
+
+#include "SandboxBrokerClient.h"
+#include "SandboxInfo.h"
+#include "SandboxInternal.h"
+#include "SandboxLogging.h"
+
+#include "mozilla/UniquePtr.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/ipc.h>
+#include <linux/net.h>
+#include <linux/prctl.h>
+#include <linux/sched.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "sandbox/linux/bpf_dsl/bpf_dsl.h"
+#include "sandbox/linux/system_headers/linux_seccomp.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+using namespace sandbox::bpf_dsl;
+#define CASES SANDBOX_BPF_DSL_CASES
+
+// Fill in defines in case of old headers.
+// (Warning: these are wrong on PA-RISC.)
+#ifndef MADV_NOHUGEPAGE
+#define MADV_NOHUGEPAGE 15
+#endif
+#ifndef MADV_DONTDUMP
+#define MADV_DONTDUMP 16
+#endif
+
+// Added in Linux 4.5; see bug 1303813.
+#ifndef MADV_FREE
+#define MADV_FREE 8
+#endif
+
+#ifndef PR_SET_PTRACER
+#define PR_SET_PTRACER 0x59616d61
+#endif
+
+// To avoid visual confusion between "ifdef ANDROID" and "ifndef ANDROID":
+#ifndef ANDROID
+#define DESKTOP
+#endif
+
+// This file defines the seccomp-bpf system call filter policies.
+// See also SandboxFilterUtil.h, for the CASES_FOR_* macros and
+// SandboxFilterBase::Evaluate{Socket,Ipc}Call.
+//
+// One important difference from how Chromium bpf_dsl filters are
+// normally interpreted: returning -ENOSYS from a Trap() handler
+// indicates an unexpected system call; SigSysHandler() in Sandbox.cpp
+// will detect this, request a crash dump, and terminate the process.
+// This does not apply to using Error(ENOSYS) in the policy, so that
+// can be used if returning an actual ENOSYS is needed.
+
+namespace mozilla {
+
+// This class whitelists everything used by the sandbox itself, by the
+// core IPC code, by the crash reporter, or other core code.
+class SandboxPolicyCommon : public SandboxPolicyBase
+{
+protected:
+ typedef const sandbox::arch_seccomp_data& ArgsRef;
+
+ static intptr_t BlockedSyscallTrap(ArgsRef aArgs, void *aux) {
+ MOZ_ASSERT(!aux);
+ return -ENOSYS;
+ }
+
+private:
+#if defined(ANDROID) && ANDROID_VERSION < 16
+ // Bug 1093893: Translate tkill to tgkill for pthread_kill; fixed in
+ // bionic commit 10c8ce59a (in JB and up; API level 16 = Android 4.1).
+ static intptr_t TKillCompatTrap(const sandbox::arch_seccomp_data& aArgs,
+ void *aux)
+ {
+ return syscall(__NR_tgkill, getpid(), aArgs.args[0], aArgs.args[1]);
+ }
+#endif
+
+ static intptr_t SetNoNewPrivsTrap(ArgsRef& aArgs, void* aux) {
+ if (gSetSandboxFilter == nullptr) {
+ // Called after BroadcastSetThreadSandbox finished, therefore
+ // not our doing and not expected.
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ // Signal that the filter is already in place.
+ return -ETXTBSY;
+ }
+
+public:
+ virtual ResultExpr InvalidSyscall() const override {
+ return Trap(BlockedSyscallTrap, nullptr);
+ }
+
+ virtual ResultExpr ClonePolicy(ResultExpr failPolicy) const {
+ // Allow use for simple thread creation (pthread_create) only.
+
+ // WARNING: s390 and cris pass the flags in the second arg -- see
+ // CLONE_BACKWARDS2 in arch/Kconfig in the kernel source -- but we
+ // don't support seccomp-bpf on those archs yet.
+ Arg<int> flags(0);
+
+ // The glibc source hasn't changed the thread creation clone flags
+ // since 2004, so this *should* be safe to hard-code. Bionic's
+ // value has changed a few times, and has converged on the same one
+ // as glibc; allow any of them.
+ static const int flags_common = CLONE_VM | CLONE_FS | CLONE_FILES |
+ CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM;
+ static const int flags_modern = flags_common | CLONE_SETTLS |
+ CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
+
+ // Can't use CASES here because its decltype magic infers const
+ // int instead of regular int and bizarre voluminous errors issue
+ // forth from the depths of the standard library implementation.
+ return Switch(flags)
+#ifdef ANDROID
+ .Case(flags_common | CLONE_DETACHED, Allow()) // <= JB 4.2
+ .Case(flags_common, Allow()) // JB 4.3 or KK 4.4
+#endif
+ .Case(flags_modern, Allow()) // Android L or glibc
+ .Default(failPolicy);
+ }
+
+ virtual ResultExpr PrctlPolicy() const {
+ // Note: this will probably need PR_SET_VMA if/when it's used on
+ // Android without being overridden by an allow-all policy, and
+ // the constant will need to be defined locally.
+ Arg<int> op(0);
+ return Switch(op)
+ .CASES((PR_GET_SECCOMP, // BroadcastSetThreadSandbox, etc.
+ PR_SET_NAME, // Thread creation
+ PR_SET_DUMPABLE, // Crash reporting
+ PR_SET_PTRACER), // Debug-mode crash handling
+ Allow())
+ .Default(InvalidSyscall());
+ }
+
+ virtual Maybe<ResultExpr> EvaluateSocketCall(int aCall) const override {
+ switch (aCall) {
+ case SYS_RECVMSG:
+ case SYS_SENDMSG:
+ return Some(Allow());
+ default:
+ return Nothing();
+ }
+ }
+
+ virtual ResultExpr EvaluateSyscall(int sysno) const override {
+ switch (sysno) {
+ // Timekeeping
+ case __NR_clock_gettime: {
+ Arg<clockid_t> clk_id(0);
+ return If(clk_id == CLOCK_MONOTONIC, Allow())
+#ifdef CLOCK_MONOTONIC_COARSE
+ .ElseIf(clk_id == CLOCK_MONOTONIC_COARSE, Allow())
+#endif
+ .ElseIf(clk_id == CLOCK_PROCESS_CPUTIME_ID, Allow())
+ .ElseIf(clk_id == CLOCK_REALTIME, Allow())
+#ifdef CLOCK_REALTIME_COARSE
+ .ElseIf(clk_id == CLOCK_REALTIME_COARSE, Allow())
+#endif
+ .ElseIf(clk_id == CLOCK_THREAD_CPUTIME_ID, Allow())
+ .Else(InvalidSyscall());
+ }
+ case __NR_gettimeofday:
+#ifdef __NR_time
+ case __NR_time:
+#endif
+ case __NR_nanosleep:
+ return Allow();
+
+ // Thread synchronization
+ case __NR_futex:
+ // FIXME: This could be more restrictive....
+ return Allow();
+
+ // Asynchronous I/O
+ case __NR_epoll_wait:
+ case __NR_epoll_pwait:
+ case __NR_epoll_ctl:
+ case __NR_ppoll:
+ case __NR_poll:
+ return Allow();
+
+ // Used when requesting a crash dump.
+ case __NR_pipe:
+ return Allow();
+
+ // Metadata of opened files
+ CASES_FOR_fstat:
+ return Allow();
+
+ // Simple I/O
+ case __NR_write:
+ case __NR_read:
+ case __NR_readv:
+ case __NR_writev: // see SandboxLogging.cpp
+ CASES_FOR_lseek:
+ return Allow();
+
+ // Memory mapping
+ CASES_FOR_mmap:
+ case __NR_munmap:
+ return Allow();
+
+ // Signal handling
+#if defined(ANDROID) || defined(MOZ_ASAN)
+ case __NR_sigaltstack:
+#endif
+ CASES_FOR_sigreturn:
+ CASES_FOR_sigprocmask:
+ CASES_FOR_sigaction:
+ return Allow();
+
+ // Send signals within the process (raise(), profiling, etc.)
+ case __NR_tgkill: {
+ Arg<pid_t> tgid(0);
+ return If(tgid == getpid(), Allow())
+ .Else(InvalidSyscall());
+ }
+
+#if defined(ANDROID) && ANDROID_VERSION < 16
+ // Polyfill with tgkill; see above.
+ case __NR_tkill:
+ return Trap(TKillCompatTrap, nullptr);
+#endif
+
+ // Yield
+ case __NR_sched_yield:
+ return Allow();
+
+ // Thread creation.
+ case __NR_clone:
+ return ClonePolicy(InvalidSyscall());
+
+ // More thread creation.
+#ifdef __NR_set_robust_list
+ case __NR_set_robust_list:
+ return Allow();
+#endif
+#ifdef ANDROID
+ case __NR_set_tid_address:
+ return Allow();
+#endif
+
+ // prctl
+ case __NR_prctl: {
+ if (SandboxInfo::Get().Test(SandboxInfo::kHasSeccompTSync)) {
+ return PrctlPolicy();
+ }
+
+ Arg<int> option(0);
+ return If(option == PR_SET_NO_NEW_PRIVS,
+ Trap(SetNoNewPrivsTrap, nullptr))
+ .Else(PrctlPolicy());
+ }
+
+ // NSPR can call this when creating a thread, but it will accept a
+ // polite "no".
+ case __NR_getpriority:
+ // But if thread creation races with sandbox startup, that call
+ // could succeed, and then we get one of these:
+ case __NR_setpriority:
+ return Error(EACCES);
+
+ // Stack bounds are obtained via pthread_getattr_np, which calls
+ // this but doesn't actually need it:
+ case __NR_sched_getaffinity:
+ return Error(ENOSYS);
+
+ // Read own pid/tid.
+ case __NR_getpid:
+ case __NR_gettid:
+ return Allow();
+
+ // Discard capabilities
+ case __NR_close:
+ return Allow();
+
+ // Machine-dependent stuff
+#ifdef __arm__
+ case __ARM_NR_breakpoint:
+ case __ARM_NR_cacheflush:
+ case __ARM_NR_usr26: // FIXME: do we actually need this?
+ case __ARM_NR_usr32:
+ case __ARM_NR_set_tls:
+ return Allow();
+#endif
+
+ // Needed when being debugged:
+ case __NR_restart_syscall:
+ return Allow();
+
+ // Terminate threads or the process
+ case __NR_exit:
+ case __NR_exit_group:
+ return Allow();
+
+#ifdef MOZ_ASAN
+ // ASAN's error reporter wants to know if stderr is a tty.
+ case __NR_ioctl: {
+ Arg<int> fd(0);
+ return If(fd == STDERR_FILENO, Allow())
+ .Else(InvalidSyscall());
+ }
+
+ // ...and before compiler-rt r209773, it will call readlink on
+ // /proc/self/exe and use the cached value only if that fails:
+ case __NR_readlink:
+ case __NR_readlinkat:
+ return Error(ENOENT);
+
+ // ...and if it found an external symbolizer, it will try to run it:
+ // (See also bug 1081242 comment #7.)
+ CASES_FOR_stat:
+ return Error(ENOENT);
+#endif
+
+ default:
+ return SandboxPolicyBase::EvaluateSyscall(sysno);
+ }
+ }
+};
+
+// The process-type-specific syscall rules start here:
+
+#ifdef MOZ_CONTENT_SANDBOX
+// The seccomp-bpf filter for content processes is not a true sandbox
+// on its own; its purpose is attack surface reduction and syscall
+// interception in support of a semantic sandboxing layer. On B2G
+// this is the Android process permission model; on desktop,
+// namespaces and chroot() will be used.
+class ContentSandboxPolicy : public SandboxPolicyCommon {
+ SandboxBrokerClient* mBroker;
+
+ // Trap handlers for filesystem brokering.
+ // (The amount of code duplication here could be improved....)
+#ifdef __NR_open
+ static intptr_t OpenTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto flags = static_cast<int>(aArgs.args[1]);
+ return broker->Open(path, flags);
+ }
+#endif
+
+ static intptr_t OpenAtTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto fd = static_cast<int>(aArgs.args[0]);
+ auto path = reinterpret_cast<const char*>(aArgs.args[1]);
+ auto flags = static_cast<int>(aArgs.args[2]);
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG_ERROR("unsupported fd-relative openat(%d, \"%s\", 0%o)",
+ fd, path, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Open(path, flags);
+ }
+
+#ifdef __NR_access
+ static intptr_t AccessTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto mode = static_cast<int>(aArgs.args[1]);
+ return broker->Access(path, mode);
+ }
+#endif
+
+ static intptr_t AccessAtTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto fd = static_cast<int>(aArgs.args[0]);
+ auto path = reinterpret_cast<const char*>(aArgs.args[1]);
+ auto mode = static_cast<int>(aArgs.args[2]);
+ // Linux's faccessat syscall has no "flags" argument. Attempting
+ // to handle the flags != 0 case is left to userspace; this is
+ // impossible to do correctly in all cases, but that's not our
+ // problem.
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG_ERROR("unsupported fd-relative faccessat(%d, \"%s\", %d)",
+ fd, path, mode);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Access(path, mode);
+ }
+
+ static intptr_t StatTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto buf = reinterpret_cast<statstruct*>(aArgs.args[1]);
+ return broker->Stat(path, buf);
+ }
+
+ static intptr_t LStatTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto buf = reinterpret_cast<statstruct*>(aArgs.args[1]);
+ return broker->LStat(path, buf);
+ }
+
+ static intptr_t StatAtTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto fd = static_cast<int>(aArgs.args[0]);
+ auto path = reinterpret_cast<const char*>(aArgs.args[1]);
+ auto buf = reinterpret_cast<statstruct*>(aArgs.args[2]);
+ auto flags = static_cast<int>(aArgs.args[3]);
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG_ERROR("unsupported fd-relative fstatat(%d, \"%s\", %p, %d)",
+ fd, path, buf, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ if ((flags & ~AT_SYMLINK_NOFOLLOW) != 0) {
+ SANDBOX_LOG_ERROR("unsupported flags %d in fstatat(%d, \"%s\", %p, %d)",
+ (flags & ~AT_SYMLINK_NOFOLLOW), fd, path, buf, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return (flags & AT_SYMLINK_NOFOLLOW) == 0
+ ? broker->Stat(path, buf)
+ : broker->LStat(path, buf);
+ }
+
+ static intptr_t ChmodTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto mode = static_cast<mode_t>(aArgs.args[1]);
+ return broker->Chmod(path, mode);
+ }
+
+ static intptr_t LinkTrap(ArgsRef aArgs, void *aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
+ return broker->Link(path, path2);
+ }
+
+ static intptr_t SymlinkTrap(ArgsRef aArgs, void *aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
+ return broker->Symlink(path, path2);
+ }
+
+ static intptr_t RenameTrap(ArgsRef aArgs, void *aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
+ return broker->Rename(path, path2);
+ }
+
+ static intptr_t MkdirTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto mode = static_cast<mode_t>(aArgs.args[1]);
+ return broker->Mkdir(path, mode);
+ }
+
+ static intptr_t RmdirTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ return broker->Rmdir(path);
+ }
+
+ static intptr_t UnlinkTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ return broker->Unlink(path);
+ }
+
+ static intptr_t ReadlinkTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto buf = reinterpret_cast<char*>(aArgs.args[1]);
+ auto size = static_cast<size_t>(aArgs.args[2]);
+ return broker->Readlink(path, buf, size);
+ }
+
+ static intptr_t GetPPidTrap(ArgsRef aArgs, void* aux) {
+ // In a pid namespace, getppid() will return 0. We will return 0 instead
+ // of the real parent pid to see what breaks when we introduce the
+ // pid namespace (Bug 1151624).
+ return 0;
+ }
+
+public:
+ explicit ContentSandboxPolicy(SandboxBrokerClient* aBroker):mBroker(aBroker) { }
+ virtual ~ContentSandboxPolicy() { }
+ virtual ResultExpr PrctlPolicy() const override {
+ // Ideally this should be restricted to a whitelist, but content
+ // uses enough things that it's not trivial to determine it.
+ return Allow();
+ }
+ virtual Maybe<ResultExpr> EvaluateSocketCall(int aCall) const override {
+ switch(aCall) {
+ case SYS_RECVFROM:
+ case SYS_SENDTO:
+ return Some(Allow());
+
+ case SYS_SOCKETPAIR: {
+ // See bug 1066750.
+ if (!kSocketCallHasArgs) {
+ // We can't filter the args if the platform passes them by pointer.
+ return Some(Allow());
+ }
+ Arg<int> domain(0), type(1);
+ return Some(If(AllOf(domain == AF_UNIX,
+ AnyOf(type == SOCK_STREAM, type == SOCK_SEQPACKET)),
+ Allow())
+ .Else(InvalidSyscall()));
+ }
+
+#ifdef ANDROID
+ case SYS_SOCKET:
+ return Some(Error(EACCES));
+#else // #ifdef DESKTOP
+ case SYS_RECV:
+ case SYS_SEND:
+ case SYS_SOCKET: // DANGEROUS
+ case SYS_CONNECT: // DANGEROUS
+ case SYS_ACCEPT:
+ case SYS_ACCEPT4:
+ case SYS_BIND:
+ case SYS_LISTEN:
+ case SYS_GETSOCKOPT:
+ case SYS_SETSOCKOPT:
+ case SYS_GETSOCKNAME:
+ case SYS_GETPEERNAME:
+ case SYS_SHUTDOWN:
+ return Some(Allow());
+#endif
+ default:
+ return SandboxPolicyCommon::EvaluateSocketCall(aCall);
+ }
+ }
+
+#ifdef DESKTOP
+ virtual Maybe<ResultExpr> EvaluateIpcCall(int aCall) const override {
+ switch(aCall) {
+ // These are a problem: SysV shared memory follows the Unix
+ // "same uid policy" and can't be restricted/brokered like file
+ // access. But the graphics layer might not be using them
+ // anymore; this needs to be studied.
+ case SHMGET:
+ case SHMCTL:
+ case SHMAT:
+ case SHMDT:
+ case SEMGET:
+ case SEMCTL:
+ case SEMOP:
+ case MSGGET:
+ return Some(Allow());
+ default:
+ return SandboxPolicyCommon::EvaluateIpcCall(aCall);
+ }
+ }
+#endif
+
+ virtual ResultExpr EvaluateSyscall(int sysno) const override {
+ if (mBroker) {
+ // Have broker; route the appropriate syscalls to it.
+ switch (sysno) {
+ case __NR_open:
+ return Trap(OpenTrap, mBroker);
+ case __NR_openat:
+ return Trap(OpenAtTrap, mBroker);
+ case __NR_access:
+ return Trap(AccessTrap, mBroker);
+ case __NR_faccessat:
+ return Trap(AccessAtTrap, mBroker);
+ CASES_FOR_stat:
+ return Trap(StatTrap, mBroker);
+ CASES_FOR_lstat:
+ return Trap(LStatTrap, mBroker);
+ CASES_FOR_fstatat:
+ return Trap(StatAtTrap, mBroker);
+ case __NR_chmod:
+ return Trap(ChmodTrap, mBroker);
+ case __NR_link:
+ return Trap(LinkTrap, mBroker);
+ case __NR_mkdir:
+ return Trap(MkdirTrap, mBroker);
+ case __NR_symlink:
+ return Trap(SymlinkTrap, mBroker);
+ case __NR_rename:
+ return Trap(RenameTrap, mBroker);
+ case __NR_rmdir:
+ return Trap(RmdirTrap, mBroker);
+ case __NR_unlink:
+ return Trap(UnlinkTrap, mBroker);
+ case __NR_readlink:
+ return Trap(ReadlinkTrap, mBroker);
+ }
+ } else {
+ // No broker; allow the syscalls directly. )-:
+ switch(sysno) {
+ case __NR_open:
+ case __NR_openat:
+ case __NR_access:
+ case __NR_faccessat:
+ CASES_FOR_stat:
+ CASES_FOR_lstat:
+ CASES_FOR_fstatat:
+ case __NR_chmod:
+ case __NR_link:
+ case __NR_mkdir:
+ case __NR_symlink:
+ case __NR_rename:
+ case __NR_rmdir:
+ case __NR_unlink:
+ case __NR_readlink:
+ return Allow();
+ }
+ }
+
+ switch (sysno) {
+#ifdef DESKTOP
+ case __NR_getppid:
+ return Trap(GetPPidTrap, nullptr);
+
+ // Filesystem syscalls that need more work to determine who's
+ // using them, if they need to be, and what we intend to about it.
+ case __NR_getcwd:
+ CASES_FOR_statfs:
+ CASES_FOR_fstatfs:
+ case __NR_quotactl:
+ CASES_FOR_fchown:
+ case __NR_fchmod:
+ case __NR_flock:
+#endif
+ return Allow();
+
+ case __NR_readlinkat:
+#ifdef DESKTOP
+ // Bug 1290896
+ return Allow();
+#else
+ // Workaround for bug 964455:
+ return Error(EINVAL);
+#endif
+
+ CASES_FOR_select:
+ case __NR_pselect6:
+ return Allow();
+
+ CASES_FOR_getdents:
+ CASES_FOR_ftruncate:
+ case __NR_writev:
+ case __NR_pread64:
+#ifdef DESKTOP
+ case __NR_pwrite64:
+ case __NR_readahead:
+#endif
+ return Allow();
+
+ case __NR_ioctl:
+ // ioctl() is for GL. Remove when GL proxy is implemented.
+ // Additionally ioctl() might be a place where we want to have
+ // argument filtering
+ return Allow();
+
+ CASES_FOR_fcntl:
+ // Some fcntls have significant side effects like sending
+ // arbitrary signals, and there's probably nontrivial kernel
+ // attack surface; this should be locked down more if possible.
+ return Allow();
+
+ case __NR_mprotect:
+ case __NR_brk:
+ case __NR_madvise:
+#if !defined(MOZ_MEMORY)
+ // libc's realloc uses mremap (Bug 1286119).
+ case __NR_mremap:
+#endif
+ return Allow();
+
+ case __NR_sigaltstack:
+ return Allow();
+
+#ifdef __NR_set_thread_area
+ case __NR_set_thread_area:
+ return Allow();
+#endif
+
+ case __NR_getrusage:
+ case __NR_times:
+ return Allow();
+
+ case __NR_dup:
+ return Allow();
+
+ CASES_FOR_getuid:
+ CASES_FOR_getgid:
+ CASES_FOR_geteuid:
+ CASES_FOR_getegid:
+ return Allow();
+
+ case __NR_fsync:
+ case __NR_msync:
+ return Allow();
+
+ case __NR_getpriority:
+ case __NR_setpriority:
+ case __NR_sched_get_priority_min:
+ case __NR_sched_get_priority_max:
+ case __NR_sched_getscheduler:
+ case __NR_sched_setscheduler:
+ case __NR_sched_getparam:
+ case __NR_sched_setparam:
+#ifdef DESKTOP
+ case __NR_sched_getaffinity:
+#endif
+ return Allow();
+
+#ifdef DESKTOP
+ case __NR_pipe2:
+ return Allow();
+
+ CASES_FOR_getrlimit:
+ case __NR_clock_getres:
+ CASES_FOR_getresuid:
+ CASES_FOR_getresgid:
+ return Allow();
+
+ case __NR_umask:
+ case __NR_kill:
+ case __NR_wait4:
+#ifdef __NR_waitpid
+ case __NR_waitpid:
+#endif
+#ifdef __NR_arch_prctl
+ case __NR_arch_prctl:
+#endif
+ return Allow();
+
+ case __NR_eventfd2:
+ case __NR_inotify_init1:
+ case __NR_inotify_add_watch:
+ case __NR_inotify_rm_watch:
+ return Allow();
+
+#ifdef __NR_memfd_create
+ case __NR_memfd_create:
+ return Allow();
+#endif
+
+#ifdef __NR_rt_tgsigqueueinfo
+ // Only allow to send signals within the process.
+ case __NR_rt_tgsigqueueinfo: {
+ Arg<pid_t> tgid(0);
+ return If(tgid == getpid(), Allow())
+ .Else(InvalidSyscall());
+ }
+#endif
+
+ case __NR_mlock:
+ case __NR_munlock:
+ return Allow();
+
+ // We can't usefully allow fork+exec, even on a temporary basis;
+ // the child would inherit the seccomp-bpf policy and almost
+ // certainly die from an unexpected SIGSYS. We also can't have
+ // fork() crash, currently, because there are too many system
+ // libraries/plugins that try to run commands. But they can
+ // usually do something reasonable on error.
+ case __NR_clone:
+ return ClonePolicy(Error(EPERM));
+
+#ifdef __NR_fadvise64
+ case __NR_fadvise64:
+ return Allow();
+#endif
+
+#ifdef __NR_fadvise64_64
+ case __NR_fadvise64_64:
+ return Allow();
+#endif
+
+ case __NR_fallocate:
+ return Allow();
+
+ case __NR_get_mempolicy:
+ return Allow();
+
+#endif // DESKTOP
+
+#ifdef __NR_getrandom
+ case __NR_getrandom:
+ return Allow();
+#endif
+
+ // nsSystemInfo uses uname (and we cache an instance, so
+ // the info remains present even if we block the syscall)
+ case __NR_uname:
+#ifdef DESKTOP
+ case __NR_sysinfo:
+#endif
+ return Allow();
+
+#ifdef MOZ_JPROF
+ case __NR_setitimer:
+ return Allow();
+#endif // MOZ_JPROF
+
+ default:
+ return SandboxPolicyCommon::EvaluateSyscall(sysno);
+ }
+ }
+};
+
+UniquePtr<sandbox::bpf_dsl::Policy>
+GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker)
+{
+ return UniquePtr<sandbox::bpf_dsl::Policy>(new ContentSandboxPolicy(aMaybeBroker));
+}
+#endif // MOZ_CONTENT_SANDBOX
+
+
+#ifdef MOZ_GMP_SANDBOX
+// Unlike for content, the GeckoMediaPlugin seccomp-bpf policy needs
+// to be an effective sandbox by itself, because we allow GMP on Linux
+// systems where that's the only sandboxing mechanism we can use.
+//
+// Be especially careful about what this policy allows.
+class GMPSandboxPolicy : public SandboxPolicyCommon {
+ static intptr_t OpenTrap(const sandbox::arch_seccomp_data& aArgs,
+ void* aux)
+ {
+ auto plugin = static_cast<SandboxOpenedFile*>(aux);
+ const char* path;
+ int flags;
+
+ switch (aArgs.nr) {
+#ifdef __NR_open
+ case __NR_open:
+ path = reinterpret_cast<const char*>(aArgs.args[0]);
+ flags = static_cast<int>(aArgs.args[1]);
+ break;
+#endif
+ case __NR_openat:
+ // The path has to be absolute to match the pre-opened file (see
+ // assertion in ctor) so the dirfd argument is ignored.
+ path = reinterpret_cast<const char*>(aArgs.args[1]);
+ flags = static_cast<int>(aArgs.args[2]);
+ break;
+ default:
+ MOZ_CRASH("unexpected syscall number");
+ }
+
+ if (strcmp(path, plugin->mPath) != 0) {
+ SANDBOX_LOG_ERROR("attempt to open file %s (flags=0%o) which is not the"
+ " media plugin %s", path, flags, plugin->mPath);
+ return -EPERM;
+ }
+ if ((flags & O_ACCMODE) != O_RDONLY) {
+ SANDBOX_LOG_ERROR("non-read-only open of file %s attempted (flags=0%o)",
+ path, flags);
+ return -EPERM;
+ }
+ int fd = plugin->mFd.exchange(-1);
+ if (fd < 0) {
+ SANDBOX_LOG_ERROR("multiple opens of media plugin file unimplemented");
+ return -ENOSYS;
+ }
+ return fd;
+ }
+
+ static intptr_t SchedTrap(const sandbox::arch_seccomp_data& aArgs,
+ void* aux)
+ {
+ const pid_t tid = syscall(__NR_gettid);
+ if (aArgs.args[0] == static_cast<uint64_t>(tid)) {
+ return syscall(aArgs.nr,
+ 0,
+ aArgs.args[1],
+ aArgs.args[2],
+ aArgs.args[3],
+ aArgs.args[4],
+ aArgs.args[5]);
+ }
+ SANDBOX_LOG_ERROR("unsupported tid in SchedTrap");
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+
+ SandboxOpenedFile* mPlugin;
+public:
+ explicit GMPSandboxPolicy(SandboxOpenedFile* aPlugin)
+ : mPlugin(aPlugin)
+ {
+ MOZ_ASSERT(aPlugin->mPath[0] == '/', "plugin path should be absolute");
+ }
+
+ virtual ~GMPSandboxPolicy() { }
+
+ virtual ResultExpr EvaluateSyscall(int sysno) const override {
+ switch (sysno) {
+ // Simulate opening the plugin file.
+#ifdef __NR_open
+ case __NR_open:
+#endif
+ case __NR_openat:
+ return Trap(OpenTrap, mPlugin);
+
+ // ipc::Shmem
+ case __NR_mprotect:
+ return Allow();
+ case __NR_madvise: {
+ Arg<int> advice(2);
+ return If(advice == MADV_DONTNEED, Allow())
+ .ElseIf(advice == MADV_FREE, Allow())
+#ifdef MOZ_ASAN
+ .ElseIf(advice == MADV_NOHUGEPAGE, Allow())
+ .ElseIf(advice == MADV_DONTDUMP, Allow())
+#endif
+ .Else(InvalidSyscall());
+ }
+ case __NR_brk:
+ CASES_FOR_geteuid:
+ return Allow();
+ case __NR_sched_getparam:
+ case __NR_sched_getscheduler:
+ case __NR_sched_get_priority_min:
+ case __NR_sched_get_priority_max:
+ case __NR_sched_setscheduler: {
+ Arg<pid_t> pid(0);
+ return If(pid == 0, Allow())
+ .Else(Trap(SchedTrap, nullptr));
+ }
+
+ // For clock(3) on older glibcs; bug 1304220.
+ case __NR_times:
+ return Allow();
+
+ default:
+ return SandboxPolicyCommon::EvaluateSyscall(sysno);
+ }
+ }
+};
+
+UniquePtr<sandbox::bpf_dsl::Policy>
+GetMediaSandboxPolicy(SandboxOpenedFile* aPlugin)
+{
+ return UniquePtr<sandbox::bpf_dsl::Policy>(new GMPSandboxPolicy(aPlugin));
+}
+
+#endif // MOZ_GMP_SANDBOX
+
+}
diff --git a/security/sandbox/linux/SandboxFilter.h b/security/sandbox/linux/SandboxFilter.h
new file mode 100644
index 000000000..6b1cb47f4
--- /dev/null
+++ b/security/sandbox/linux/SandboxFilter.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxFilter_h
+#define mozilla_SandboxFilter_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/UniquePtr.h"
+
+namespace sandbox {
+namespace bpf_dsl {
+class Policy;
+}
+}
+
+namespace mozilla {
+
+#ifdef MOZ_CONTENT_SANDBOX
+class SandboxBrokerClient;
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker);
+#endif
+
+#ifdef MOZ_GMP_SANDBOX
+struct SandboxOpenedFile {
+ const char *mPath;
+ Atomic<int> mFd;
+};
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetMediaSandboxPolicy(SandboxOpenedFile* aPlugin);
+#endif
+
+} // namespace mozilla
+
+#endif
diff --git a/security/sandbox/linux/SandboxFilterUtil.cpp b/security/sandbox/linux/SandboxFilterUtil.cpp
new file mode 100644
index 000000000..04fd6709c
--- /dev/null
+++ b/security/sandbox/linux/SandboxFilterUtil.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxFilterUtil.h"
+
+#ifndef ANDROID
+#include <linux/ipc.h>
+#endif
+#include <linux/net.h>
+
+#include "mozilla/UniquePtr.h"
+#include "sandbox/linux/bpf_dsl/bpf_dsl.h"
+
+// Older kernel headers (mostly Android, but also some older desktop
+// distributions) are missing some or all of these:
+#ifndef SYS_ACCEPT4
+#define SYS_ACCEPT4 18
+#endif
+#ifndef SYS_RECVMMSG
+#define SYS_RECVMMSG 19
+#endif
+#ifndef SYS_SENDMMSG
+#define SYS_SENDMMSG 20
+#endif
+
+using namespace sandbox::bpf_dsl;
+#define CASES SANDBOX_BPF_DSL_CASES
+
+namespace mozilla {
+
+sandbox::bpf_dsl::ResultExpr
+SandboxPolicyBase::EvaluateSyscall(int aSysno) const {
+ switch (aSysno) {
+#ifdef __NR_socketcall
+ case __NR_socketcall: {
+ Arg<int> call(0);
+ UniquePtr<Caser<int>> acc(new Caser<int>(Switch(call)));
+ for (int i = SYS_SOCKET; i <= SYS_SENDMMSG; ++i) {
+ auto thisCase = EvaluateSocketCall(i);
+ // Optimize out cases that are equal to the default.
+ if (thisCase) {
+ acc.reset(new Caser<int>(acc->Case(i, *thisCase)));
+ }
+ }
+ return acc->Default(InvalidSyscall());
+ }
+#ifndef ANDROID
+ case __NR_ipc: {
+ Arg<int> callAndVersion(0);
+ auto call = callAndVersion & 0xFFFF;
+ UniquePtr<Caser<int>> acc(new Caser<int>(Switch(call)));
+ for (int i = SEMOP; i <= DIPC; ++i) {
+ auto thisCase = EvaluateIpcCall(i);
+ // Optimize out cases that are equal to the default.
+ if (thisCase) {
+ acc.reset(new Caser<int>(acc->Case(i, *thisCase)));
+ }
+ }
+ return acc->Default(InvalidSyscall());
+ }
+#endif // ANDROID
+#endif // __NR_socketcall
+#define DISPATCH_SOCKETCALL(sysnum, socketnum) \
+ case sysnum: \
+ return EvaluateSocketCall(socketnum).valueOr(InvalidSyscall())
+#ifdef __NR_socket
+ DISPATCH_SOCKETCALL(__NR_socket, SYS_SOCKET);
+ DISPATCH_SOCKETCALL(__NR_bind, SYS_BIND);
+ DISPATCH_SOCKETCALL(__NR_connect, SYS_CONNECT);
+ DISPATCH_SOCKETCALL(__NR_listen, SYS_LISTEN);
+#ifdef __NR_accept
+ DISPATCH_SOCKETCALL(__NR_accept, SYS_ACCEPT);
+#endif
+ DISPATCH_SOCKETCALL(__NR_getsockname, SYS_GETSOCKNAME);
+ DISPATCH_SOCKETCALL(__NR_getpeername, SYS_GETPEERNAME);
+ DISPATCH_SOCKETCALL(__NR_socketpair, SYS_SOCKETPAIR);
+#ifdef __NR_send
+ DISPATCH_SOCKETCALL(__NR_send, SYS_SEND);
+ DISPATCH_SOCKETCALL(__NR_recv, SYS_RECV);
+#endif // __NR_send
+ DISPATCH_SOCKETCALL(__NR_sendto, SYS_SENDTO);
+ DISPATCH_SOCKETCALL(__NR_recvfrom, SYS_RECVFROM);
+ DISPATCH_SOCKETCALL(__NR_shutdown, SYS_SHUTDOWN);
+ DISPATCH_SOCKETCALL(__NR_setsockopt, SYS_SETSOCKOPT);
+ DISPATCH_SOCKETCALL(__NR_getsockopt, SYS_GETSOCKOPT);
+ DISPATCH_SOCKETCALL(__NR_sendmsg, SYS_SENDMSG);
+ DISPATCH_SOCKETCALL(__NR_recvmsg, SYS_RECVMSG);
+ DISPATCH_SOCKETCALL(__NR_accept4, SYS_ACCEPT4);
+ DISPATCH_SOCKETCALL(__NR_recvmmsg, SYS_RECVMMSG);
+ DISPATCH_SOCKETCALL(__NR_sendmmsg, SYS_SENDMMSG);
+#endif // __NR_socket
+#undef DISPATCH_SOCKETCALL
+#ifndef __NR_socketcall
+#ifndef ANDROID
+#define DISPATCH_SYSVCALL(sysnum, ipcnum) \
+ case sysnum: \
+ return EvaluateIpcCall(ipcnum).valueOr(InvalidSyscall())
+ DISPATCH_SYSVCALL(__NR_semop, SEMOP);
+ DISPATCH_SYSVCALL(__NR_semget, SEMGET);
+ DISPATCH_SYSVCALL(__NR_semctl, SEMCTL);
+ DISPATCH_SYSVCALL(__NR_semtimedop, SEMTIMEDOP);
+ DISPATCH_SYSVCALL(__NR_msgsnd, MSGSND);
+ DISPATCH_SYSVCALL(__NR_msgrcv, MSGRCV);
+ DISPATCH_SYSVCALL(__NR_msgget, MSGGET);
+ DISPATCH_SYSVCALL(__NR_msgctl, MSGCTL);
+ DISPATCH_SYSVCALL(__NR_shmat, SHMAT);
+ DISPATCH_SYSVCALL(__NR_shmdt, SHMDT);
+ DISPATCH_SYSVCALL(__NR_shmget, SHMGET);
+ DISPATCH_SYSVCALL(__NR_shmctl, SHMCTL);
+#undef DISPATCH_SYSVCALL
+#endif // ANDROID
+#endif // __NR_socketcall
+ default:
+ return InvalidSyscall();
+ }
+}
+
+}
diff --git a/security/sandbox/linux/SandboxFilterUtil.h b/security/sandbox/linux/SandboxFilterUtil.h
new file mode 100644
index 000000000..fb9afa79f
--- /dev/null
+++ b/security/sandbox/linux/SandboxFilterUtil.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxFilterUtil_h
+#define mozilla_SandboxFilterUtil_h
+
+// This header file exists to hold helper code for SandboxFilter.cpp,
+// to make that file easier to read for anyone trying to understand
+// the filter policy. It's mostly about smoothing out differences
+// between different Linux architectures.
+
+#include "mozilla/Maybe.h"
+#include "sandbox/linux/bpf_dsl/policy.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+namespace mozilla {
+
+// This class handles syscalls for BSD socket and SysV IPC operations.
+// On 32-bit x86 they're multiplexed via socketcall(2) and ipc(2),
+// respectively; on most other architectures they're individual system
+// calls. It translates the syscalls into socketcall/ipc selector
+// values, because those are defined (even if not used) for all
+// architectures.
+//
+// This EvaluateSyscall() routine always returns InvalidSyscall() for
+// everything else. It's assumed that subclasses will be implementing
+// a whitelist policy, so they can handle what they're whitelisting
+// and then defer to this class in the default case.
+class SandboxPolicyBase : public sandbox::bpf_dsl::Policy
+{
+public:
+ using ResultExpr = sandbox::bpf_dsl::ResultExpr;
+
+ virtual ResultExpr EvaluateSyscall(int aSysno) const override;
+ virtual Maybe<ResultExpr> EvaluateSocketCall(int aCall) const {
+ return Nothing();
+ }
+#ifndef ANDROID
+ // Android doesn't use SysV IPC (and doesn't define the selector
+ // constants in its headers), so this isn't implemented there.
+ virtual Maybe<ResultExpr> EvaluateIpcCall(int aCall) const {
+ return Nothing();
+ }
+#endif
+
+#ifdef __NR_socketcall
+ // socketcall(2) takes the actual call's arguments via a pointer, so
+ // seccomp-bpf can't inspect them; ipc(2) takes them at different indices.
+ static const bool kSocketCallHasArgs = false;
+ static const bool kIpcCallNormalArgs = false;
+#else
+ // Otherwise, the bpf_dsl Arg<> class can be used normally.
+ static const bool kSocketCallHasArgs = true;
+ static const bool kIpcCallNormalArgs = true;
+#endif
+};
+
+} // namespace mozilla
+
+// "Machine independent" pseudo-syscall numbers, to deal with arch
+// dependencies. (Most 32-bit archs started with 32-bit off_t; older
+// archs started with 16-bit uid_t/gid_t; 32-bit registers can't hold
+// a 64-bit offset for mmap; and so on.)
+//
+// For some of these, the "old" syscalls are also in use in some
+// cases; see, e.g., the handling of RT vs. non-RT signal syscalls.
+
+#ifdef __NR_mmap2
+#define CASES_FOR_mmap case __NR_mmap2
+#else
+#define CASES_FOR_mmap case __NR_mmap
+#endif
+
+#ifdef __NR_fchown32
+#define CASES_FOR_fchown case __NR_fchown32: case __NR_fchown
+#else
+#define CASES_FOR_fchown case __NR_fchown
+#endif
+
+#ifdef __NR_getuid32
+#define CASES_FOR_getuid case __NR_getuid32
+#define CASES_FOR_getgid case __NR_getgid32
+#define CASES_FOR_geteuid case __NR_geteuid32
+#define CASES_FOR_getegid case __NR_getegid32
+#define CASES_FOR_getresuid case __NR_getresuid32: case __NR_getresuid
+#define CASES_FOR_getresgid case __NR_getresgid32: case __NR_getresgid
+// The set*id syscalls are omitted; we'll probably never need to allow them.
+#else
+#define CASES_FOR_getuid case __NR_getuid
+#define CASES_FOR_getgid case __NR_getgid
+#define CASES_FOR_geteuid case __NR_geteuid
+#define CASES_FOR_getegid case __NR_getegid
+#define CASES_FOR_getresuid case __NR_getresuid
+#define CASES_FOR_getresgid case __NR_getresgid
+#endif
+
+#ifdef __NR_stat64
+#define CASES_FOR_stat case __NR_stat64
+#define CASES_FOR_lstat case __NR_lstat64
+#define CASES_FOR_fstat case __NR_fstat64
+#define CASES_FOR_fstatat case __NR_fstatat64
+#define CASES_FOR_statfs case __NR_statfs64: case __NR_statfs
+#define CASES_FOR_fstatfs case __NR_fstatfs64: case __NR_fstatfs
+#define CASES_FOR_fcntl case __NR_fcntl64
+// We're using the 32-bit version on 32-bit desktop for some reason.
+#define CASES_FOR_getdents case __NR_getdents64: case __NR_getdents
+// FIXME: we might not need the compat cases for these on non-Android:
+#define CASES_FOR_lseek case __NR_lseek: case __NR__llseek
+#define CASES_FOR_ftruncate case __NR_ftruncate: case __NR_ftruncate64
+#else
+#define CASES_FOR_stat case __NR_stat
+#define CASES_FOR_lstat case __NR_lstat
+#define CASES_FOR_fstatat case __NR_newfstatat
+#define CASES_FOR_fstat case __NR_fstat
+#define CASES_FOR_fstatfs case __NR_fstatfs
+#define CASES_FOR_statfs case __NR_statfs
+#define CASES_FOR_fcntl case __NR_fcntl
+#define CASES_FOR_getdents case __NR_getdents
+#define CASES_FOR_lseek case __NR_lseek
+#define CASES_FOR_ftruncate case __NR_ftruncate
+#endif
+
+#ifdef __NR_sigprocmask
+#define CASES_FOR_sigprocmask case __NR_sigprocmask: case __NR_rt_sigprocmask
+#define CASES_FOR_sigaction case __NR_sigaction: case __NR_rt_sigaction
+#define CASES_FOR_sigreturn case __NR_sigreturn: case __NR_rt_sigreturn
+#else
+#define CASES_FOR_sigprocmask case __NR_rt_sigprocmask
+#define CASES_FOR_sigaction case __NR_rt_sigaction
+#define CASES_FOR_sigreturn case __NR_rt_sigreturn
+#endif
+
+#ifdef __NR__newselect
+#define CASES_FOR_select case __NR__newselect
+#else
+#define CASES_FOR_select case __NR_select
+#endif
+
+#ifdef __NR_ugetrlimit
+#define CASES_FOR_getrlimit case __NR_ugetrlimit
+#else
+#define CASES_FOR_getrlimit case __NR_getrlimit
+#endif
+
+#endif // mozilla_SandboxFilterUtil_h
diff --git a/security/sandbox/linux/SandboxHooks.cpp b/security/sandbox/linux/SandboxHooks.cpp
new file mode 100644
index 000000000..eaaf56982
--- /dev/null
+++ b/security/sandbox/linux/SandboxHooks.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <dlfcn.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "mozilla/Types.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// Signal number used to enable seccomp on each thread.
+extern int gSeccompTsyncBroadcastSignum;
+
+// This file defines a hook for sigprocmask() and pthread_sigmask().
+// Bug 1176099: some threads block SIGSYS signal which breaks our seccomp-bpf
+// sandbox. To avoid this, we intercept the call and remove SIGSYS.
+//
+// ENOSYS indicates an error within the hook function itself.
+static int HandleSigset(int (*aRealFunc)(int, const sigset_t*, sigset_t*),
+ int aHow, const sigset_t* aSet,
+ sigset_t* aOldSet, bool aUseErrno)
+{
+ if (!aRealFunc) {
+ if (aUseErrno) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ return ENOSYS;
+ }
+
+ // Avoid unnecessary work
+ if (aSet == NULL || aHow == SIG_UNBLOCK) {
+ return aRealFunc(aHow, aSet, aOldSet);
+ }
+
+ sigset_t newSet = *aSet;
+ if (sigdelset(&newSet, SIGSYS) != 0 ||
+ (gSeccompTsyncBroadcastSignum &&
+ sigdelset(&newSet, gSeccompTsyncBroadcastSignum) != 0)) {
+ if (aUseErrno) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ return ENOSYS;
+ }
+
+ return aRealFunc(aHow, &newSet, aOldSet);
+}
+
+extern "C" MOZ_EXPORT int
+sigprocmask(int how, const sigset_t* set, sigset_t* oldset)
+{
+ static auto sRealFunc = (int (*)(int, const sigset_t*, sigset_t*))
+ dlsym(RTLD_NEXT, "sigprocmask");
+
+ return HandleSigset(sRealFunc, how, set, oldset, true);
+}
+
+extern "C" MOZ_EXPORT int
+pthread_sigmask(int how, const sigset_t* set, sigset_t* oldset)
+{
+ static auto sRealFunc = (int (*)(int, const sigset_t*, sigset_t*))
+ dlsym(RTLD_NEXT, "pthread_sigmask");
+
+ return HandleSigset(sRealFunc, how, set, oldset, false);
+}
diff --git a/security/sandbox/linux/SandboxInfo.cpp b/security/sandbox/linux/SandboxInfo.cpp
new file mode 100644
index 000000000..f813fb026
--- /dev/null
+++ b/security/sandbox/linux/SandboxInfo.cpp
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxInfo.h"
+#include "SandboxLogging.h"
+#include "LinuxSched.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ArrayUtils.h"
+#include "sandbox/linux/system_headers/linux_seccomp.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+#ifdef MOZ_VALGRIND
+#include <valgrind/valgrind.h>
+#endif
+
+
+// A note about assertions: in general, the worst thing this module
+// should be able to do is disable sandboxing features, so release
+// asserts or MOZ_CRASH should be avoided, even for seeming
+// impossibilities like an unimplemented syscall returning success
+// (which has happened: https://crbug.com/439795 ).
+//
+// MOZ_DIAGNOSTIC_ASSERT (debug builds, plus Nightly/Aurora non-debug)
+// is probably the best choice for conditions that shouldn't be able
+// to fail without the help of bugs in the kernel or system libraries.
+//
+// Regardless of assertion type, whatever condition caused it to fail
+// should generally also disable the corresponding feature on builds
+// that omit the assertion.
+
+namespace mozilla {
+
+// Bug 1229136: this is copied from ../SandboxUtil.cpp to avoid
+// complicated build issues; renamespaced to avoid the possibility of
+// symbol conflict.
+namespace {
+
+static bool
+IsSingleThreaded()
+{
+ // This detects the thread count indirectly. /proc/<pid>/task has a
+ // subdirectory for each thread in <pid>'s thread group, and the
+ // link count on the "task" directory follows Unix expectations: the
+ // link from its parent, the "." link from itself, and the ".." link
+ // from each subdirectory; thus, 2+N links for N threads.
+ struct stat sb;
+ if (stat("/proc/self/task", &sb) < 0) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Couldn't access /proc/self/task!");
+ return false;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(sb.st_nlink >= 3);
+ return sb.st_nlink == 3;
+}
+
+} // anonymous namespace
+
+static bool
+HasSeccompBPF()
+{
+ // Allow simulating the absence of seccomp-bpf support, for testing.
+ if (getenv("MOZ_FAKE_NO_SANDBOX")) {
+ return false;
+ }
+
+ // Valgrind and the sandbox don't interact well, probably because Valgrind
+ // does various system calls which aren't allowed, even if Firefox itself
+ // is playing by the rules.
+# if defined(MOZ_VALGRIND)
+ if (RUNNING_ON_VALGRIND) {
+ return false;
+ }
+# endif
+
+ // Determine whether seccomp-bpf is supported by trying to
+ // enable it with an invalid pointer for the filter. This will
+ // fail with EFAULT if supported and EINVAL if not, without
+ // changing the process's state.
+
+ int rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, nullptr);
+ MOZ_DIAGNOSTIC_ASSERT(rv == -1, "prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER,"
+ " nullptr) didn't fail");
+ MOZ_DIAGNOSTIC_ASSERT(errno == EFAULT || errno == EINVAL);
+ return rv == -1 && errno == EFAULT;
+}
+
+static bool
+HasSeccompTSync()
+{
+ // Similar to above, but for thread-sync mode. See also Chromium's
+ // sandbox::SandboxBPF::SupportsSeccompThreadFilterSynchronization
+ if (getenv("MOZ_FAKE_NO_SECCOMP_TSYNC")) {
+ return false;
+ }
+ int rv = syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
+ SECCOMP_FILTER_FLAG_TSYNC, nullptr);
+ MOZ_DIAGNOSTIC_ASSERT(rv == -1, "seccomp(..., SECCOMP_FILTER_FLAG_TSYNC,"
+ " nullptr) didn't fail");
+ MOZ_DIAGNOSTIC_ASSERT(errno == EFAULT || errno == EINVAL || errno == ENOSYS);
+ return rv == -1 && errno == EFAULT;
+}
+
+static bool
+HasUserNamespaceSupport()
+{
+ // Note: the /proc/<pid>/ns/* files track setns(2) support, which in
+ // some cases (e.g., pid) significantly postdates kernel support for
+ // the namespace type, so in general this type of check could be a
+ // false negative. However, for user namespaces, any kernel new
+ // enough for the feature to be usable for us has setns support
+ // (v3.8), so this is okay.
+ //
+ // The non-user namespaces all default to "y" in init/Kconfig, but
+ // check them explicitly in case someone has a weird custom config.
+ static const char* const paths[] = {
+ "/proc/self/ns/user",
+ "/proc/self/ns/pid",
+ "/proc/self/ns/net",
+ "/proc/self/ns/ipc",
+ };
+ for (size_t i = 0; i < ArrayLength(paths); ++i) {
+ if (access(paths[i], F_OK) == -1) {
+ MOZ_ASSERT(errno == ENOENT);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+CanCreateUserNamespace()
+{
+ // Unfortunately, the only way to verify that this process can
+ // create a new user namespace is to actually create one; because
+ // this process's namespaces shouldn't be side-effected (yet), it's
+ // necessary to clone (and collect) a child process. See also
+ // Chromium's sandbox::Credentials::SupportsNewUserNS.
+ //
+ // This is somewhat more expensive than the other tests, so it's
+ // cached in the environment to prevent child processes from having
+ // to re-run the test.
+ //
+ // This is run at static initializer time, while single-threaded, so
+ // locking isn't needed to access the environment.
+ static const char kCacheEnvName[] = "MOZ_ASSUME_USER_NS";
+ const char* cached = getenv(kCacheEnvName);
+ if (cached) {
+ return cached[0] > '0';
+ }
+
+ // Valgrind might allow the clone, but doesn't know what to do with
+ // unshare. Check for that by unsharing nothing. (Valgrind will
+ // probably need sandboxing disabled entirely, but no need to break
+ // things worse than strictly necessary.)
+ if (syscall(__NR_unshare, 0) != 0) {
+#ifdef MOZ_VALGRIND
+ MOZ_ASSERT(errno == ENOSYS);
+#else
+ // If something else can cause that call to fail, we's like to know
+ // about it; the right way to handle it might not be the same.
+ MOZ_ASSERT(false);
+#endif
+ return false;
+ }
+
+ pid_t pid = syscall(__NR_clone, SIGCHLD | CLONE_NEWUSER,
+ nullptr, nullptr, nullptr, nullptr);
+ if (pid == 0) {
+ // In the child. Do as little as possible.
+ _exit(0);
+ }
+ if (pid == -1) {
+ // Failure.
+ MOZ_ASSERT(errno == EINVAL || // unsupported
+ errno == EPERM || // root-only, or we're already chrooted
+ errno == EUSERS); // already at user namespace nesting limit
+ setenv(kCacheEnvName, "0", 1);
+ return false;
+ }
+ // Otherwise, in the parent and successful.
+ bool waitpid_ok = HANDLE_EINTR(waitpid(pid, nullptr, 0)) == pid;
+ MOZ_ASSERT(waitpid_ok);
+ if (!waitpid_ok) {
+ return false;
+ }
+ setenv(kCacheEnvName, "1", 1);
+ return true;
+}
+
+/* static */
+SandboxInfo SandboxInfo::sSingleton = SandboxInfo();
+
+SandboxInfo::SandboxInfo() {
+ int flags = 0;
+ static_assert(sizeof(flags) >= sizeof(Flags), "enum Flags fits in an int");
+
+ if (HasSeccompBPF()) {
+ flags |= kHasSeccompBPF;
+ if (HasSeccompTSync()) {
+ flags |= kHasSeccompTSync;
+ }
+ }
+
+ // Detect the threading-problem signal from the parent process.
+ if (getenv("MOZ_SANDBOX_UNEXPECTED_THREADS")) {
+ flags |= kUnexpectedThreads;
+ } else {
+ if (HasUserNamespaceSupport()) {
+ flags |= kHasPrivilegedUserNamespaces;
+ if (CanCreateUserNamespace()) {
+ flags |= kHasUserNamespaces;
+ }
+ }
+ }
+
+#ifdef MOZ_CONTENT_SANDBOX
+ if (!getenv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+ flags |= kEnabledForContent;
+ }
+ if (getenv("MOZ_PERMISSIVE_CONTENT_SANDBOX")) {
+ flags |= kPermissive;
+ }
+#endif
+#ifdef MOZ_GMP_SANDBOX
+ if (!getenv("MOZ_DISABLE_GMP_SANDBOX")) {
+ flags |= kEnabledForMedia;
+ }
+#endif
+ if (getenv("MOZ_SANDBOX_VERBOSE")) {
+ flags |= kVerbose;
+ }
+
+ mFlags = static_cast<Flags>(flags);
+}
+
+/* static */ void
+SandboxInfo::ThreadingCheck()
+{
+ // Allow MOZ_SANDBOX_UNEXPECTED_THREADS to be set manually for testing.
+ if (IsSingleThreaded() &&
+ !getenv("MOZ_SANDBOX_UNEXPECTED_THREADS")) {
+ return;
+ }
+ SANDBOX_LOG_ERROR("unexpected multithreading found; this prevents using"
+ " namespace sandboxing.%s",
+ // getenv isn't thread-safe, but see below.
+ getenv("LD_PRELOAD") ? " (If you're LD_PRELOAD'ing"
+ " nVidia GL: that's not necessary for Gecko.)" : "");
+
+ // Propagate this information for use by child processes. (setenv
+ // isn't thread-safe, but other threads are from non-Gecko code so
+ // they wouldn't be using NSPR; we have to hope for the best.)
+ setenv("MOZ_SANDBOX_UNEXPECTED_THREADS", "1", 0);
+ int flags = sSingleton.mFlags;
+ flags |= kUnexpectedThreads;
+ flags &= ~(kHasUserNamespaces | kHasPrivilegedUserNamespaces);
+ sSingleton.mFlags = static_cast<Flags>(flags);
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/SandboxInfo.h b/security/sandbox/linux/SandboxInfo.h
new file mode 100644
index 000000000..1999ac392
--- /dev/null
+++ b/security/sandbox/linux/SandboxInfo.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxInfo_h
+#define mozilla_SandboxInfo_h
+
+#include "mozilla/Types.h"
+
+// Information on what parts of sandboxing are enabled in this build
+// and/or supported by the system.
+
+namespace mozilla {
+
+class SandboxInfo {
+public:
+ // No need to prevent copying; this is essentially just a const int.
+ SandboxInfo(const SandboxInfo& aOther) : mFlags(aOther.mFlags) { }
+
+ // Flags are checked at initializer time; this returns them.
+ static const SandboxInfo& Get() { return sSingleton; }
+
+ enum Flags {
+ // System call filtering; kernel config option CONFIG_SECCOMP_FILTER.
+ kHasSeccompBPF = 1 << 0,
+ // Config flag MOZ_CONTENT_SANDBOX; env var MOZ_DISABLE_CONTENT_SANDBOX.
+ kEnabledForContent = 1 << 1,
+ // Config flag MOZ_GMP_SANDBOX; env var MOZ_DISABLE_GMP_SANDBOX.
+ kEnabledForMedia = 1 << 2,
+ // Env var MOZ_SANDBOX_VERBOSE.
+ kVerbose = 1 << 3,
+ // Kernel can atomically set system call filtering on entire thread group.
+ kHasSeccompTSync = 1 << 4,
+ // Can this process create user namespaces? (Man page user_namespaces(7).)
+ kHasUserNamespaces = 1 << 5,
+ // Could a more privileged process have user namespaces, even if we can't?
+ kHasPrivilegedUserNamespaces = 1 << 6,
+ // Env var MOZ_PERMISSIVE_CONTENT_SANDBOX
+ kPermissive = 1 << 7,
+ // Something is creating threads when we need to still be single-threaded.
+ kUnexpectedThreads = 1 << 8,
+ };
+
+ bool Test(Flags aFlag) const { return (mFlags & aFlag) == aFlag; }
+
+ // Returns true if SetContentProcessSandbox may be called.
+ bool CanSandboxContent() const
+ {
+ return !Test(kEnabledForContent) || Test(kHasSeccompBPF);
+ }
+
+ // Returns true if SetMediaPluginSandbox may be called.
+ bool CanSandboxMedia() const
+ {
+ return !Test(kEnabledForMedia) || Test(kHasSeccompBPF);
+ }
+
+ // For telemetry / crash annotation uses.
+ uint32_t AsInteger() const {
+ return mFlags;
+ }
+
+ // For bug 1222500 or anything else like it: On desktop, this is
+ // called in the parent process at a point when it should still be
+ // single-threaded, to check that the SandboxEarlyInit() call in a
+ // child process is early enough to be single-threaded. If not,
+ // kUnexpectedThreads is set and affected flags (user namespaces;
+ // possibly others in the future) are cleared.
+ static MOZ_EXPORT void ThreadingCheck();
+private:
+ enum Flags mFlags;
+ // This should be const, but has to allow for ThreadingCheck.
+ static MOZ_EXPORT SandboxInfo sSingleton;
+ SandboxInfo();
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxInfo_h
diff --git a/security/sandbox/linux/SandboxInternal.h b/security/sandbox/linux/SandboxInternal.h
new file mode 100644
index 000000000..90a688421
--- /dev/null
+++ b/security/sandbox/linux/SandboxInternal.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxInternal_h
+#define mozilla_SandboxInternal_h
+
+#include <signal.h>
+
+#include "mozilla/Types.h"
+
+struct sock_fprog;
+
+namespace mozilla {
+
+// SandboxCrash() has to be in libxul to use internal interfaces, but
+// its caller in libmozsandbox.
+// See also bug 1101170.
+
+typedef void (*SandboxCrashFunc)(int, siginfo_t*, void*);
+extern MOZ_EXPORT SandboxCrashFunc gSandboxCrashFunc;
+extern const sock_fprog* gSetSandboxFilter;
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxInternal_h
diff --git a/security/sandbox/linux/SandboxLogging.cpp b/security/sandbox/linux/SandboxLogging.cpp
new file mode 100644
index 000000000..19196a75a
--- /dev/null
+++ b/security/sandbox/linux/SandboxLogging.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxLogging.h"
+
+#ifdef ANDROID
+#include <android/log.h>
+#endif
+#include <algorithm>
+#include <stdio.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+
+namespace mozilla {
+
+// Alters an iovec array to remove the first `toDrop` bytes. This
+// complexity is necessary because writev can return a short write
+// (e.g., if stderr is a pipe and the buffer is almost full).
+static void
+IOVecDrop(struct iovec* iov, int iovcnt, size_t toDrop)
+{
+ while (toDrop > 0 && iovcnt > 0) {
+ size_t toDropHere = std::min(toDrop, iov->iov_len);
+ iov->iov_base = static_cast<char*>(iov->iov_base) + toDropHere;
+ iov->iov_len -= toDropHere;
+ toDrop -= toDropHere;
+ ++iov;
+ --iovcnt;
+ }
+}
+
+void
+SandboxLogError(const char* message)
+{
+#ifdef ANDROID
+ // This uses writev internally and appears to be async signal safe.
+ __android_log_write(ANDROID_LOG_ERROR, "Sandbox", message);
+#endif
+ static const char logPrefix[] = "Sandbox: ", logSuffix[] = "\n";
+ struct iovec iovs[3] = {
+ { const_cast<char*>(logPrefix), sizeof(logPrefix) - 1 },
+ { const_cast<char*>(message), strlen(message) },
+ { const_cast<char*>(logSuffix), sizeof(logSuffix) - 1 },
+ };
+ while (iovs[2].iov_len > 0) {
+ ssize_t written = HANDLE_EINTR(writev(STDERR_FILENO, iovs, 3));
+ if (written <= 0) {
+ break;
+ }
+ IOVecDrop(iovs, 3, static_cast<size_t>(written));
+ }
+}
+
+}
diff --git a/security/sandbox/linux/SandboxLogging.h b/security/sandbox/linux/SandboxLogging.h
new file mode 100644
index 000000000..88891adfb
--- /dev/null
+++ b/security/sandbox/linux/SandboxLogging.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxLogging_h
+#define mozilla_SandboxLogging_h
+
+// This header defines the SANDBOX_LOG_ERROR macro used in the Linux
+// sandboxing code. It uses Android logging on Android and writes to
+// stderr otherwise. Android logging has severity levels; currently
+// only "error" severity is exposed here, and this isn't marked when
+// writing to stderr.
+//
+// The format strings are processed by Chromium SafeSPrintf, which
+// doesn't accept size modifiers or %u because it uses C++11 variadic
+// templates to obtain the actual argument types; all decimal integer
+// formatting uses %d. See safe_sprintf.h for more details.
+
+// Build SafeSPrintf without assertions to avoid a dependency on
+// Chromium logging. This doesn't affect safety; it just means that
+// type mismatches (pointer vs. integer) always result in unexpanded
+// %-directives instead of crashing. See also the moz.build files,
+// which apply NDEBUG to the .cc file.
+#ifndef NDEBUG
+#define NDEBUG 1
+#include "base/strings/safe_sprintf.h"
+#undef NDEBUG
+#else
+#include "base/strings/safe_sprintf.h"
+#endif
+
+namespace mozilla {
+// Logs the formatted string (marked with "error" severity, if supported).
+void SandboxLogError(const char* aMessage);
+}
+
+#define SANDBOX_LOG_LEN 256
+
+// Formats a log message and logs it (with "error" severity, if supported).
+//
+// Note that SafeSPrintf doesn't accept size modifiers or %u; all
+// decimal integers are %d, because it uses C++11 variadic templates
+// to use the actual argument type.
+#define SANDBOX_LOG_ERROR(fmt, args...) do { \
+ char _sandboxLogBuf[SANDBOX_LOG_LEN]; \
+ ::base::strings::SafeSPrintf(_sandboxLogBuf, fmt, ## args); \
+ ::mozilla::SandboxLogError(_sandboxLogBuf); \
+} while(0)
+
+#endif // mozilla_SandboxLogging_h
diff --git a/security/sandbox/linux/SandboxUtil.cpp b/security/sandbox/linux/SandboxUtil.cpp
new file mode 100644
index 000000000..999329882
--- /dev/null
+++ b/security/sandbox/linux/SandboxUtil.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxUtil.h"
+
+#include "LinuxCapabilities.h"
+#include "LinuxSched.h"
+#include "SandboxLogging.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Unused.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+namespace mozilla {
+
+bool
+IsSingleThreaded()
+{
+ // This detects the thread count indirectly. /proc/<pid>/task has a
+ // subdirectory for each thread in <pid>'s thread group, and the
+ // link count on the "task" directory follows Unix expectations: the
+ // link from its parent, the "." link from itself, and the ".." link
+ // from each subdirectory; thus, 2+N links for N threads.
+ struct stat sb;
+ if (stat("/proc/self/task", &sb) < 0) {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Couldn't access /proc/self/task!");
+ return false;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(sb.st_nlink >= 3);
+ return sb.st_nlink == 3;
+}
+
+static bool
+WriteStringToFile(const char* aPath, const char* aStr, const size_t aLen)
+{
+ int fd = open(aPath, O_WRONLY);
+ if (fd < 0) {
+ return false;
+ }
+ ssize_t written = write(fd, aStr, aLen);
+ if (close(fd) != 0 || written != ssize_t(aLen)) {
+ return false;
+ }
+ return true;
+}
+
+bool
+UnshareUserNamespace()
+{
+ // The uid and gid need to be retrieved before the unshare; see
+ // below.
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+ char buf[80];
+
+ if (syscall(__NR_unshare, CLONE_NEWUSER) != 0) {
+ return false;
+ }
+
+ // As mentioned in the header, this function sets up uid/gid
+ // mappings that preserve the process's previous ids. Mapping the
+ // uid/gid to something is necessary in order to nest user
+ // namespaces (not used yet, but we'll need this in the future for
+ // pid namespace support), and leaving the ids unchanged is the
+ // least confusing option.
+ //
+ // In recent kernels (3.19, 3.18.2, 3.17.8), for security reasons,
+ // establishing gid mappings will fail unless the process first
+ // revokes its ability to call setgroups() by using a /proc node
+ // added in the same set of patches.
+ //
+ // Note that /proc/self points to the thread group leader, not the
+ // current thread. However, CLONE_NEWUSER can be unshared only in a
+ // single-threaded process, so those are equivalent if we reach this
+ // point.
+ int len = SprintfLiteral(buf, "%u %u 1\n", uid, uid);
+ if (len >= int(sizeof(buf)) || len < 0) {
+ return false;
+ }
+ if (!WriteStringToFile("/proc/self/uid_map", buf, size_t(len))) {
+ MOZ_CRASH("Failed to write /proc/self/uid_map");
+ }
+
+ Unused << WriteStringToFile("/proc/self/setgroups", "deny", 4);
+
+ len = SprintfLiteral(buf, "%u %u 1\n", gid, gid);
+ if (len >= int(sizeof(buf)) || len < 0) {
+ return false;
+ }
+ if (!WriteStringToFile("/proc/self/gid_map", buf, size_t(len))) {
+ MOZ_CRASH("Failed to write /proc/self/gid_map");
+ }
+ return true;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/SandboxUtil.h b/security/sandbox/linux/SandboxUtil.h
new file mode 100644
index 000000000..7bd84f798
--- /dev/null
+++ b/security/sandbox/linux/SandboxUtil.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxUtil_h
+#define mozilla_SandboxUtil_h
+
+namespace mozilla {
+
+bool IsSingleThreaded();
+
+// Unshare the user namespace, and set up id mappings so that the
+// process's subjective uid and gid are unchanged. This will always
+// fail if the process is multithreaded.
+bool UnshareUserNamespace();
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxUtil_h
diff --git a/security/sandbox/linux/broker/SandboxBroker.cpp b/security/sandbox/linux/broker/SandboxBroker.cpp
new file mode 100644
index 000000000..a31d1fc66
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -0,0 +1,731 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxBroker.h"
+#include "SandboxInfo.h"
+#include "SandboxLogging.h"
+#include "SandboxBrokerUtils.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifdef XP_LINUX
+#include <sys/prctl.h>
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+#include <private/android_filesystem_config.h>
+#include <sys/syscall.h>
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Move.h"
+#include "mozilla/NullPtr.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+namespace mozilla {
+
+// This constructor signals failure by setting mFileDesc and aClientFd to -1.
+SandboxBroker::SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
+ int& aClientFd)
+ : mChildPid(aChildPid), mPolicy(Move(aPolicy))
+{
+ int fds[2];
+ if (0 != socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)) {
+ SANDBOX_LOG_ERROR("SandboxBroker: socketpair failed: %s", strerror(errno));
+ mFileDesc = -1;
+ aClientFd = -1;
+ return;
+ }
+ mFileDesc = fds[0];
+ aClientFd = fds[1];
+
+ if (!PlatformThread::Create(0, this, &mThread)) {
+ SANDBOX_LOG_ERROR("SandboxBroker: thread creation failed: %s",
+ strerror(errno));
+ close(mFileDesc);
+ close(aClientFd);
+ mFileDesc = -1;
+ aClientFd = -1;
+ }
+}
+
+UniquePtr<SandboxBroker>
+SandboxBroker::Create(UniquePtr<const Policy> aPolicy, int aChildPid,
+ ipc::FileDescriptor& aClientFdOut)
+{
+ int clientFd;
+ // Can't use MakeUnique here because the constructor is private.
+ UniquePtr<SandboxBroker> rv(new SandboxBroker(Move(aPolicy), aChildPid,
+ clientFd));
+ if (clientFd < 0) {
+ rv = nullptr;
+ } else {
+ aClientFdOut = ipc::FileDescriptor(clientFd);
+ }
+ return Move(rv);
+}
+
+SandboxBroker::~SandboxBroker() {
+ // If the constructor failed, there's nothing to be done here.
+ if (mFileDesc < 0) {
+ return;
+ }
+
+ shutdown(mFileDesc, SHUT_RD);
+ // The thread will now get EOF even if the client hasn't exited.
+ PlatformThread::Join(mThread);
+ // Now that the thread has exited, the fd will no longer be accessed.
+ close(mFileDesc);
+ // Having ensured that this object outlives the thread, this
+ // destructor can now return.
+}
+
+SandboxBroker::Policy::Policy() { }
+SandboxBroker::Policy::~Policy() { }
+
+SandboxBroker::Policy::Policy(const Policy& aOther) {
+ for (auto iter = aOther.mMap.ConstIter(); !iter.Done(); iter.Next()) {
+ mMap.Put(iter.Key(), iter.Data());
+ }
+}
+
+// Chromium
+// sandbox/linux/syscall_broker/broker_file_permission.cc
+// Async signal safe
+bool
+SandboxBroker::Policy::ValidatePath(const char* path) const {
+ if (!path)
+ return false;
+
+ const size_t len = strlen(path);
+ // No empty paths
+ if (len == 0)
+ return false;
+ // Paths must be absolute and not relative
+ if (path[0] != '/')
+ return false;
+ // No trailing / (but "/" is valid)
+ if (len > 1 && path[len - 1] == '/')
+ return false;
+ // No trailing /.
+ if (len >= 2 && path[len - 2] == '/' && path[len - 1] == '.')
+ return false;
+ // No trailing /..
+ if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' &&
+ path[len - 1] == '.')
+ return false;
+ // No /../ anywhere
+ for (size_t i = 0; i < len; i++) {
+ if (path[i] == '/' && (len - i) > 3) {
+ if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void
+SandboxBroker::Policy::AddPath(int aPerms, const char* aPath,
+ AddCondition aCond)
+{
+ nsDependentCString path(aPath);
+ MOZ_ASSERT(path.Length() <= kMaxPathLen);
+ int perms;
+ if (aCond == AddIfExistsNow) {
+ struct stat statBuf;
+ if (lstat(aPath, &statBuf) != 0) {
+ return;
+ }
+ }
+ if (!mMap.Get(path, &perms)) {
+ perms = MAY_ACCESS;
+ } else {
+ MOZ_ASSERT(perms & MAY_ACCESS);
+ }
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("policy for %s: %d -> %d", aPath, perms, perms | aPerms);
+ }
+ perms |= aPerms;
+ mMap.Put(path, perms);
+}
+
+void
+SandboxBroker::Policy::AddTree(int aPerms, const char* aPath)
+{
+ struct stat statBuf;
+
+ if (stat(aPath, &statBuf) != 0) {
+ return;
+ }
+ if (!S_ISDIR(statBuf.st_mode)) {
+ AddPath(aPerms, aPath, AddAlways);
+ } else {
+ DIR* dirp = opendir(aPath);
+ if (!dirp) {
+ return;
+ }
+ while (struct dirent* de = readdir(dirp)) {
+ if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
+ continue;
+ }
+ // Note: could optimize the string handling.
+ nsAutoCString subPath;
+ subPath.Assign(aPath);
+ subPath.Append('/');
+ subPath.Append(de->d_name);
+ AddTree(aPerms, subPath.get());
+ }
+ closedir(dirp);
+ }
+}
+
+void
+SandboxBroker::Policy::AddDir(int aPerms, const char* aPath)
+{
+ struct stat statBuf;
+
+ if (stat(aPath, &statBuf) != 0) {
+ return;
+ }
+
+ if (!S_ISDIR(statBuf.st_mode)) {
+ return;
+ }
+
+ nsDependentCString path(aPath);
+ MOZ_ASSERT(path.Length() <= kMaxPathLen - 1);
+ // Enforce trailing / on aPath
+ if (path[path.Length() - 1] != '/') {
+ path.Append('/');
+ }
+ int origPerms;
+ if (!mMap.Get(path, &origPerms)) {
+ origPerms = MAY_ACCESS;
+ } else {
+ MOZ_ASSERT(origPerms & MAY_ACCESS);
+ }
+ int newPerms = origPerms | aPerms | RECURSIVE;
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("policy for %s: %d -> %d", aPath, origPerms, newPerms);
+ }
+ mMap.Put(path, newPerms);
+}
+
+void
+SandboxBroker::Policy::AddPrefix(int aPerms, const char* aDir,
+ const char* aPrefix)
+{
+ size_t prefixLen = strlen(aPrefix);
+ DIR* dirp = opendir(aDir);
+ struct dirent* de;
+ if (!dirp) {
+ return;
+ }
+ while ((de = readdir(dirp))) {
+ if (strncmp(de->d_name, aPrefix, prefixLen) == 0) {
+ nsAutoCString subPath;
+ subPath.Assign(aDir);
+ subPath.Append('/');
+ subPath.Append(de->d_name);
+ AddPath(aPerms, subPath.get(), AddAlways);
+ }
+ }
+ closedir(dirp);
+}
+
+int
+SandboxBroker::Policy::Lookup(const nsACString& aPath) const
+{
+ // Early exit for paths explicitly found in the
+ // whitelist.
+ // This means they will not gain extra permissions
+ // from recursive paths.
+ int perms = mMap.Get(aPath);
+ if (perms) {
+ return perms;
+ }
+
+ // Not a legally constructed path
+ if (!ValidatePath(PromiseFlatCString(aPath).get()))
+ return 0;
+
+ // Now it's either an illegal access, or a recursive
+ // directory permission. We'll have to check the entire
+ // whitelist for the best match (slower).
+ int allPerms = 0;
+ for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
+ const nsACString& whiteListPath = iter.Key();
+ const int& perms = iter.Data();
+
+ if (!(perms & RECURSIVE))
+ continue;
+
+ // passed part starts with something on the whitelist
+ if (StringBeginsWith(aPath, whiteListPath)) {
+ allPerms |= perms;
+ }
+ }
+
+ // Strip away the RECURSIVE flag as it doesn't
+ // necessarily apply to aPath.
+ return allPerms & ~RECURSIVE;
+}
+
+static bool
+AllowOperation(int aReqFlags, int aPerms)
+{
+ int needed = 0;
+ if (aReqFlags & R_OK) {
+ needed |= SandboxBroker::MAY_READ;
+ }
+ if (aReqFlags & W_OK) {
+ needed |= SandboxBroker::MAY_WRITE;
+ }
+ // We don't really allow executing anything,
+ // so in true unix tradition we hijack this
+ // for directories.
+ if (aReqFlags & X_OK) {
+ needed |= SandboxBroker::MAY_CREATE;
+ }
+ return (aPerms & needed) == needed;
+}
+
+static bool
+AllowAccess(int aReqFlags, int aPerms)
+{
+ if (aReqFlags & ~(R_OK|W_OK|F_OK)) {
+ return false;
+ }
+ int needed = 0;
+ if (aReqFlags & R_OK) {
+ needed |= SandboxBroker::MAY_READ;
+ }
+ if (aReqFlags & W_OK) {
+ needed |= SandboxBroker::MAY_WRITE;
+ }
+ return (aPerms & needed) == needed;
+}
+
+// These flags are added to all opens to prevent possible side-effects
+// on this process. These shouldn't be relevant to the child process
+// in any case due to the sandboxing restrictions on it. (See also
+// the use of MSG_CMSG_CLOEXEC in SandboxBrokerCommon.cpp).
+static const int kRequiredOpenFlags = O_CLOEXEC | O_NOCTTY;
+
+// Linux originally assigned a flag bit to O_SYNC but implemented the
+// semantics standardized as O_DSYNC; later, that bit was renamed and
+// a new bit was assigned to the full O_SYNC, and O_SYNC was redefined
+// to be both bits. As a result, this #define is needed to compensate
+// for outdated kernel headers like Android's.
+#define O_SYNC_NEW 04010000
+static const int kAllowedOpenFlags =
+ O_APPEND | O_ASYNC | O_DIRECT | O_DIRECTORY | O_EXCL | O_LARGEFILE
+ | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY | O_SYNC_NEW
+ | O_TRUNC | O_CLOEXEC | O_CREAT;
+#undef O_SYNC_NEW
+
+static bool
+AllowOpen(int aReqFlags, int aPerms)
+{
+ if (aReqFlags & ~O_ACCMODE & ~kAllowedOpenFlags) {
+ return false;
+ }
+ int needed;
+ switch(aReqFlags & O_ACCMODE) {
+ case O_RDONLY:
+ needed = SandboxBroker::MAY_READ;
+ break;
+ case O_WRONLY:
+ needed = SandboxBroker::MAY_WRITE;
+ break;
+ case O_RDWR:
+ needed = SandboxBroker::MAY_READ | SandboxBroker::MAY_WRITE;
+ break;
+ default:
+ return false;
+ }
+ if (aReqFlags & O_CREAT) {
+ needed |= SandboxBroker::MAY_CREATE;
+ }
+ return (aPerms & needed) == needed;
+}
+
+static int
+DoStat(const char* aPath, void* aBuff, int aFlags)
+{
+ if (aFlags & O_NOFOLLOW) {
+ return lstatsyscall(aPath, (statstruct*)aBuff);
+ }
+ return statsyscall(aPath, (statstruct*)aBuff);
+}
+
+static int
+DoLink(const char* aPath, const char* aPath2,
+ SandboxBrokerCommon::Operation aOper)
+{
+ if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) {
+ return link(aPath, aPath2);
+ } else if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
+ return symlink(aPath, aPath2);
+ }
+ MOZ_CRASH("SandboxBroker: Unknown link operation");
+}
+
+size_t
+SandboxBroker::ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen)
+{
+ if (strstr(aPath, "..") != NULL) {
+ char* result = realpath(aPath, NULL);
+ if (result != NULL) {
+ strncpy(aPath, result, aBufSize);
+ aPath[aBufSize - 1] = '\0';
+ free(result);
+ // Size changed, but guaranteed to be 0 terminated
+ aPathLen = strlen(aPath);
+ }
+ // ValidatePath will handle failure to translate
+ }
+ return aPathLen;
+}
+
+void
+SandboxBroker::ThreadMain(void)
+{
+ char threadName[16];
+ SprintfLiteral(threadName, "FS Broker %d", mChildPid);
+ PlatformThread::SetName(threadName);
+
+ // Permissive mode can only be enabled through an environment variable,
+ // therefore it is sufficient to fetch the value once
+ // before the main thread loop starts
+ bool permissive = SandboxInfo::Get().Test(SandboxInfo::kPermissive);
+
+#ifdef MOZ_WIDGET_GONK
+#ifdef __NR_setreuid32
+ static const long nr_setreuid = __NR_setreuid32;
+ static const long nr_setregid = __NR_setregid32;
+#else
+ static const long nr_setreuid = __NR_setreuid;
+ static const long nr_setregid = __NR_setregid;
+#endif
+ if (syscall(nr_setregid, getgid(), AID_APP + mChildPid) != 0 ||
+ syscall(nr_setreuid, getuid(), AID_APP + mChildPid) != 0) {
+ MOZ_CRASH("SandboxBroker: failed to drop privileges");
+ }
+#endif
+
+ while (true) {
+ struct iovec ios[2];
+ // We will receive the path strings in 1 buffer and split them back up.
+ char recvBuf[2 * (kMaxPathLen + 1)];
+ char pathBuf[kMaxPathLen + 1];
+ char pathBuf2[kMaxPathLen + 1];
+ size_t pathLen;
+ size_t pathLen2;
+ char respBuf[kMaxPathLen + 1]; // Also serves as struct stat
+ Request req;
+ Response resp;
+ int respfd;
+
+ // Make sure stat responses fit in the response buffer
+ MOZ_ASSERT((kMaxPathLen + 1) > sizeof(struct stat));
+
+ // This makes our string handling below a bit less error prone.
+ memset(recvBuf, 0, sizeof(recvBuf));
+
+ ios[0].iov_base = &req;
+ ios[0].iov_len = sizeof(req);
+ ios[1].iov_base = recvBuf;
+ ios[1].iov_len = sizeof(recvBuf);
+
+ const ssize_t recvd = RecvWithFd(mFileDesc, ios, 2, &respfd);
+ if (recvd == 0) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("EOF from pid %d", mChildPid);
+ }
+ break;
+ }
+ // It could be possible to continue after errors and short reads,
+ // at least in some cases, but protocol violation indicates a
+ // hostile client, so terminate the broker instead.
+ if (recvd < 0) {
+ SANDBOX_LOG_ERROR("bad read from pid %d: %s",
+ mChildPid, strerror(errno));
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+ if (recvd < static_cast<ssize_t>(sizeof(req))) {
+ SANDBOX_LOG_ERROR("bad read from pid %d (%d < %d)",
+ mChildPid, recvd, sizeof(req));
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+ if (respfd == -1) {
+ SANDBOX_LOG_ERROR("no response fd from pid %d", mChildPid);
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+
+ // Initialize the response with the default failure.
+ memset(&resp, 0, sizeof(resp));
+ memset(&respBuf, 0, sizeof(respBuf));
+ resp.mError = -EACCES;
+ ios[0].iov_base = &resp;
+ ios[0].iov_len = sizeof(resp);
+ ios[1].iov_base = nullptr;
+ ios[1].iov_len = 0;
+ int openedFd = -1;
+
+ // Clear permissions
+ int perms;
+
+ // Find end of first string, make sure the buffer is still
+ // 0 terminated.
+ size_t recvBufLen = static_cast<size_t>(recvd) - sizeof(req);
+ if (recvBufLen > 0 && recvBuf[recvBufLen - 1] != 0) {
+ SANDBOX_LOG_ERROR("corrupted path buffer from pid %d", mChildPid);
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+
+ // First path should fit in maximum path length buffer.
+ size_t first_len = strlen(recvBuf);
+ if (first_len <= kMaxPathLen) {
+ strcpy(pathBuf, recvBuf);
+ // Skip right over the terminating 0, and try to copy in the
+ // second path, if any. If there's no path, this will hit a
+ // 0 immediately (we nulled the buffer before receiving).
+ // We do not assume the second path is 0-terminated, this is
+ // enforced below.
+ strncpy(pathBuf2, recvBuf + first_len + 1, kMaxPathLen + 1);
+
+ // First string is guaranteed to be 0-terminated.
+ pathLen = first_len;
+
+ // Look up the first pathname but first translate relative paths.
+ pathLen = ConvertToRealPath(pathBuf, sizeof(pathBuf), pathLen);
+ perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
+
+ // Same for the second path.
+ pathLen2 = strnlen(pathBuf2, kMaxPathLen);
+ if (pathLen2 > 0) {
+ // Force 0 termination.
+ pathBuf2[pathLen2] = '\0';
+ pathLen2 = ConvertToRealPath(pathBuf2, sizeof(pathBuf2), pathLen2);
+ int perms2 = mPolicy->Lookup(nsDependentCString(pathBuf2, pathLen2));
+
+ // Take the intersection of the permissions for both paths.
+ perms &= perms2;
+ }
+ } else {
+ // Failed to receive intelligible paths.
+ perms = 0;
+ }
+
+ // And now perform the operation if allowed.
+ if (perms & CRASH_INSTEAD) {
+ // This is somewhat nonmodular, but it works.
+ resp.mError = -ENOSYS;
+ } else if (permissive || perms & MAY_ACCESS) {
+ // If the operation was only allowed because of permissive mode, log it.
+ if (permissive && !(perms & MAY_ACCESS)) {
+ AuditPermissive(req.mOp, req.mFlags, perms, pathBuf);
+ }
+
+ switch(req.mOp) {
+ case SANDBOX_FILE_OPEN:
+ if (permissive || AllowOpen(req.mFlags, perms)) {
+ // Permissions for O_CREAT hardwired to 0600; if that's
+ // ever a problem we can change the protocol (but really we
+ // should be trying to remove uses of MAY_CREATE, not add
+ // new ones).
+ openedFd = open(pathBuf, req.mFlags | kRequiredOpenFlags, 0600);
+ if (openedFd >= 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_ACCESS:
+ if (permissive || AllowAccess(req.mFlags, perms)) {
+ // This can't use access() itself because that uses the ruid
+ // and not the euid. In theory faccessat() with AT_EACCESS
+ // would work, but Linux doesn't actually implement the
+ // flags != 0 case; glibc has a hack which doesn't even work
+ // in this case so it'll ignore the flag, and Bionic just
+ // passes through the syscall and always ignores the flags.
+ //
+ // Instead, because we've already checked the requested
+ // r/w/x bits against the policy, just return success if the
+ // file exists and hope that's close enough.
+ if (stat(pathBuf, (struct stat*)&respBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_STAT:
+ if (DoStat(pathBuf, (struct stat*)&respBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ ios[1].iov_base = &respBuf;
+ ios[1].iov_len = req.mBufSize;
+ } else {
+ resp.mError = -errno;
+ }
+ break;
+
+ case SANDBOX_FILE_CHMOD:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (chmod(pathBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_LINK:
+ case SANDBOX_FILE_SYMLINK:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_RENAME:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (rename(pathBuf, pathBuf2) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_MKDIR:
+ if (permissive || AllowOperation(W_OK | X_OK, perms)) {
+ if (mkdir(pathBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_UNLINK:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (unlink(pathBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_RMDIR:
+ if (permissive || AllowOperation(W_OK | X_OK, perms)) {
+ if (rmdir(pathBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_READLINK:
+ if (permissive || AllowOperation(R_OK, perms)) {
+ ssize_t respSize = readlink(pathBuf, (char*)&respBuf, sizeof(respBuf));
+ if (respSize >= 0) {
+ resp.mError = respSize;
+ ios[1].iov_base = &respBuf;
+ ios[1].iov_len = respSize;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+ }
+ } else {
+ MOZ_ASSERT(perms == 0);
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+
+ const size_t numIO = ios[1].iov_len > 0 ? 2 : 1;
+ DebugOnly<const ssize_t> sent = SendWithFd(respfd, ios, numIO, openedFd);
+ close(respfd);
+ MOZ_ASSERT(sent < 0 ||
+ static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len);
+
+ if (openedFd >= 0) {
+ close(openedFd);
+ }
+ }
+}
+
+void
+SandboxBroker::AuditPermissive(int aOp, int aFlags, int aPerms, const char* aPath)
+{
+ MOZ_RELEASE_ASSERT(SandboxInfo::Get().Test(SandboxInfo::kPermissive));
+
+ struct stat statBuf;
+
+ if (lstat(aPath, &statBuf) == 0) {
+ // Path exists, set errno to 0 to indicate "success".
+ errno = 0;
+ }
+
+ SANDBOX_LOG_ERROR("SandboxBroker: would have denied op=%d rflags=%o perms=%d path=%s for pid=%d" \
+ " permissive=1 error=\"%s\"", aOp, aFlags, aPerms,
+ aPath, mChildPid, strerror(errno));
+}
+
+void
+SandboxBroker::AuditDenial(int aOp, int aFlags, int aPerms, const char* aPath)
+{
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("SandboxBroker: denied op=%d rflags=%o perms=%d path=%s for pid=%d" \
+ " error=\"%s\"", aOp, aFlags, aPerms, aPath, mChildPid,
+ strerror(errno));
+ }
+}
+
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBroker.h b/security/sandbox/linux/broker/SandboxBroker.h
new file mode 100644
index 000000000..bb4570a64
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBroker.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxBroker_h
+#define mozilla_SandboxBroker_h
+
+#include "mozilla/SandboxBrokerCommon.h"
+
+#include "base/platform_thread.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+namespace ipc {
+class FileDescriptor;
+}
+
+// This class implements a broker for filesystem operations requested
+// by a sandboxed child process -- opening files and accessing their
+// metadata. (This is necessary in order to restrict access by path;
+// seccomp-bpf can filter only on argument register values, not
+// parameters passed in memory like pathnames.)
+//
+// The broker currently runs on a thread in the parent process (with
+// effective uid changed on B2G), which is for memory efficiency
+// (compared to forking a process) and simplicity (compared to having
+// a separate executable and serializing/deserializing the policy).
+//
+// See also ../SandboxBrokerClient.h for the corresponding client.
+
+class SandboxBroker final
+ : private SandboxBrokerCommon
+ , public PlatformThread::Delegate
+{
+ public:
+ enum Perms {
+ MAY_ACCESS = 1 << 0,
+ MAY_READ = 1 << 1,
+ MAY_WRITE = 1 << 2,
+ MAY_CREATE = 1 << 3,
+ // This flag is for testing policy changes -- when the client is
+ // used with the seccomp-bpf integration, an access to this file
+ // will invoke a crash dump with the context of the syscall.
+ // (This overrides all other flags.)
+ CRASH_INSTEAD = 1 << 4,
+ // Applies to everything below this path, including subdirs created
+ // at runtime
+ RECURSIVE = 1 << 5,
+ };
+ // Bitwise operations on enum values return ints, so just use int in
+ // the hash table type (and below) to avoid cluttering code with casts.
+ typedef nsDataHashtable<nsCStringHashKey, int> PathMap;
+
+ class Policy {
+ PathMap mMap;
+ public:
+ Policy();
+ Policy(const Policy& aOther);
+ ~Policy();
+
+ enum AddCondition {
+ AddIfExistsNow,
+ AddAlways,
+ };
+ // Typically, files that don't exist at policy creation time don't
+ // need to be whitelisted, but this allows adding entries for
+ // them if they'll exist later. See also the overload below.
+ void AddPath(int aPerms, const char* aPath, AddCondition aCond);
+ // This adds all regular files (not directories) in the tree
+ // rooted at the given path.
+ void AddTree(int aPerms, const char* aPath);
+ // A directory, and all files and directories under it, even those
+ // added after creation (the dir itself must exist).
+ void AddDir(int aPerms, const char* aPath);
+ // All files in a directory with a given prefix; useful for devices.
+ void AddPrefix(int aPerms, const char* aDir, const char* aPrefix);
+ // Default: add file if it exists when creating policy or if we're
+ // conferring permission to create it (log files, etc.).
+ void AddPath(int aPerms, const char* aPath) {
+ AddPath(aPerms, aPath,
+ (aPerms & MAY_CREATE) ? AddAlways : AddIfExistsNow);
+ }
+ int Lookup(const nsACString& aPath) const;
+ int Lookup(const char* aPath) const {
+ return Lookup(nsDependentCString(aPath));
+ }
+ private:
+ // ValidatePath checks |path| and returns true if these conditions are met
+ // * Greater than 0 length
+ // * Is an absolute path
+ // * No trailing slash
+ // * No /../ path traversal
+ bool ValidatePath(const char* path) const;
+ };
+
+ // Constructing a broker involves creating a socketpair and a
+ // background thread to handle requests, so it can fail. If this
+ // returns nullptr, do not use the value of aClientFdOut.
+ static UniquePtr<SandboxBroker>
+ Create(UniquePtr<const Policy> aPolicy, int aChildPid,
+ ipc::FileDescriptor& aClientFdOut);
+ virtual ~SandboxBroker();
+
+ private:
+ PlatformThreadHandle mThread;
+ int mFileDesc;
+ const int mChildPid;
+ const UniquePtr<const Policy> mPolicy;
+
+ SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
+ int& aClientFd);
+ void ThreadMain(void) override;
+ void AuditPermissive(int aOp, int aFlags, int aPerms, const char* aPath);
+ void AuditDenial(int aOp, int aFlags, int aPerms, const char* aPath);
+ // Remap relative paths to absolute paths.
+ size_t ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen);
+
+ // Holding a UniquePtr should disallow copying, but to make that explicit:
+ SandboxBroker(const SandboxBroker&) = delete;
+ void operator=(const SandboxBroker&) = delete;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxBroker_h
diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.cpp b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
new file mode 100644
index 000000000..fe7bc8c45
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxBrokerCommon.h"
+
+#include "mozilla/Assertions.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef MSG_CMSG_CLOEXEC
+#ifdef XP_LINUX
+// As always, Android's kernel headers are somewhat old.
+#define MSG_CMSG_CLOEXEC 0x40000000
+#else
+// Most of this code can support other POSIX OSes, but being able to
+// receive fds and atomically make them close-on-exec is important,
+// because this is running in a multithreaded process that can fork.
+// In the future, if the broker becomes a dedicated executable, this
+// can change.
+#error "No MSG_CMSG_CLOEXEC?"
+#endif // XP_LINUX
+#endif // MSG_CMSG_CLOEXEC
+
+namespace mozilla {
+
+/* static */ ssize_t
+SandboxBrokerCommon::RecvWithFd(int aFd, const iovec* aIO, size_t aNumIO,
+ int* aPassedFdPtr)
+{
+ struct msghdr msg = {};
+ msg.msg_iov = const_cast<iovec*>(aIO);
+ msg.msg_iovlen = aNumIO;
+
+ char cmsg_buf[CMSG_SPACE(sizeof(int))];
+ if (aPassedFdPtr) {
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+ *aPassedFdPtr = -1;
+ }
+
+ ssize_t rv;
+ do {
+ // MSG_CMSG_CLOEXEC is needed to prevent the parent process from
+ // accidentally leaking a copy of the child's response socket to a
+ // new child process. (The child won't be able to exec, so this
+ // doesn't matter as much for that direction.)
+ rv = recvmsg(aFd, &msg, MSG_CMSG_CLOEXEC);
+ } while (rv < 0 && errno == EINTR);
+
+ if (rv <= 0) {
+ return rv;
+ }
+ if (msg.msg_controllen > 0) {
+ MOZ_ASSERT(aPassedFdPtr);
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ int* fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
+ // A client could, for example, send an extra 32-bit int if
+ // CMSG_SPACE pads to 64-bit size_t alignment. If so, treat
+ // it as an error, but also don't leak the fds.
+ for (size_t i = 0; CMSG_LEN(sizeof(int) * i) < cmsg->cmsg_len; ++i) {
+ close(fds[i]);
+ }
+ errno = EMSGSIZE;
+ return -1;
+ }
+ *aPassedFdPtr = fds[0];
+ } else {
+ errno = EPROTO;
+ return -1;
+ }
+ }
+ if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
+ if (aPassedFdPtr && *aPassedFdPtr >= 0) {
+ close(*aPassedFdPtr);
+ *aPassedFdPtr = -1;
+ }
+ errno = EMSGSIZE;
+ return -1;
+ }
+
+ return rv;
+}
+
+/* static */ ssize_t
+SandboxBrokerCommon::SendWithFd(int aFd, const iovec* aIO, size_t aNumIO,
+ int aPassedFd)
+{
+ struct msghdr msg = {};
+ msg.msg_iov = const_cast<iovec*>(aIO);
+ msg.msg_iovlen = aNumIO;
+
+ char cmsg_buf[CMSG_SPACE(sizeof(int))];
+ if (aPassedFd != -1) {
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ *reinterpret_cast<int*>(CMSG_DATA(cmsg)) = aPassedFd;
+ }
+
+ ssize_t rv;
+ do {
+ rv = sendmsg(aFd, &msg, MSG_NOSIGNAL);
+ } while (rv < 0 && errno == EINTR);
+
+ return rv;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.h b/security/sandbox/linux/broker/SandboxBrokerCommon.h
new file mode 100644
index 000000000..dbd17e0b9
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxBrokerCommon_h
+#define mozilla_SandboxBrokerCommon_h
+
+#include <sys/types.h>
+
+struct iovec;
+
+// This file defines the protocol between the filesystem broker,
+// described in SandboxBroker.h, and its client, described in
+// ../SandboxBrokerClient.h; and it defines some utility functions
+// used by both.
+//
+// In order to keep the client simple while allowing it to be thread
+// safe and async signal safe, the main broker socket is used only for
+// requests; responses arrive on a per-request socketpair sent with
+// the request. (This technique is also used by Chromium and Breakpad.)
+
+namespace mozilla {
+
+class SandboxBrokerCommon {
+public:
+ enum Operation {
+ SANDBOX_FILE_OPEN,
+ SANDBOX_FILE_ACCESS,
+ SANDBOX_FILE_STAT,
+ SANDBOX_FILE_CHMOD,
+ SANDBOX_FILE_LINK,
+ SANDBOX_FILE_SYMLINK,
+ SANDBOX_FILE_MKDIR,
+ SANDBOX_FILE_RENAME,
+ SANDBOX_FILE_RMDIR,
+ SANDBOX_FILE_UNLINK,
+ SANDBOX_FILE_READLINK,
+ };
+
+ struct Request {
+ Operation mOp;
+ // For open, flags; for access, "mode"; for stat, O_NOFOLLOW for lstat.
+ int mFlags;
+ // Size of return value buffer, if any
+ size_t mBufSize;
+ // The rest of the packet is the pathname.
+ // SCM_RIGHTS for response socket attached.
+ };
+
+ struct Response {
+ // Syscall result, -errno if failure, or 0 for no error
+ int mError;
+ // Followed by struct stat for stat/lstat.
+ // SCM_RIGHTS attached for successful open.
+ };
+
+ // This doesn't need to be the system's maximum path length, just
+ // the largest path that would be allowed by any policy. (It's used
+ // to size a stack-allocated buffer.)
+ static const size_t kMaxPathLen = 4096;
+
+ static ssize_t RecvWithFd(int aFd, const iovec* aIO, size_t aNumIO,
+ int* aPassedFdPtr);
+ static ssize_t SendWithFd(int aFd, const iovec* aIO, size_t aNumIO,
+ int aPassedFd);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxBrokerCommon_h
diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
new file mode 100644
index 000000000..8e698606e
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxBrokerPolicyFactory.h"
+#include "SandboxInfo.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "SpecialSystemDirectory.h"
+
+#ifdef ANDROID
+#include "cutils/properties.h"
+#endif
+
+namespace mozilla {
+
+/* static */ bool
+SandboxBrokerPolicyFactory::IsSystemSupported() {
+#ifdef ANDROID
+ char hardware[PROPERTY_VALUE_MAX];
+ int length = property_get("ro.hardware", hardware, nullptr);
+ // "goldfish" -> emulator. Other devices can be added when we're
+ // reasonably sure they work. Eventually this won't be needed....
+ if (length > 0 && strcmp(hardware, "goldfish") == 0) {
+ return true;
+ }
+
+ // When broker is running in permissive mode, we enable it
+ // automatically regardless of the device.
+ if (SandboxInfo::Get().Test(SandboxInfo::kPermissive)) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+#if defined(MOZ_CONTENT_SANDBOX)
+namespace {
+static const int rdonly = SandboxBroker::MAY_READ;
+static const int wronly = SandboxBroker::MAY_WRITE;
+static const int rdwr = rdonly | wronly;
+static const int rdwrcr = rdwr | SandboxBroker::MAY_CREATE;
+#if defined(MOZ_WIDGET_GONK)
+static const int wrlog = wronly | SandboxBroker::MAY_CREATE;
+#endif
+}
+#endif
+
+SandboxBrokerPolicyFactory::SandboxBrokerPolicyFactory()
+{
+ // Policy entries that are the same in every process go here, and
+ // are cached over the lifetime of the factory.
+#if defined(MOZ_CONTENT_SANDBOX) && defined(MOZ_WIDGET_GONK)
+ SandboxBroker::Policy* policy = new SandboxBroker::Policy;
+
+ // Devices that need write access:
+ policy->AddPath(rdwr, "/dev/genlock"); // bug 980924
+ policy->AddPath(rdwr, "/dev/ashmem"); // bug 980947
+ policy->AddTree(wronly, "/dev/log"); // bug 1199857
+ // Graphics devices are a significant source of attack surface, but
+ // there's not much we can do about it without proxying (which is
+ // very difficult and a perforamnce hit).
+ policy->AddPrefix(rdwr, "/dev", "kgsl"); // bug 995072
+ policy->AddPath(rdwr, "/dev/qemu_pipe"); // but 1198410: goldfish gralloc.
+
+ // Bug 1198475: mochitest logs. (This is actually passed in via URL
+ // query param to the mochitest page, and is configurable, so this
+ // isn't enough in general, but hopefully it's good enough for B2G.)
+ // Conditional on tests being run, using the same check seen in
+ // DirectoryProvider.js to set ProfD.
+ if (access("/data/local/tests/profile", R_OK) == 0) {
+ policy->AddPath(wrlog, "/data/local/tests/log/mochitest.log");
+ }
+
+ // Read-only items below this line.
+
+ policy->AddPath(rdonly, "/dev/urandom"); // bug 964500, bug 995069
+ policy->AddPath(rdonly, "/dev/ion"); // bug 980937
+ policy->AddPath(rdonly, "/proc/cpuinfo"); // bug 995067
+ policy->AddPath(rdonly, "/proc/meminfo"); // bug 1025333
+ policy->AddPath(rdonly, "/sys/devices/system/cpu/present"); // bug 1025329
+ policy->AddPath(rdonly, "/sys/devices/system/soc/soc0/id"); // bug 1025339
+ policy->AddPath(rdonly, "/etc/media_profiles.xml"); // bug 1198419
+ policy->AddPath(rdonly, "/etc/media_codecs.xml"); // bug 1198460
+ policy->AddTree(rdonly, "/system/fonts"); // bug 1026063
+
+ // Bug 1199051 (crossplatformly, this is NS_GRE_DIR).
+ policy->AddTree(rdonly, "/system/b2g");
+
+ // Bug 1026356: dynamic library loading from assorted frameworks we
+ // don't control (media codecs, maybe others).
+ //
+ // Bug 1198515: Also, the profiler calls breakpad code to get info
+ // on all loaded ELF objects, which opens those files.
+ policy->AddTree(rdonly, "/system/lib");
+ policy->AddTree(rdonly, "/vendor/lib");
+ policy->AddPath(rdonly, "/system/bin/linker"); // (profiler only)
+
+ // Bug 1199866: EGL/WebGL.
+ policy->AddPath(rdonly, "/system/lib/egl");
+ policy->AddPath(rdonly, "/vendor/lib/egl");
+
+ // Bug 1198401: timezones. Yes, we need both of these; see bug.
+ policy->AddTree(rdonly, "/system/usr/share/zoneinfo");
+ policy->AddTree(rdonly, "/system//usr/share/zoneinfo");
+
+ policy->AddPath(rdonly, "/data/local/tmp/profiler.options",
+ SandboxBroker::Policy::AddAlways); // bug 1029337
+
+ mCommonContentPolicy.reset(policy);
+#elif defined(MOZ_CONTENT_SANDBOX)
+ SandboxBroker::Policy* policy = new SandboxBroker::Policy;
+ policy->AddDir(rdonly, "/");
+ policy->AddDir(rdwrcr, "/dev/shm");
+ // Add write permissions on the temporary directory. This can come
+ // from various environment variables (TMPDIR,TMP,TEMP,...) so
+ // make sure to use the full logic.
+ nsCOMPtr<nsIFile> tmpDir;
+ nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
+ getter_AddRefs(tmpDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = tmpDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdwrcr, tmpPath.get());
+ }
+ }
+ // If the above fails at any point, fall back to a very good guess.
+ if (NS_FAILED(rv)) {
+ policy->AddDir(rdwrcr, "/tmp");
+ }
+
+ // Bug 1308851: NVIDIA proprietary driver when using WebGL
+ policy->AddPrefix(rdwr, "/dev", "nvidia");
+
+ // Bug 1312678: radeonsi/Intel with DRI when using WebGL
+ policy->AddDir(rdwr, "/dev/dri");
+
+ mCommonContentPolicy.reset(policy);
+#endif
+}
+
+#ifdef MOZ_CONTENT_SANDBOX
+UniquePtr<SandboxBroker::Policy>
+SandboxBrokerPolicyFactory::GetContentPolicy(int aPid)
+{
+ // Policy entries that vary per-process (currently the only reason
+ // that can happen is because they contain the pid) are added here.
+
+ MOZ_ASSERT(NS_IsMainThread());
+ // File broker usage is controlled through a pref.
+ if (Preferences::GetInt("security.sandbox.content.level") <= 1) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mCommonContentPolicy);
+#if defined(MOZ_WIDGET_GONK)
+ // Allow overriding "unsupported"ness with a pref, for testing.
+ if (!IsSystemSupported()) {
+ return nullptr;
+ }
+ UniquePtr<SandboxBroker::Policy>
+ policy(new SandboxBroker::Policy(*mCommonContentPolicy));
+
+ // Bug 1029337: where the profiler writes the data.
+ nsPrintfCString profilerLogPath("/data/local/tmp/profile_%d_%d.txt",
+ GeckoProcessType_Content, aPid);
+ policy->AddPath(wrlog, profilerLogPath.get());
+
+ // Bug 1198550: the profiler's replacement for dl_iterate_phdr
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/maps", aPid).get());
+
+ // Bug 1198552: memory reporting.
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get());
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get());
+
+ return policy;
+#else
+ UniquePtr<SandboxBroker::Policy>
+ policy(new SandboxBroker::Policy(*mCommonContentPolicy));
+ // Return the common policy.
+ return policy;
+#endif
+}
+
+#endif // MOZ_CONTENT_SANDBOX
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h
new file mode 100644
index 000000000..c66a09189
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxBrokerPolicyFactory_h
+#define mozilla_SandboxBrokerPolicyFactory_h
+
+#include "mozilla/SandboxBroker.h"
+
+namespace mozilla {
+
+class SandboxBrokerPolicyFactory {
+public:
+ SandboxBrokerPolicyFactory();
+
+#ifdef MOZ_CONTENT_SANDBOX
+ UniquePtr<SandboxBroker::Policy> GetContentPolicy(int aPid);
+#endif
+
+private:
+ UniquePtr<const SandboxBroker::Policy> mCommonContentPolicy;
+ // B2G devices tend to have hardware-specific paths used by device
+ // drivers, so rollout of filesystem isolation will need per-device
+ // testing. This predicate allows that to happen gradually.
+ static bool IsSystemSupported();
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxBrokerPolicyFactory_h
diff --git a/security/sandbox/linux/broker/SandboxBrokerUtils.h b/security/sandbox/linux/broker/SandboxBrokerUtils.h
new file mode 100644
index 000000000..1db4f4411
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerUtils.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_SandboxBrokerUtils_h
+#define mozilla_SandboxBrokerUtils_h
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+// On 32-bit Linux, stat calls are translated by libc into stat64
+// calls. We'll intercept those and handle them in the stat functions
+// but must be sure to use the right structure layout.
+
+#if defined(__NR_stat64)
+typedef struct stat64 statstruct;
+#define statsyscall stat64
+#define lstatsyscall lstat64
+#elif defined(__NR_stat)
+typedef struct stat statstruct;
+#define statsyscall stat
+#define lstatsyscall lstat
+#else
+#error Missing stat syscall include.
+#endif
+
+#endif // mozilla_SandboxBrokerUtils_h
diff --git a/security/sandbox/linux/broker/moz.build b/security/sandbox/linux/broker/moz.build
new file mode 100644
index 000000000..343a5cfad
--- /dev/null
+++ b/security/sandbox/linux/broker/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; python-indent: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla += [
+ 'SandboxBroker.h',
+ 'SandboxBrokerCommon.h',
+ 'SandboxBrokerPolicyFactory.h',
+]
+
+SOURCES += [
+ 'SandboxBroker.cpp',
+ 'SandboxBrokerCommon.cpp',
+ 'SandboxBrokerPolicyFactory.cpp',
+]
+
+if CONFIG['OS_TARGET'] == 'Android':
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ DEFINES['HAVE_ANDROID_OS'] = True
+
+LOCAL_INCLUDES += [
+ '/security/sandbox/linux', # SandboxLogging.h, SandboxInfo.h
+]
+
+# Need this for mozilla::ipc::FileDescriptor etc.
+include('/ipc/chromium/chromium-config.mozbuild')
+
+# Need this for safe_sprintf.h used by SandboxLogging.h,
+# but it has to be after ipc/chromium/src.
+LOCAL_INCLUDES += [
+ '/security/sandbox/chromium',
+]
+
+
+FINAL_LIBRARY = 'xul'
diff --git a/security/sandbox/linux/glue/SandboxCrash.cpp b/security/sandbox/linux/glue/SandboxCrash.cpp
new file mode 100644
index 000000000..87a75e845
--- /dev/null
+++ b/security/sandbox/linux/glue/SandboxCrash.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file needs to be linked into libxul, so it can access the JS
+// stack and the crash reporter. Everything else in this directory
+// should be able to be linked into its own shared library, in order
+// to be able to isolate sandbox/chromium from ipc/chromium.
+
+#include "SandboxInternal.h"
+#include "SandboxLogging.h"
+
+#include <unistd.h>
+#include <sys/syscall.h>
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Exceptions.h"
+#include "nsContentUtils.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+#include "mozilla/StackWalk.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+// Log JS stack info in the same place as the sandbox violation
+// message. Useful in case the responsible code is JS and all we have
+// are logs and a minidump with the C++ stacks (e.g., on TBPL).
+static void
+SandboxLogJSStack(void)
+{
+ if (!NS_IsMainThread()) {
+ // This might be a worker thread... or it might be a non-JS
+ // thread, or a non-NSPR thread. There's isn't a good API for
+ // dealing with this, yet.
+ return;
+ }
+ if (!nsContentUtils::XPConnect()) {
+ // There is no content (e.g., the process is a media plugin), in
+ // which case this will probably crash and definitely not work.
+ return;
+ }
+ nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack();
+ // If we got a stack, we must have a current JSContext. This is icky. :(
+ // Would be better if GetCurrentJSStack() handed out the JSContext it ended up
+ // using or something.
+ JSContext* cx = frame ? nsContentUtils::GetCurrentJSContext() : nullptr;
+ for (int i = 0; frame != nullptr; ++i) {
+ nsAutoString fileName, funName;
+ int32_t lineNumber;
+
+ // Don't stop unwinding if an attribute can't be read.
+ fileName.SetIsVoid(true);
+ Unused << frame->GetFilename(cx, fileName);
+ lineNumber = 0;
+ Unused << frame->GetLineNumber(cx, &lineNumber);
+ funName.SetIsVoid(true);
+ Unused << frame->GetName(cx, funName);
+
+ if (!funName.IsVoid() || !fileName.IsVoid()) {
+ SANDBOX_LOG_ERROR("JS frame %d: %s %s line %d", i,
+ funName.IsVoid() ?
+ "(anonymous)" : NS_ConvertUTF16toUTF8(funName).get(),
+ fileName.IsVoid() ?
+ "(no file)" : NS_ConvertUTF16toUTF8(fileName).get(),
+ lineNumber);
+ }
+
+ nsCOMPtr<nsIStackFrame> nextFrame;
+ nsresult rv = frame->GetCaller(cx, getter_AddRefs(nextFrame));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ frame = nextFrame;
+ }
+}
+
+static void SandboxPrintStackFrame(uint32_t aFrameNumber, void *aPC, void *aSP,
+ void *aClosure)
+{
+ char buf[1024];
+ MozCodeAddressDetails details;
+
+ MozDescribeCodeAddress(aPC, &details);
+ MozFormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details);
+ SANDBOX_LOG_ERROR("frame %s", buf);
+}
+
+static void
+SandboxLogCStack()
+{
+ // Skip 3 frames: one for this module, one for the signal handler in
+ // libmozsandbox, and one for the signal trampoline.
+ //
+ // Warning: this might not print any stack frames. MozStackWalk
+ // can't walk past the signal trampoline on ARM (bug 968531), and
+ // x86 frame pointer walking may or may not work (bug 1082276).
+
+ MozStackWalk(SandboxPrintStackFrame, /* skip */ 3, /* max */ 0,
+ nullptr, 0, nullptr);
+ SANDBOX_LOG_ERROR("end of stack.");
+}
+
+static void
+SandboxCrash(int nr, siginfo_t *info, void *void_context)
+{
+ pid_t pid = getpid(), tid = syscall(__NR_gettid);
+ bool dumped = false;
+
+#ifdef MOZ_CRASHREPORTER
+ dumped = CrashReporter::WriteMinidumpForSigInfo(nr, info, void_context);
+#endif
+ if (!dumped) {
+ SANDBOX_LOG_ERROR("crash reporter is disabled (or failed);"
+ " trying stack trace:");
+ SandboxLogCStack();
+ }
+
+ // Do this last, in case it crashes or deadlocks.
+ SandboxLogJSStack();
+
+ // Try to reraise, so the parent sees that this process crashed.
+ // (If tgkill is forbidden, then seccomp will raise SIGSYS, which
+ // also accomplishes that goal.)
+ signal(SIGSYS, SIG_DFL);
+ syscall(__NR_tgkill, pid, tid, nr);
+}
+
+static void __attribute__((constructor))
+SandboxSetCrashFunc()
+{
+ gSandboxCrashFunc = SandboxCrash;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/glue/moz.build b/security/sandbox/linux/glue/moz.build
new file mode 100644
index 000000000..0d40dcd63
--- /dev/null
+++ b/security/sandbox/linux/glue/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; python-indent: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ '../../chromium/base/strings/safe_sprintf.cc',
+ '../SandboxLogging.cpp',
+ 'SandboxCrash.cpp',
+]
+
+# Avoid Chromium logging dependency, because this is going into
+# libxul. See also the comment in SandboxLogging.h.
+SOURCES['../../chromium/base/strings/safe_sprintf.cc'].flags += ['-DNDEBUG']
+
+LOCAL_INCLUDES += [
+ '/security/sandbox/chromium',
+ '/security/sandbox/linux',
+]
+
+USE_LIBS += [
+ 'mozsandbox',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/security/sandbox/linux/gtest/TestBroker.cpp b/security/sandbox/linux/gtest/TestBroker.cpp
new file mode 100644
index 000000000..a311e181a
--- /dev/null
+++ b/security/sandbox/linux/gtest/TestBroker.cpp
@@ -0,0 +1,626 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "broker/SandboxBroker.h"
+#include "broker/SandboxBrokerUtils.h"
+#include "SandboxBrokerClient.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <semaphore.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/NullPtr.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/FileDescriptor.h"
+
+namespace mozilla {
+
+static const int MAY_ACCESS = SandboxBroker::MAY_ACCESS;
+static const int MAY_READ = SandboxBroker::MAY_READ;
+static const int MAY_WRITE = SandboxBroker::MAY_WRITE;
+static const int MAY_CREATE = SandboxBroker::MAY_CREATE;
+static const auto AddAlways = SandboxBroker::Policy::AddAlways;
+
+class SandboxBrokerTest : public ::testing::Test
+{
+ UniquePtr<SandboxBroker> mServer;
+ UniquePtr<SandboxBrokerClient> mClient;
+
+ UniquePtr<const SandboxBroker::Policy> GetPolicy() const;
+
+ template<class C, void (C::* Main)()>
+ static void* ThreadMain(void* arg) {
+ (static_cast<C*>(arg)->*Main)();
+ return nullptr;
+ }
+
+protected:
+ int Open(const char* aPath, int aFlags) {
+ return mClient->Open(aPath, aFlags);
+ }
+ int Access(const char* aPath, int aMode) {
+ return mClient->Access(aPath, aMode);
+ }
+ int Stat(const char* aPath, statstruct* aStat) {
+ return mClient->Stat(aPath, aStat);
+ }
+ int LStat(const char* aPath, statstruct* aStat) {
+ return mClient->LStat(aPath, aStat);
+ }
+ int Chmod(const char* aPath, int aMode) {
+ return mClient->Chmod(aPath, aMode);
+ }
+ int Link(const char* aPath, const char* bPath) {
+ return mClient->Link(aPath, bPath);
+ }
+ int Mkdir(const char* aPath, int aMode) {
+ return mClient->Mkdir(aPath, aMode);
+ }
+ int Symlink(const char* aPath, const char* bPath) {
+ return mClient->Symlink(aPath, bPath);
+ }
+ int Rename(const char* aPath, const char* bPath) {
+ return mClient->Rename(aPath, bPath);
+ }
+ int Rmdir(const char* aPath) {
+ return mClient->Rmdir(aPath);
+ }
+ int Unlink(const char* aPath) {
+ return mClient->Unlink(aPath);
+ }
+ ssize_t Readlink(const char* aPath, char* aBuff, size_t aSize) {
+ return mClient->Readlink(aPath, aBuff, aSize);
+ }
+
+ virtual void SetUp() {
+ ipc::FileDescriptor fd;
+
+ mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd);
+ ASSERT_NE(mServer, nullptr);
+ ASSERT_TRUE(fd.IsValid());
+ auto rawFD = fd.ClonePlatformHandle();
+ mClient.reset(new SandboxBrokerClient(rawFD.release()));
+ }
+
+ template<class C, void (C::* Main)()>
+ void StartThread(pthread_t *aThread) {
+ ASSERT_EQ(0, pthread_create(aThread, nullptr, ThreadMain<C, Main>,
+ static_cast<C*>(this)));
+ }
+
+ template<class C, void (C::* Main)()>
+ void RunOnManyThreads() {
+ static const int kNumThreads = 5;
+ pthread_t threads[kNumThreads];
+ for (int i = 0; i < kNumThreads; ++i) {
+ StartThread<C, Main>(&threads[i]);
+ }
+ for (int i = 0; i < kNumThreads; ++i) {
+ void* retval;
+ ASSERT_EQ(pthread_join(threads[i], &retval), 0);
+ ASSERT_EQ(retval, static_cast<void*>(nullptr));
+ }
+ }
+
+public:
+ void MultiThreadOpenWorker();
+ void MultiThreadStatWorker();
+};
+
+UniquePtr<const SandboxBroker::Policy>
+SandboxBrokerTest::GetPolicy() const
+{
+ UniquePtr<SandboxBroker::Policy> policy(new SandboxBroker::Policy());
+
+ policy->AddPath(MAY_READ | MAY_WRITE, "/dev/null", AddAlways);
+ policy->AddPath(MAY_READ, "/dev/zero", AddAlways);
+ policy->AddPath(MAY_READ, "/var/empty/qwertyuiop", AddAlways);
+ policy->AddPath(MAY_ACCESS, "/proc/self", AddAlways); // Warning: Linux-specific.
+ policy->AddPath(MAY_READ | MAY_WRITE, "/tmp", AddAlways);
+ policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublu", AddAlways);
+ policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublublu", AddAlways);
+
+ return Move(policy);
+}
+
+TEST_F(SandboxBrokerTest, OpenForRead)
+{
+ int fd;
+
+ fd = Open("/dev/null", O_RDONLY);
+ ASSERT_GE(fd, 0) << "Opening /dev/null failed.";
+ close(fd);
+ fd = Open("/dev/zero", O_RDONLY);
+ ASSERT_GE(fd, 0) << "Opening /dev/zero failed.";
+ close(fd);
+ fd = Open("/var/empty/qwertyuiop", O_RDONLY);
+ EXPECT_EQ(-ENOENT, fd) << "Opening allowed but nonexistent file succeeded.";
+ fd = Open("/proc/self", O_RDONLY);
+ EXPECT_EQ(-EACCES, fd) << "Opening stat-only file for read succeeded.";
+ fd = Open("/proc/self/stat", O_RDONLY);
+ EXPECT_EQ(-EACCES, fd) << "Opening disallowed file succeeded.";
+}
+
+TEST_F(SandboxBrokerTest, OpenForWrite)
+{
+ int fd;
+
+ fd = Open("/dev/null", O_WRONLY);
+ ASSERT_GE(fd, 0) << "Opening /dev/null write-only failed.";
+ close(fd);
+ fd = Open("/dev/null", O_RDWR);
+ ASSERT_GE(fd, 0) << "Opening /dev/null read/write failed.";
+ close(fd);
+ fd = Open("/dev/zero", O_WRONLY);
+ ASSERT_EQ(-EACCES, fd) << "Opening read-only-by-policy file write-only succeeded.";
+ fd = Open("/dev/zero", O_RDWR);
+ ASSERT_EQ(-EACCES, fd) << "Opening read-only-by-policy file read/write succeeded.";
+}
+
+TEST_F(SandboxBrokerTest, SimpleRead)
+{
+ int fd;
+ char c;
+
+ fd = Open("/dev/null", O_RDONLY);
+ ASSERT_GE(fd, 0);
+ EXPECT_EQ(0, read(fd, &c, 1));
+ close(fd);
+ fd = Open("/dev/zero", O_RDONLY);
+ ASSERT_GE(fd, 0);
+ ASSERT_EQ(1, read(fd, &c, 1));
+ EXPECT_EQ(c, '\0');
+}
+
+TEST_F(SandboxBrokerTest, Access)
+{
+ EXPECT_EQ(0, Access("/dev/null", F_OK));
+ EXPECT_EQ(0, Access("/dev/null", R_OK));
+ EXPECT_EQ(0, Access("/dev/null", W_OK));
+ EXPECT_EQ(0, Access("/dev/null", R_OK|W_OK));
+ EXPECT_EQ(-EACCES, Access("/dev/null", X_OK));
+ EXPECT_EQ(-EACCES, Access("/dev/null", R_OK|X_OK));
+
+ EXPECT_EQ(0, Access("/dev/zero", R_OK));
+ EXPECT_EQ(-EACCES, Access("/dev/zero", W_OK));
+ EXPECT_EQ(-EACCES, Access("/dev/zero", R_OK|W_OK));
+
+ EXPECT_EQ(-ENOENT, Access("/var/empty/qwertyuiop", R_OK));
+ EXPECT_EQ(-EACCES, Access("/var/empty/qwertyuiop", W_OK));
+
+ EXPECT_EQ(0, Access("/proc/self", F_OK));
+ EXPECT_EQ(-EACCES, Access("/proc/self", R_OK));
+
+ EXPECT_EQ(-EACCES, Access("/proc/self/stat", F_OK));
+}
+
+TEST_F(SandboxBrokerTest, Stat)
+{
+ statstruct realStat, brokeredStat;
+ ASSERT_EQ(0, statsyscall("/dev/null", &realStat)) << "Shouldn't ever fail!";
+ EXPECT_EQ(0, Stat("/dev/null", &brokeredStat));
+ EXPECT_EQ(realStat.st_ino, brokeredStat.st_ino);
+ EXPECT_EQ(realStat.st_rdev, brokeredStat.st_rdev);
+
+ EXPECT_EQ(-ENOENT, Stat("/var/empty/qwertyuiop", &brokeredStat));
+ EXPECT_EQ(-EACCES, Stat("/dev", &brokeredStat));
+
+ EXPECT_EQ(0, Stat("/proc/self", &brokeredStat));
+ EXPECT_TRUE(S_ISDIR(brokeredStat.st_mode));
+}
+
+TEST_F(SandboxBrokerTest, LStat)
+{
+ statstruct realStat, brokeredStat;
+ ASSERT_EQ(0, lstatsyscall("/dev/null", &realStat));
+ EXPECT_EQ(0, LStat("/dev/null", &brokeredStat));
+ EXPECT_EQ(realStat.st_ino, brokeredStat.st_ino);
+ EXPECT_EQ(realStat.st_rdev, brokeredStat.st_rdev);
+
+ EXPECT_EQ(-ENOENT, LStat("/var/empty/qwertyuiop", &brokeredStat));
+ EXPECT_EQ(-EACCES, LStat("/dev", &brokeredStat));
+
+ EXPECT_EQ(0, LStat("/proc/self", &brokeredStat));
+ EXPECT_TRUE(S_ISLNK(brokeredStat.st_mode));
+}
+
+static void PrePostTestCleanup(void)
+{
+ unlink("/tmp/blublu");
+ rmdir("/tmp/blublu");
+ unlink("/tmp/nope");
+ rmdir("/tmp/nope");
+ unlink("/tmp/blublublu");
+ rmdir("/tmp/blublublu");
+}
+
+TEST_F(SandboxBrokerTest, Chmod)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ // Set read only. SandboxBroker enforces 0600 mode flags.
+ ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR));
+ // SandboxBroker doesn't use real access(), it just checks against
+ // the policy. So it can't see the change in permisions here.
+ // This won't work:
+ // EXPECT_EQ(-EACCES, Access("/tmp/blublu", W_OK));
+ statstruct realStat;
+ EXPECT_EQ(0, statsyscall("/tmp/blublu", &realStat));
+ EXPECT_EQ((mode_t)S_IRUSR, realStat.st_mode & 0777);
+
+ ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR | S_IWUSR));
+ EXPECT_EQ(0, statsyscall("/tmp/blublu", &realStat));
+ EXPECT_EQ((mode_t)(S_IRUSR | S_IWUSR), realStat.st_mode & 0777);
+ EXPECT_EQ(0, unlink("/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Link)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ ASSERT_EQ(0, Link("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Link("/tmp/blublu", "/tmp/nope"));
+ EXPECT_EQ(0, unlink("/tmp/blublublu"));
+ EXPECT_EQ(0, unlink("/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Symlink)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ statstruct aStat;
+ ASSERT_EQ(0, lstatsyscall("/tmp/blublublu", &aStat));
+ EXPECT_EQ((mode_t)S_IFLNK, aStat.st_mode & S_IFMT);
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Symlink("/tmp/blublu", "/tmp/nope"));
+ EXPECT_EQ(0, unlink("/tmp/blublublu"));
+ EXPECT_EQ(0, unlink("/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Mkdir)
+{
+ PrePostTestCleanup();
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
+ << "Creating dir /tmp/blublu failed.";
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Mkdir("/tmp/nope", 0600))
+ << "Creating dir without MAY_CREATE succeed.";
+ EXPECT_EQ(0, rmdir("/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Rename)
+{
+ PrePostTestCleanup();
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
+ << "Creating dir /tmp/blublu failed.";
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ ASSERT_EQ(0, Rename("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ EXPECT_EQ(-ENOENT , Access("/tmp/blublu", F_OK));
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Rename("/tmp/blublublu", "/tmp/nope"))
+ << "Renaming dir without write access succeed.";
+ EXPECT_EQ(0, rmdir("/tmp/blublublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Rmdir)
+{
+ PrePostTestCleanup();
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
+ << "Creating dir /tmp/blublu failed.";
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ ASSERT_EQ(0, Rmdir("/tmp/blublu"));
+ EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
+ // Bypass sandbox to create a non-deletable dir
+ ASSERT_EQ(0, mkdir("/tmp/nope", 0600));
+ EXPECT_EQ(-EACCES, Rmdir("/tmp/nope"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Unlink)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ EXPECT_EQ(0, Unlink("/tmp/blublu"));
+ EXPECT_EQ(-ENOENT , Access("/tmp/blublu", F_OK));
+ // Bypass sandbox to write a non-deletable file
+ fd = open("/tmp/nope", O_WRONLY | O_CREAT, 0600);
+ ASSERT_GE(fd, 0) << "Opening /tmp/nope for writing failed.";
+ close(fd);
+ EXPECT_EQ(-EACCES, Unlink("/tmp/nope"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Readlink)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ char linkBuff[256];
+ EXPECT_EQ(11, Readlink("/tmp/blublublu", linkBuff, sizeof(linkBuff)));
+ linkBuff[11] = '\0';
+ EXPECT_EQ(0, strcmp(linkBuff, "/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, MultiThreadOpen) {
+ RunOnManyThreads<SandboxBrokerTest,
+ &SandboxBrokerTest::MultiThreadOpenWorker>();
+}
+void SandboxBrokerTest::MultiThreadOpenWorker() {
+ static const int kNumLoops = 10000;
+
+ for (int i = 1; i <= kNumLoops; ++i) {
+ int nullfd = Open("/dev/null", O_RDONLY);
+ int zerofd = Open("/dev/zero", O_RDONLY);
+ ASSERT_GE(nullfd, 0) << "Loop " << i << "/" << kNumLoops;
+ ASSERT_GE(zerofd, 0) << "Loop " << i << "/" << kNumLoops;
+ char c;
+ ASSERT_EQ(0, read(nullfd, &c, 1)) << "Loop " << i << "/" << kNumLoops;
+ ASSERT_EQ(1, read(zerofd, &c, 1)) << "Loop " << i << "/" << kNumLoops;
+ ASSERT_EQ('\0', c) << "Loop " << i << "/" << kNumLoops;
+ close(nullfd);
+ close(zerofd);
+ }
+}
+
+TEST_F(SandboxBrokerTest, MultiThreadStat) {
+ RunOnManyThreads<SandboxBrokerTest,
+ &SandboxBrokerTest::MultiThreadStatWorker>();
+}
+void SandboxBrokerTest::MultiThreadStatWorker() {
+ static const int kNumLoops = 7500;
+ statstruct nullStat, zeroStat, selfStat;
+ dev_t realNullDev, realZeroDev;
+ ino_t realSelfInode;
+
+ ASSERT_EQ(0, statsyscall("/dev/null", &nullStat)) << "Shouldn't ever fail!";
+ ASSERT_EQ(0, statsyscall("/dev/zero", &zeroStat)) << "Shouldn't ever fail!";
+ ASSERT_EQ(0, lstatsyscall("/proc/self", &selfStat)) << "Shouldn't ever fail!";
+ ASSERT_TRUE(S_ISLNK(selfStat.st_mode)) << "Shouldn't ever fail!";
+ realNullDev = nullStat.st_rdev;
+ realZeroDev = zeroStat.st_rdev;
+ realSelfInode = selfStat.st_ino;
+ for (int i = 1; i <= kNumLoops; ++i) {
+ ASSERT_EQ(0, Stat("/dev/null", &nullStat))
+ << "Loop " << i << "/" << kNumLoops;
+ ASSERT_EQ(0, Stat("/dev/zero", &zeroStat))
+ << "Loop " << i << "/" << kNumLoops;
+ ASSERT_EQ(0, LStat("/proc/self", &selfStat))
+ << "Loop " << i << "/" << kNumLoops;
+
+ ASSERT_EQ(realNullDev, nullStat.st_rdev)
+ << "Loop " << i << "/" << kNumLoops;
+ ASSERT_EQ(realZeroDev, zeroStat.st_rdev)
+ << "Loop " << i << "/" << kNumLoops;
+ ASSERT_TRUE(S_ISLNK(selfStat.st_mode))
+ << "Loop " << i << "/" << kNumLoops;
+ ASSERT_EQ(realSelfInode, selfStat.st_ino)
+ << "Loop " << i << "/" << kNumLoops;
+ }
+}
+
+#if 0
+class SandboxBrokerSigStress : public SandboxBrokerTest
+{
+ int mSigNum;
+ struct sigaction mOldAction;
+ Atomic<void*> mVoidPtr;
+
+ static void SigHandler(int aSigNum, siginfo_t* aSigInfo, void *aCtx) {
+ ASSERT_EQ(SI_QUEUE, aSigInfo->si_code);
+ SandboxBrokerSigStress* that =
+ static_cast<SandboxBrokerSigStress*>(aSigInfo->si_value.sival_ptr);
+ ASSERT_EQ(that->mSigNum, aSigNum);
+ that->DoSomething();
+ }
+
+protected:
+ Atomic<int> mTestIter;
+ sem_t mSemaphore;
+
+ void SignalThread(pthread_t aThread) {
+ union sigval sv;
+ sv.sival_ptr = this;
+ ASSERT_NE(0, mSigNum);
+ ASSERT_EQ(0, pthread_sigqueue(aThread, mSigNum, sv));
+ }
+
+ virtual void SetUp() {
+ ASSERT_EQ(0, sem_init(&mSemaphore, 0, 0));
+ mVoidPtr = nullptr;
+ mSigNum = 0;
+ for (int sigNum = SIGRTMIN; sigNum < SIGRTMAX; ++sigNum) {
+ ASSERT_EQ(0, sigaction(sigNum, nullptr, &mOldAction));
+ if ((mOldAction.sa_flags & SA_SIGINFO) == 0 &&
+ mOldAction.sa_handler == SIG_DFL) {
+ struct sigaction newAction;
+ PodZero(&newAction);
+ newAction.sa_flags = SA_SIGINFO;
+ newAction.sa_sigaction = SigHandler;
+ ASSERT_EQ(0, sigaction(sigNum, &newAction, nullptr));
+ mSigNum = sigNum;
+ break;
+ }
+ }
+ ASSERT_NE(mSigNum, 0);
+
+ SandboxBrokerTest::SetUp();
+ }
+
+ virtual void TearDown() {
+ ASSERT_EQ(0, sem_destroy(&mSemaphore));
+ if (mSigNum != 0) {
+ ASSERT_EQ(0, sigaction(mSigNum, &mOldAction, nullptr));
+ }
+ if (mVoidPtr) {
+ free(mVoidPtr);
+ }
+ }
+
+ void DoSomething();
+
+public:
+ void MallocWorker();
+ void FreeWorker();
+};
+
+TEST_F(SandboxBrokerSigStress, StressTest)
+{
+ static const int kIters = 6250;
+ static const int kNsecPerIterPerIter = 4;
+ struct timespec delay = { 0, 0 };
+ pthread_t threads[2];
+
+ mTestIter = kIters;
+
+ StartThread<SandboxBrokerSigStress,
+ &SandboxBrokerSigStress::MallocWorker>(&threads[0]);
+ StartThread<SandboxBrokerSigStress,
+ &SandboxBrokerSigStress::FreeWorker>(&threads[1]);
+
+ for (int i = kIters; i > 0; --i) {
+ SignalThread(threads[i % 2]);
+ while (sem_wait(&mSemaphore) == -1 && errno == EINTR)
+ /* retry */;
+ ASSERT_EQ(i - 1, mTestIter);
+ delay.tv_nsec += kNsecPerIterPerIter;
+ struct timespec req = delay, rem;
+ while (nanosleep(&req, &rem) == -1 && errno == EINTR) {
+ req = rem;
+ }
+ }
+ void *retval;
+ ASSERT_EQ(0, pthread_join(threads[0], &retval));
+ ASSERT_EQ(nullptr, retval);
+ ASSERT_EQ(0, pthread_join(threads[1], &retval));
+ ASSERT_EQ(nullptr, retval);
+}
+
+void
+SandboxBrokerSigStress::MallocWorker()
+{
+ static const size_t kSize = 64;
+
+ void* mem = malloc(kSize);
+ while (mTestIter > 0) {
+ ASSERT_NE(mem, mVoidPtr);
+ mem = mVoidPtr.exchange(mem);
+ if (mem) {
+ sched_yield();
+ } else {
+ mem = malloc(kSize);
+ }
+ }
+ if (mem) {
+ free(mem);
+ }
+}
+
+void
+SandboxBrokerSigStress::FreeWorker()
+{
+ void *mem = nullptr;
+ while (mTestIter > 0) {
+ mem = mVoidPtr.exchange(mem);
+ if (mem) {
+ free(mem);
+ mem = nullptr;
+ } else {
+ sched_yield();
+ }
+ }
+}
+
+void
+SandboxBrokerSigStress::DoSomething()
+{
+ int fd;
+ char c;
+ struct stat st;
+
+ //fprintf(stderr, "Don't try this at home: %d\n", static_cast<int>(mTestIter));
+ switch (mTestIter % 5) {
+ case 0:
+ fd = Open("/dev/null", O_RDWR);
+ ASSERT_GE(fd, 0);
+ ASSERT_EQ(0, read(fd, &c, 1));
+ close(fd);
+ break;
+ case 1:
+ fd = Open("/dev/zero", O_RDONLY);
+ ASSERT_GE(fd, 0);
+ ASSERT_EQ(1, read(fd, &c, 1));
+ ASSERT_EQ('\0', c);
+ close(fd);
+ break;
+ case 2:
+ ASSERT_EQ(0, Access("/dev/null", W_OK));
+ break;
+ case 3:
+ ASSERT_EQ(0, Stat("/proc/self", &st));
+ ASSERT_TRUE(S_ISDIR(st.st_mode));
+ break;
+ case 4:
+ ASSERT_EQ(0, LStat("/proc/self", &st));
+ ASSERT_TRUE(S_ISLNK(st.st_mode));
+ break;
+ }
+ mTestIter--;
+ sem_post(&mSemaphore);
+}
+#endif
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/gtest/TestBrokerPolicy.cpp b/security/sandbox/linux/gtest/TestBrokerPolicy.cpp
new file mode 100644
index 000000000..474458446
--- /dev/null
+++ b/security/sandbox/linux/gtest/TestBrokerPolicy.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "broker/SandboxBroker.h"
+
+namespace mozilla {
+
+static const int MAY_ACCESS = SandboxBroker::MAY_ACCESS;
+static const int MAY_READ = SandboxBroker::MAY_READ;
+static const int MAY_WRITE = SandboxBroker::MAY_WRITE;
+//static const int MAY_CREATE = SandboxBroker::MAY_CREATE;
+//static const int RECURSIVE = SandboxBroker::RECURSIVE;
+static const auto AddAlways = SandboxBroker::Policy::AddAlways;
+
+TEST(SandboxBrokerPolicyLookup, Simple)
+{
+ SandboxBroker::Policy p;
+ p.AddPath(MAY_READ, "/dev/urandom", AddAlways);
+
+ EXPECT_NE(0, p.Lookup("/dev/urandom")) << "Added path not found.";
+ EXPECT_EQ(MAY_ACCESS | MAY_READ, p.Lookup("/dev/urandom"))
+ << "Added path found with wrong perms.";
+ EXPECT_EQ(0, p.Lookup("/etc/passwd")) << "Non-added path was found.";
+}
+
+TEST(SandboxBrokerPolicyLookup, CopyCtor)
+{
+ SandboxBroker::Policy psrc;
+ psrc.AddPath(MAY_READ | MAY_WRITE, "/dev/null", AddAlways);
+ SandboxBroker::Policy pdst(psrc);
+ psrc.AddPath(MAY_READ, "/dev/zero", AddAlways);
+ pdst.AddPath(MAY_READ, "/dev/urandom", AddAlways);
+
+ EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/null"))
+ << "Common path absent in copy source.";
+ EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, pdst.Lookup("/dev/null"))
+ << "Common path absent in copy destination.";
+
+ EXPECT_EQ(MAY_ACCESS | MAY_READ, psrc.Lookup("/dev/zero"))
+ << "Source-only path is absent.";
+ EXPECT_EQ(0, pdst.Lookup("/dev/zero"))
+ << "Source-only path is present in copy destination.";
+
+ EXPECT_EQ(0, psrc.Lookup("/dev/urandom"))
+ << "Destination-only path is present in copy source.";
+ EXPECT_EQ(MAY_ACCESS | MAY_READ, pdst.Lookup("/dev/urandom"))
+ << "Destination-only path is absent.";
+
+ EXPECT_EQ(0, psrc.Lookup("/etc/passwd"))
+ << "Non-added path is present in copy source.";
+ EXPECT_EQ(0, pdst.Lookup("/etc/passwd"))
+ << "Non-added path is present in copy source.";
+}
+
+TEST(SandboxBrokerPolicyLookup, Recursive)
+{
+ SandboxBroker::Policy psrc;
+ psrc.AddPath(MAY_READ | MAY_WRITE, "/dev/null", AddAlways);
+ psrc.AddPath(MAY_READ, "/dev/zero", AddAlways);
+ psrc.AddPath(MAY_READ, "/dev/urandom", AddAlways);
+
+ EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/null"))
+ << "Basic path is present.";
+ EXPECT_EQ(MAY_ACCESS | MAY_READ, psrc.Lookup("/dev/zero"))
+ << "Basic path has no extra flags";
+
+ psrc.AddDir(MAY_READ | MAY_WRITE, "/dev/");
+
+ EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/random"))
+ << "Permission via recursive dir.";
+ EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/sd/0"))
+ << "Permission via recursive dir, nested deeper";
+ EXPECT_EQ(0, psrc.Lookup("/dev/sd/0/"))
+ << "Invalid path format.";
+ EXPECT_EQ(0, psrc.Lookup("/usr/dev/sd"))
+ << "Match must be a prefix.";
+
+ psrc.AddDir(MAY_READ, "/dev/sd/");
+ EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/sd/0"))
+ << "Extra permissions from parent path granted.";
+ EXPECT_EQ(0, psrc.Lookup("/dev/.."))
+ << "Refuse attempted subdir escape.";
+
+ psrc.AddDir(MAY_READ, "/tmp");
+ EXPECT_EQ(MAY_ACCESS | MAY_READ, psrc.Lookup("/tmp/good/a"))
+ << "Check whether dir add with no trailing / was sucessful.";
+ EXPECT_EQ(0, psrc.Lookup("/tmp_good_but_bad"))
+ << "Enforce terminator on directories.";
+ EXPECT_EQ(0, psrc.Lookup("/tmp/."))
+ << "Do not allow opening a directory handle.";
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/gtest/TestSandboxUtil.cpp b/security/sandbox/linux/gtest/TestSandboxUtil.cpp
new file mode 100644
index 000000000..cd29813f9
--- /dev/null
+++ b/security/sandbox/linux/gtest/TestSandboxUtil.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "SandboxUtil.h"
+#include "SandboxInfo.h"
+
+#include <pthread.h>
+
+namespace mozilla {
+
+// In order to test IsSingleThreaded when the test-running process is
+// single-threaded, before assorted XPCOM components have created many
+// additional threads, a static initializer is used.
+
+namespace {
+
+struct EarlyTest {
+ bool mWasSingleThreaded;
+
+ EarlyTest()
+ : mWasSingleThreaded(IsSingleThreaded())
+ { }
+};
+
+static const EarlyTest gEarlyTest;
+
+} // namespace
+
+TEST(SandboxUtil, IsSingleThreaded)
+{
+ // If the test system if affected by kUnexpectedThreads, (1) there's
+ // no point in doing this test, and (2) if that happens on Mozilla
+ // CI then burning the tree is an appropriate response.
+ ASSERT_FALSE(SandboxInfo::Get().Test(SandboxInfo::kUnexpectedThreads));
+ EXPECT_TRUE(gEarlyTest.mWasSingleThreaded);
+ EXPECT_FALSE(IsSingleThreaded());
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/gtest/moz.build b/security/sandbox/linux/gtest/moz.build
new file mode 100644
index 000000000..7aecc7fe3
--- /dev/null
+++ b/security/sandbox/linux/gtest/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library('sandboxtest')
+
+SOURCES = [
+ '../SandboxBrokerClient.cpp',
+ '../SandboxUtil.cpp',
+ 'TestBroker.cpp',
+ 'TestBrokerPolicy.cpp',
+ 'TestSandboxUtil.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/security/sandbox/linux',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+ '/security/sandbox/chromium',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/security/sandbox/linux/moz.build b/security/sandbox/linux/moz.build
new file mode 100644
index 000000000..bde75cdb7
--- /dev/null
+++ b/security/sandbox/linux/moz.build
@@ -0,0 +1,116 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SharedLibrary('mozsandbox')
+
+# Depend on mozglue if and only if it's a shared library;
+# this needs to match mozglue/build/moz.build:
+if CONFIG['OS_TARGET'] == 'Android':
+ USE_LIBS += [
+ 'mozglue',
+ ]
+
+EXPORTS.mozilla += [
+ 'Sandbox.h',
+ 'SandboxInfo.h',
+]
+
+SOURCES += [
+ '../chromium-shim/base/logging.cpp',
+ '../chromium/base/at_exit.cc',
+ '../chromium/base/callback_internal.cc',
+ '../chromium/base/lazy_instance.cc',
+ '../chromium/base/memory/ref_counted.cc',
+ '../chromium/base/memory/singleton.cc',
+ '../chromium/base/strings/safe_sprintf.cc',
+ '../chromium/base/strings/string16.cc',
+ '../chromium/base/strings/string_piece.cc',
+ '../chromium/base/strings/string_util.cc',
+ '../chromium/base/strings/string_util_constants.cc',
+ '../chromium/base/strings/stringprintf.cc',
+ '../chromium/base/strings/utf_string_conversion_utils.cc',
+ '../chromium/base/strings/utf_string_conversions.cc',
+ '../chromium/base/synchronization/condition_variable_posix.cc',
+ '../chromium/base/synchronization/lock.cc',
+ '../chromium/base/synchronization/lock_impl_posix.cc',
+ '../chromium/base/synchronization/waitable_event_posix.cc',
+ '../chromium/base/third_party/icu/icu_utf.cc',
+ '../chromium/base/threading/platform_thread_internal_posix.cc',
+ '../chromium/base/threading/platform_thread_linux.cc',
+ '../chromium/base/threading/platform_thread_posix.cc',
+ '../chromium/base/threading/thread_collision_warner.cc',
+ '../chromium/base/threading/thread_id_name_manager.cc',
+ '../chromium/base/threading/thread_local_posix.cc',
+ '../chromium/base/threading/thread_restrictions.cc',
+ '../chromium/base/time/time.cc',
+ '../chromium/base/time/time_posix.cc',
+ '../chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc',
+ '../chromium/sandbox/linux/bpf_dsl/codegen.cc',
+ '../chromium/sandbox/linux/bpf_dsl/dump_bpf.cc',
+ '../chromium/sandbox/linux/bpf_dsl/policy.cc',
+ '../chromium/sandbox/linux/bpf_dsl/policy_compiler.cc',
+ '../chromium/sandbox/linux/bpf_dsl/syscall_set.cc',
+ '../chromium/sandbox/linux/seccomp-bpf/die.cc',
+ '../chromium/sandbox/linux/seccomp-bpf/syscall.cc',
+ '../chromium/sandbox/linux/seccomp-bpf/trap.cc',
+ '../chromium/sandbox/linux/services/syscall_wrappers.cc',
+ 'broker/SandboxBrokerCommon.cpp',
+ 'LinuxCapabilities.cpp',
+ 'Sandbox.cpp',
+ 'SandboxBrokerClient.cpp',
+ 'SandboxChroot.cpp',
+ 'SandboxFilter.cpp',
+ 'SandboxFilterUtil.cpp',
+ 'SandboxHooks.cpp',
+ 'SandboxInfo.cpp',
+ 'SandboxLogging.cpp',
+ 'SandboxUtil.cpp',
+]
+
+# This copy of SafeSPrintf doesn't need to avoid the Chromium logging
+# dependency like the one in libxul does, but this way the behavior is
+# consistent. See also the comment in SandboxLogging.h.
+SOURCES['../chromium/base/strings/safe_sprintf.cc'].flags += ['-DNDEBUG']
+
+# Keep clang from warning about intentional 'switch' fallthrough in icu_utf.cc:
+if CONFIG['CLANG_CXX']:
+ SOURCES['../chromium/base/third_party/icu/icu_utf.cc'].flags += ['-Wno-implicit-fallthrough']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-shadow']
+ SOURCES['../chromium/sandbox/linux/services/syscall_wrappers.cc'].flags += [
+ '-Wno-empty-body',
+ ]
+
+# gcc lto likes to put the top level asm in syscall.cc in a different partition
+# from the function using it which breaks the build. Work around that by
+# forcing there to be only one partition.
+if '-flto' in CONFIG['OS_CXXFLAGS'] and not CONFIG['CLANG_CXX']:
+ LDFLAGS += ['--param lto-partitions=1']
+
+DEFINES['NS_NO_XPCOM'] = True
+DISABLE_STL_WRAPPING = True
+
+LOCAL_INCLUDES += ['/security/sandbox/linux']
+LOCAL_INCLUDES += ['/security/sandbox/chromium-shim']
+LOCAL_INCLUDES += ['/security/sandbox/chromium']
+LOCAL_INCLUDES += ['/nsprpub']
+
+
+if CONFIG['OS_TARGET'] != 'Android':
+ # Needed for clock_gettime with glibc < 2.17:
+ OS_LIBS += [
+ 'rt',
+ ]
+
+DIRS += [
+ 'broker',
+ 'glue',
+]
+
+TEST_DIRS += [
+ 'gtest',
+]