summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /security/sandbox/linux
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/sandbox/linux')
-rw-r--r--security/sandbox/linux/LinuxSched.h35
-rw-r--r--security/sandbox/linux/Sandbox.cpp813
-rw-r--r--security/sandbox/linux/Sandbox.h76
-rw-r--r--security/sandbox/linux/SandboxBrokerClient.cpp274
-rw-r--r--security/sandbox/linux/SandboxBrokerClient.h57
-rw-r--r--security/sandbox/linux/SandboxChrootProto.h21
-rw-r--r--security/sandbox/linux/SandboxFilter.cpp2152
-rw-r--r--security/sandbox/linux/SandboxFilter.h46
-rw-r--r--security/sandbox/linux/SandboxFilterUtil.cpp142
-rw-r--r--security/sandbox/linux/SandboxFilterUtil.h248
-rw-r--r--security/sandbox/linux/SandboxHooks.cpp110
-rw-r--r--security/sandbox/linux/SandboxInfo.cpp201
-rw-r--r--security/sandbox/linux/SandboxInfo.h70
-rw-r--r--security/sandbox/linux/SandboxInternal.h28
-rw-r--r--security/sandbox/linux/SandboxLogging.cpp148
-rw-r--r--security/sandbox/linux/SandboxLogging.h81
-rw-r--r--security/sandbox/linux/SandboxOpenedFiles.cpp77
-rw-r--r--security/sandbox/linux/SandboxOpenedFiles.h97
-rw-r--r--security/sandbox/linux/SandboxReporterClient.cpp88
-rw-r--r--security/sandbox/linux/SandboxReporterClient.h47
-rw-r--r--security/sandbox/linux/broker/SandboxBroker.cpp1104
-rw-r--r--security/sandbox/linux/broker/SandboxBroker.h180
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerCommon.cpp159
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerCommon.h77
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp1083
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h36
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerRealpath.cpp277
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerUtils.h32
-rw-r--r--security/sandbox/linux/broker/moz.build37
-rw-r--r--security/sandbox/linux/glue/SandboxCrash.cpp118
-rw-r--r--security/sandbox/linux/glue/SandboxPrefBridge.cpp50
-rw-r--r--security/sandbox/linux/glue/moz.build35
-rw-r--r--security/sandbox/linux/gtest/TestBroker.cpp689
-rw-r--r--security/sandbox/linux/gtest/TestBrokerPolicy.cpp95
-rw-r--r--security/sandbox/linux/gtest/TestLogging.cpp56
-rw-r--r--security/sandbox/linux/gtest/moz.build26
-rw-r--r--security/sandbox/linux/interfaces/moz.build11
-rw-r--r--security/sandbox/linux/interfaces/mozISandboxReporter.idl65
-rw-r--r--security/sandbox/linux/launch/LinuxCapabilities.cpp26
-rw-r--r--security/sandbox/linux/launch/LinuxCapabilities.h122
-rw-r--r--security/sandbox/linux/launch/SandboxLaunch.cpp675
-rw-r--r--security/sandbox/linux/launch/SandboxLaunch.h71
-rw-r--r--security/sandbox/linux/launch/moz.build33
-rw-r--r--security/sandbox/linux/moz.build139
-rw-r--r--security/sandbox/linux/reporter/SandboxReporter.cpp299
-rw-r--r--security/sandbox/linux/reporter/SandboxReporter.h86
-rw-r--r--security/sandbox/linux/reporter/SandboxReporterCommon.h66
-rw-r--r--security/sandbox/linux/reporter/SandboxReporterWrappers.cpp199
-rw-r--r--security/sandbox/linux/reporter/components.conf13
-rw-r--r--security/sandbox/linux/reporter/moz.build34
50 files changed, 10704 insertions, 0 deletions
diff --git a/security/sandbox/linux/LinuxSched.h b/security/sandbox/linux/LinuxSched.h
new file mode 100644
index 0000000000..3ec34df670
--- /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 0000000000..9b1196bc5f
--- /dev/null
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -0,0 +1,813 @@
+/* -*- 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 "LinuxSched.h"
+#include "SandboxBrokerClient.h"
+#include "SandboxChrootProto.h"
+#include "SandboxFilter.h"
+#include "SandboxInternal.h"
+#include "SandboxLogging.h"
+#include "SandboxOpenedFiles.h"
+#include "SandboxReporterClient.h"
+
+#include <dirent.h>
+#ifdef NIGHTLY_BUILD
+# include "dlfcn.h"
+#endif
+#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/mman.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "mozilla/Array.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Range.h"
+#include "mozilla/SandboxInfo.h"
+#include "mozilla/StackWalk.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "prenv.h"
+#include "base/posix/eintr_wrapper.h"
+#include "sandbox/linux/bpf_dsl/bpf_dsl.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
+
+#ifndef SECCOMP_FILTER_FLAG_SPEC_ALLOW
+# define SECCOMP_FILTER_FLAG_SPEC_ALLOW (1UL << 2)
+#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.
+mozilla::Atomic<int> gSeccompTsyncBroadcastSignum(0);
+
+namespace mozilla {
+
+static mozilla::Atomic<bool> gSandboxCrashOnError(false);
+
+// This is initialized by SandboxSetCrashFunc().
+SandboxCrashFunc gSandboxCrashFunc;
+
+static SandboxReporterClient* gSandboxReporterClient;
+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).
+ */
+MOZ_NEVER_INLINE 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;
+ }
+
+ SandboxReport report = gSandboxReporterClient->MakeReportAndSend(&savedCtx);
+
+ // TODO, someday when this is enabled on MIPS: include the two extra
+ // args in the error message.
+ SANDBOX_LOG(
+ "seccomp sandbox violation: pid %d, tid %d, syscall %d,"
+ " args %d %d %d %d %d %d.%s",
+ report.mPid, report.mTid, report.mSyscall, report.mArgs[0],
+ report.mArgs[1], report.mArgs[2], report.mArgs[3], report.mArgs[4],
+ report.mArgs[5], gSandboxCrashOnError ? " Killing process." : "");
+
+ if (gSandboxCrashOnError) {
+ // Bug 1017393: record syscall number somewhere useful.
+ info->si_addr = reinterpret_cast<void*>(report.mSyscall);
+
+ gSandboxCrashFunc(nr, info, &savedCtx, CallerPC());
+ _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
+ */
+[[nodiscard]] static bool 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_ERRNO("prctl(PR_SET_NO_NEW_PRIVS) failed");
+ MOZ_CRASH("prctl(PR_SET_NO_NEW_PRIVS)");
+ }
+
+ if (aUseTSync) {
+ // Try with SECCOMP_FILTER_FLAGS_SPEC_ALLOW, and then without if
+ // that fails with EINVAL (or if an env var is set, for testing
+ // purposes).
+ //
+ // Context: Linux 4.17 applied some Spectre mitigations (SSBD and
+ // STIBP) by default when seccomp-bpf is used, but also added that
+ // flag to opt out (and also sysadmin-level overrides). Later,
+ // Linux 5.16 turned them off by default; the rationale seems to
+ // be, roughly: the attacks are impractical or were already
+ // mitigated in other ways, there are worse attacks that these
+ // measures don't stop, and the performance impact is severe
+ // enough that container software was already opting out.
+ //
+ // For the full rationale, see
+ // https://github.com/torvalds/linux/commit/2f46993d83ff4abb310e
+ //
+ // In our case, STIBP causes a noticeable performance hit: WASM
+ // microbenchmarks of indirect calls regress by up to 2x or 3x
+ // depending on CPU. Given that upstream Linux has changed the
+ // default years ago, we opt out.
+
+ static const bool kSpecAllow = !PR_GetEnv("MOZ_SANDBOX_NO_SPEC_ALLOW");
+
+ const auto setSeccomp = [aProg](int aFlags) -> long {
+ return syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
+ SECCOMP_FILTER_FLAG_TSYNC | aFlags, aProg);
+ };
+
+ long rv;
+ if (kSpecAllow) {
+ rv = setSeccomp(SECCOMP_FILTER_FLAG_SPEC_ALLOW);
+ } else {
+ rv = -1;
+ errno = EINVAL;
+ }
+ if (rv != 0 && errno == EINVAL) {
+ rv = setSeccomp(0);
+ }
+ if (rv != 0) {
+ SANDBOX_LOG_ERRNO("thread-synchronized seccomp failed");
+ MOZ_CRASH("seccomp+tsync failed, but kernel supports tsync");
+ }
+ } else {
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)aProg, 0,
+ 0)) {
+ SANDBOX_LOG_ERRNO("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER) failed");
+ MOZ_CRASH("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)");
+ }
+ }
+ 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() {
+ const char* env = PR_GetEnv(kSandboxChrootEnvFlag);
+ if (!env || !*env || *env == '0') {
+ return;
+ }
+ char msg = kSandboxChrootRequest;
+ ssize_t msg_len = HANDLE_EINTR(write(kSandboxChrootClientFd, &msg, 1));
+ MOZ_RELEASE_ASSERT(msg_len == 1);
+ msg_len = HANDLE_EINTR(read(kSandboxChrootClientFd, &msg, 1));
+ MOZ_RELEASE_ASSERT(msg_len == 1);
+ MOZ_RELEASE_ASSERT(msg == kSandboxChrootResponse);
+ close(kSandboxChrootClientFd);
+}
+
+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_ERRNO("opendir /proc/self/task");
+ MOZ_CRASH("failed while trying to open directory /proc/self/task");
+ }
+
+ // 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;
+ const int tsyncSignum = gSeccompTsyncBroadcastSignum;
+ 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(tsyncSignum != 0);
+
+ // Reset the futex cell and signal.
+ gSetSandboxDone = 0;
+ if (syscall(__NR_tgkill, pid, tid, tsyncSignum) != 0) {
+ if (errno == ESRCH) {
+ SANDBOX_LOG("Thread %d unexpectedly exited.", tid);
+ // Rescan threads, in case it forked before exiting.
+ sandboxProgress = true;
+ continue;
+ }
+ SANDBOX_LOG_ERRNO("tgkill(%d,%d)", pid, tid);
+ MOZ_CRASH("failed while trying to send a signal to a thread");
+ }
+ // 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_ERRNO("FUTEX_WAIT");
+ MOZ_CRASH("failed during FUTEX_WAIT");
+ }
+ }
+ // 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("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(
+ "Thread %d unresponsive for %d seconds."
+ " Killing process.",
+ tid, crashDelay);
+
+ MOZ_CRASH("failed while waiting for unresponsive thread");
+ }
+ }
+ }
+ rewinddir(taskdp);
+ } while (sandboxProgress);
+
+ void (*oldHandler)(int);
+ oldHandler = signal(tsyncSignum, SIG_DFL);
+ if (oldHandler != SetThreadSandboxHandler) {
+ // See the comment on FindFreeSignalNumber about race conditions.
+ SANDBOX_LOG("handler for signal %d was changed to %p!", tsyncSignum,
+ oldHandler);
+ MOZ_CRASH("handler for the signal was changed to another");
+ }
+ gSeccompTsyncBroadcastSignum = 0;
+ Unused << closedir(taskdp);
+ // And now, deprivilege the main thread:
+ SetThreadSandbox();
+ gSetSandboxFilter = nullptr;
+}
+
+static void ApplySandboxWithTSync(sock_fprog* aFilter) {
+ // At this point we're committed to using tsync, because we'd have
+ // needed to allocate a signal and prevent it from being blocked on
+ // other threads (see SandboxHooks.cpp), so there's no attempt to
+ // fall back to the non-tsync path.
+ if (!InstallSyscallFilter(aFilter, true)) {
+ MOZ_CRASH("failed while trying to install syscall filter");
+ }
+}
+
+#ifdef NIGHTLY_BUILD
+static bool IsLibPresent(const char* aName) {
+ if (const auto handle = dlopen(aName, RTLD_LAZY | RTLD_NOLOAD)) {
+ dlclose(handle);
+ return true;
+ }
+ return false;
+}
+
+static const Array<const char*, 1> kLibsThatWillCrash{
+ "libesets_pac.so",
+};
+#endif // NIGHTLY_BUILD
+
+void SandboxEarlyInit() {
+ if (PR_GetEnv("MOZ_SANDBOXED") == nullptr) {
+ return;
+ }
+
+ // Fix LD_PRELOAD for any child processes. See bug 1434392 comment #10;
+ // this can probably go away when audio remoting is mandatory.
+ const char* oldPreload = PR_GetEnv("MOZ_ORIG_LD_PRELOAD");
+ char* preloadEntry;
+ // This string is "leaked" because the environment takes ownership.
+ if (asprintf(&preloadEntry, "LD_PRELOAD=%s", oldPreload ? oldPreload : "") !=
+ -1) {
+ PR_SetEnv(preloadEntry);
+ }
+
+ // If TSYNC is not supported, set up signal handler
+ // used to enable seccomp on each thread.
+ if (!SandboxInfo::Get().Test(SandboxInfo::kHasSeccompTSync)) {
+ // The signal number has to be chosen early, so that the
+ // interceptions in SandboxHooks.cpp can prevent it from being
+ // masked.
+ const int tsyncSignum = FindFreeSignalNumber();
+ if (tsyncSignum == 0) {
+ SANDBOX_LOG("No available signal numbers!");
+ MOZ_CRASH("failed while trying to find a free signal number");
+ }
+ gSeccompTsyncBroadcastSignum = tsyncSignum;
+
+ // ...and the signal handler also needs to be installed now, to
+ // indicate to anything else looking for free signals that it's
+ // claimed.
+ void (*oldHandler)(int);
+ oldHandler = signal(tsyncSignum, SetThreadSandboxHandler);
+ if (oldHandler != SIG_DFL) {
+ // See the comment on FindFreeSignalNumber about race conditions.
+ if (oldHandler == SIG_ERR) {
+ MOZ_CRASH("failed while registering the signal handler");
+ } else {
+ MOZ_CRASH("failed because the signal is in use by another handler");
+ }
+ SANDBOX_LOG("signal %d in use by handler %p!\n", tsyncSignum, oldHandler);
+ }
+ }
+}
+
+static void RunGlibcLazyInitializers() {
+ // Make glibc's lazy initialization of shm_open() run before sandboxing
+ int fd = shm_open("/dummy", O_RDONLY, 0);
+ if (fd > 0) {
+ close(fd); // In the unlikely case we actually opened something
+ }
+}
+
+static void SandboxLateInit() {
+#ifdef NIGHTLY_BUILD
+ gSandboxCrashOnError = true;
+ for (const char* name : kLibsThatWillCrash) {
+ if (IsLibPresent(name)) {
+ gSandboxCrashOnError = false;
+ break;
+ }
+ }
+#endif
+
+ if (const char* envVar = PR_GetEnv("MOZ_SANDBOX_CRASH_ON_ERROR")) {
+ if (envVar[0]) {
+ gSandboxCrashOnError = envVar[0] != '0';
+ }
+ }
+
+ RunGlibcLazyInitializers();
+}
+
+// Common code for sandbox startup.
+static void SetCurrentProcessSandbox(
+ UniquePtr<sandbox::bpf_dsl::Policy> aPolicy) {
+ MOZ_ASSERT(gSandboxCrashFunc);
+ MOZ_RELEASE_ASSERT(gSandboxReporterClient != nullptr);
+ SandboxLateInit();
+
+ // Auto-collect child processes -- mainly the chroot helper if
+ // present, but also anything setns()ed into the pid namespace (not
+ // yet implemented). This process won't be able to waitpid them
+ // after the seccomp-bpf policy is applied.
+ signal(SIGCHLD, SIG_IGN);
+
+ // 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());
+
+ // In case of errors detected by the compiler (like ABI violations),
+ // log and crash normally; the default is SECCOMP_RET_KILL_THREAD,
+ // which results in hard-to-debug hangs.
+ compiler.SetPanicFunc([](const char* error) -> sandbox::bpf_dsl::ResultExpr {
+ // Note: this assumes that `error` is a string literal, which is
+ // currently the case for all callers. (An intentionally leaked
+ // heap allocation would also work.)
+ return sandbox::bpf_dsl::Trap(
+ [](const sandbox::arch_seccomp_data&, void* aux) -> intptr_t {
+ auto error = reinterpret_cast<const char*>(aux);
+ SANDBOX_LOG("Panic: %s", error);
+ MOZ_CRASH("Sandbox Panic");
+ // unreachable
+ },
+ (void*)error);
+ });
+
+ 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("using seccomp tsync");
+ }
+ ApplySandboxWithTSync(&fprog);
+ } else {
+ if (info.Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("no tsync support; using signal broadcast");
+ }
+ BroadcastSetThreadSandbox(&fprog);
+ }
+
+ // Now that all threads' filesystem accesses are being intercepted
+ // (if a broker is used) it's safe to chroot the process:
+ EnterChroot();
+}
+
+/**
+ * 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(ContentProcessSandboxParams&& aParams) {
+ int brokerFd = aParams.mBrokerFd;
+ aParams.mBrokerFd = -1;
+
+ if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForContent)) {
+ if (brokerFd >= 0) {
+ close(brokerFd);
+ }
+ return false;
+ }
+
+ auto procType = aParams.mFileProcess ? SandboxReport::ProcType::FILE
+ : SandboxReport::ProcType::CONTENT;
+ gSandboxReporterClient = new SandboxReporterClient(procType);
+
+ // This needs to live until the process exits.
+ static SandboxBrokerClient* sBroker;
+ if (brokerFd >= 0) {
+ sBroker = new SandboxBrokerClient(brokerFd);
+ }
+
+ SetCurrentProcessSandbox(
+ GetContentSandboxPolicy(sBroker, std::move(aParams)));
+ return true;
+}
+/**
+ * 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) {
+ MOZ_RELEASE_ASSERT(aFilePath != nullptr);
+ if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForMedia)) {
+ return;
+ }
+
+ gSandboxReporterClient =
+ new SandboxReporterClient(SandboxReport::ProcType::MEDIA_PLUGIN);
+
+ SandboxOpenedFile plugin(aFilePath);
+ if (!plugin.IsOpen()) {
+ SANDBOX_LOG_ERRNO("failed to open plugin file %s", aFilePath);
+ MOZ_CRASH("failed while trying to open the plugin file ");
+ }
+
+ auto files = new SandboxOpenedFiles();
+ files->Add(std::move(plugin));
+ files->Add("/dev/urandom", SandboxOpenedFile::Dup::YES);
+ files->Add("/dev/random", SandboxOpenedFile::Dup::YES);
+ files->Add("/etc/ld.so.cache"); // Needed for NSS in clearkey.
+ files->Add("/sys/devices/system/cpu/cpu0/tsc_freq_khz");
+ files->Add("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq");
+ files->Add("/proc/cpuinfo"); // Info also available via CPUID instruction.
+ files->Add("/proc/sys/crypto/fips_enabled"); // Needed for NSS in clearkey.
+#ifdef __i386__
+ files->Add("/proc/self/auxv"); // Info also in process's address space.
+#endif
+ // Bug 1712506: the Widevine CDM will try to access these but
+ // doesn't appear to need them.
+ files->Add("/sys/devices/system/cpu/online", SandboxOpenedFile::Error{});
+ files->Add("/proc/stat", SandboxOpenedFile::Error{});
+ files->Add("/proc/net/unix", SandboxOpenedFile::Error{});
+ files->Add("/proc/self/maps", SandboxOpenedFile::Error{});
+
+ // Finally, start the sandbox.
+ SetCurrentProcessSandbox(GetMediaSandboxPolicy(files));
+}
+
+void SetRemoteDataDecoderSandbox(int aBroker) {
+ if (!SandboxInfo::Get().Test(SandboxInfo::kHasSeccompBPF) ||
+ PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX")) {
+ if (aBroker >= 0) {
+ close(aBroker);
+ }
+ return;
+ }
+
+ gSandboxReporterClient =
+ new SandboxReporterClient(SandboxReport::ProcType::RDD);
+
+ // FIXME(bug 1513773): merge this with the one for content?
+ static SandboxBrokerClient* sBroker;
+ if (aBroker >= 0) {
+ sBroker = new SandboxBrokerClient(aBroker);
+ }
+
+ SetCurrentProcessSandbox(GetDecoderSandboxPolicy(sBroker));
+}
+
+void SetSocketProcessSandbox(int aBroker) {
+ if (!SandboxInfo::Get().Test(SandboxInfo::kHasSeccompBPF) ||
+ PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS_SANDBOX")) {
+ if (aBroker >= 0) {
+ close(aBroker);
+ }
+ return;
+ }
+
+ gSandboxReporterClient =
+ new SandboxReporterClient(SandboxReport::ProcType::SOCKET_PROCESS);
+
+ static SandboxBrokerClient* sBroker;
+ if (aBroker >= 0) {
+ sBroker = new SandboxBrokerClient(aBroker);
+ }
+
+ SetCurrentProcessSandbox(GetSocketProcessSandboxPolicy(sBroker));
+}
+
+void SetUtilitySandbox(int aBroker, ipc::SandboxingKind aKind) {
+ if (!SandboxInfo::Get().Test(SandboxInfo::kHasSeccompBPF) ||
+ !IsUtilitySandboxEnabled(aKind)) {
+ if (aBroker >= 0) {
+ close(aBroker);
+ }
+ return;
+ }
+
+ gSandboxReporterClient =
+ new SandboxReporterClient(SandboxReport::ProcType::UTILITY);
+
+ static SandboxBrokerClient* sBroker;
+ if (aBroker >= 0) {
+ sBroker = new SandboxBrokerClient(aBroker);
+ }
+
+ UniquePtr<sandbox::bpf_dsl::Policy> policy;
+ switch (aKind) {
+ case ipc::SandboxingKind::GENERIC_UTILITY:
+ policy = GetUtilitySandboxPolicy(sBroker);
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Invalid SandboxingKind");
+ break;
+ }
+
+ SetCurrentProcessSandbox(std::move(policy));
+}
+
+bool SetSandboxCrashOnError(bool aValue) {
+ bool oldValue = gSandboxCrashOnError;
+ gSandboxCrashOnError = aValue;
+ return oldValue;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/Sandbox.h b/security/sandbox/linux/Sandbox.h
new file mode 100644
index 0000000000..575f57a3a3
--- /dev/null
+++ b/security/sandbox/linux/Sandbox.h
@@ -0,0 +1,76 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/Types.h"
+#include "nsXULAppAPI.h"
+#include <vector>
+
+#include "mozilla/ipc/UtilityProcessSandboxing.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 {
+
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+
+// This must be called early, before glib creates any worker threads.
+// (See bug 1176099.)
+MOZ_EXPORT void SandboxEarlyInit();
+
+// A collection of sandbox parameters that have to be extracted from
+// prefs or other libxul facilities and passed down, because
+// libmozsandbox can't link against the APIs to read them.
+struct ContentProcessSandboxParams {
+ // Content sandbox level; see also GetEffectiveSandboxLevel in
+ // SandboxSettings.h and the comments for the Linux version of
+ // "security.sandbox.content.level" in browser/app/profile/firefox.js
+ int mLevel = 0;
+ // The filesystem broker client file descriptor, or -1 to allow
+ // direct filesystem access. (Warning: this is not a RAII class and
+ // will not close the fd on destruction.)
+ int mBrokerFd = -1;
+ // Determines whether we allow reading all files, for processes that
+ // render file:/// URLs.
+ bool mFileProcess = false;
+ // Syscall numbers to allow even if the seccomp-bpf policy otherwise
+ // wouldn't.
+ std::vector<int> mSyscallWhitelist;
+
+ static ContentProcessSandboxParams ForThisProcess(
+ const Maybe<ipc::FileDescriptor>& aBroker);
+};
+
+// Call only if SandboxInfo::CanSandboxContent() returns true.
+// (No-op if the sandbox is disabled.)
+// isFileProcess determines whether we allow system wide file reads.
+MOZ_EXPORT bool SetContentProcessSandbox(ContentProcessSandboxParams&& aParams);
+
+// 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);
+
+MOZ_EXPORT void SetRemoteDataDecoderSandbox(int aBroker);
+
+MOZ_EXPORT void SetSocketProcessSandbox(int aBroker);
+
+MOZ_EXPORT void SetUtilitySandbox(int aBroker, ipc::SandboxingKind aKind);
+
+// We want to turn on/off crashing on error when running some tests
+// This will return current value and set the aValue we pass
+MOZ_EXPORT bool SetSandboxCrashOnError(bool aValue);
+
+} // 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 0000000000..8dd8a2f5cf
--- /dev/null
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -0,0 +1,274 @@
+/* -*- 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 <sys/un.h>
+#include <unistd.h>
+
+#include "mozilla/Assertions.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("rewriting %s -> %s", aPath, rewrittenPath);
+ }
+ path = rewrittenPath;
+ } else {
+ SANDBOX_LOG("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 = nullptr;
+ 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("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("Failed errno %d op %s flags 0%o path %s", resp.mError,
+ OperationDescription[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) {
+ if (!aPath || !aStat) {
+ return -EFAULT;
+ }
+
+ Request req = {SANDBOX_FILE_STAT, 0, sizeof(statstruct)};
+ return DoCall(&req, aPath, nullptr, (void*)aStat, false);
+}
+
+int SandboxBrokerClient::LStat(const char* aPath, statstruct* aStat) {
+ if (!aPath || !aStat) {
+ return -EFAULT;
+ }
+
+ 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);
+}
+
+int SandboxBrokerClient::Connect(const sockaddr_un* aAddr, size_t aLen,
+ int aType) {
+ static constexpr size_t maxLen = sizeof(aAddr->sun_path);
+ const char* path = aAddr->sun_path;
+ const auto addrEnd = reinterpret_cast<const char*>(aAddr) + aLen;
+ // Ensure that the length isn't impossibly small.
+ if (addrEnd <= path) {
+ return -EINVAL;
+ }
+ // Unix domain only
+ if (aAddr->sun_family != AF_UNIX) {
+ return -EAFNOSUPPORT;
+ }
+ // How much of sun_path may be accessed?
+ auto bufLen = static_cast<size_t>(addrEnd - path);
+ if (bufLen > maxLen) {
+ bufLen = maxLen;
+ }
+
+ // Try to handle abstract addresses where the address (the part
+ // after the leading null byte) resembles a pathname: a leading
+ // slash and no embedded nulls.
+ //
+ // `DoCall` expects null-terminated strings, but in this case the
+ // "path" is terminated by the sockaddr length (without a null), so
+ // we need to make a copy.
+ if (bufLen >= 2 && path[0] == '\0' && path[1] == '/' &&
+ !memchr(path + 1, '\0', bufLen - 1)) {
+ char tmpBuf[maxLen];
+ MOZ_RELEASE_ASSERT(bufLen - 1 < maxLen);
+ memcpy(tmpBuf, path + 1, bufLen - 1);
+ tmpBuf[bufLen - 1] = '\0';
+
+ const Request req = {SANDBOX_SOCKET_CONNECT_ABSTRACT, aType, 0};
+ return DoCall(&req, tmpBuf, nullptr, nullptr, true);
+ }
+
+ // Require null-termination. (Linux doesn't require it, but
+ // applications usually null-terminate for portability, and not
+ // handling unterminated strings means we don't have to copy the path.)
+ const size_t pathLen = strnlen(path, bufLen);
+ if (pathLen == bufLen) {
+ return -ENAMETOOLONG;
+ }
+
+ // Abstract addresses are handled only in some specific case, error in others
+ if (pathLen == 0) {
+ return -ENETUNREACH;
+ }
+
+ const Request req = {SANDBOX_SOCKET_CONNECT, aType, 0};
+ return DoCall(&req, path, nullptr, nullptr, true);
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/SandboxBrokerClient.h b/security/sandbox/linux/SandboxBrokerClient.h
new file mode 100644
index 0000000000..9e4c1825c3
--- /dev/null
+++ b/security/sandbox/linux/SandboxBrokerClient.h
@@ -0,0 +1,57 @@
+/* -*- 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;
+struct sockaddr_un;
+
+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);
+ int Connect(const struct sockaddr_un* aAddr, size_t aLen, int aType);
+
+ 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/SandboxChrootProto.h b/security/sandbox/linux/SandboxChrootProto.h
new file mode 100644
index 0000000000..362e15a219
--- /dev/null
+++ b/security/sandbox/linux/SandboxChrootProto.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_SandboxChrootProto_h
+#define mozilla_SandboxChrootProto_h
+
+#include "mozilla/Types.h"
+
+namespace mozilla {
+
+static const int kSandboxChrootClientFd = 6;
+static const char kSandboxChrootRequest = 'C';
+static const char kSandboxChrootResponse = 'O';
+static const char kSandboxChrootEnvFlag[] = "MOZ_SANDBOX_USE_CHROOT";
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxChrootProto_h
diff --git a/security/sandbox/linux/SandboxFilter.cpp b/security/sandbox/linux/SandboxFilter.cpp
new file mode 100644
index 0000000000..2eec3b27a7
--- /dev/null
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -0,0 +1,2152 @@
+/* -*- 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 <errno.h>
+#include <fcntl.h>
+#include <linux/ioctl.h>
+#include <linux/ipc.h>
+#include <linux/net.h>
+#include <linux/sched.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/un.h>
+#include <sys/utsname.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "Sandbox.h" // for ContentProcessSandboxParams
+#include "SandboxBrokerClient.h"
+#include "SandboxFilterUtil.h"
+#include "SandboxInfo.h"
+#include "SandboxInternal.h"
+#include "SandboxLogging.h"
+#include "SandboxOpenedFiles.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/ProcInfo_linux.h"
+#include "mozilla/TemplateLib.h"
+#include "mozilla/UniquePtr.h"
+#include "prenv.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_HUGEPAGE
+# define MADV_HUGEPAGE 14
+#endif
+#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
+
+// Linux 5.17+
+#ifndef PR_SET_VMA
+# define PR_SET_VMA 0x53564d41
+#endif
+#ifndef PR_SET_VMA_ANON_NAME
+# define PR_SET_VMA_ANON_NAME 0
+#endif
+
+// The headers define O_LARGEFILE as 0 on x86_64, but we need the
+// actual value because it shows up in file flags.
+#define O_LARGEFILE_REAL 00100000
+
+// Not part of UAPI, but userspace sees it in F_GETFL; see bug 1650751.
+#define FMODE_NONOTIFY 0x4000000
+
+#ifndef F_LINUX_SPECIFIC_BASE
+# define F_LINUX_SPECIFIC_BASE 1024
+#else
+static_assert(F_LINUX_SPECIFIC_BASE == 1024);
+#endif
+
+#ifndef F_ADD_SEALS
+# define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
+# define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
+#else
+static_assert(F_ADD_SEALS == (F_LINUX_SPECIFIC_BASE + 9));
+static_assert(F_GET_SEALS == (F_LINUX_SPECIFIC_BASE + 10));
+#endif
+
+// To avoid visual confusion between "ifdef ANDROID" and "ifndef ANDROID":
+#ifndef ANDROID
+# define DESKTOP
+#endif
+
+namespace {
+static const unsigned long kIoctlTypeMask = _IOC_TYPEMASK << _IOC_TYPESHIFT;
+static const unsigned long kTtyIoctls = TIOCSTI & kIoctlTypeMask;
+// On some older architectures (but not x86 or ARM), ioctls are
+// assigned type fields differently, and the TIOC/TC/FIO group
+// isn't all the same type. If/when we support those archs,
+// this would need to be revised (but really this should be a
+// default-deny policy; see below).
+static_assert(kTtyIoctls == (TCSETA & kIoctlTypeMask) &&
+ kTtyIoctls == (FIOASYNC & kIoctlTypeMask),
+ "tty-related ioctls use the same type");
+}; // namespace
+
+// 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 allows everything used by the sandbox itself, by the
+// core IPC code, by the crash reporter, or other core code. It also
+// contains support for brokering file operations, but file access is
+// denied if no broker client is provided by the concrete class.
+class SandboxPolicyCommon : public SandboxPolicyBase {
+ protected:
+ // Subclasses can assign these in their constructors to loosen the
+ // default settings.
+ SandboxBrokerClient* mBroker = nullptr;
+ bool mMayCreateShmem = false;
+ bool mAllowUnsafeSocketPair = false;
+ bool mBrokeredConnect = false; // Can connect() be brokered?
+
+ SandboxPolicyCommon() = default;
+
+ typedef const sandbox::arch_seccomp_data& ArgsRef;
+
+ static intptr_t BlockedSyscallTrap(ArgsRef aArgs, void* aux) {
+ MOZ_ASSERT(!aux);
+ return -ENOSYS;
+ }
+
+ // Convert Unix-style "return -1 and set errno" APIs back into the
+ // Linux ABI "return -err" style.
+ static intptr_t ConvertError(long rv) { return rv < 0 ? -errno : rv; }
+
+ template <typename... Args>
+ static intptr_t DoSyscall(long nr, Args... args) {
+ static_assert(std::conjunction_v<
+ std::conditional_t<(sizeof(Args) <= sizeof(void*)),
+ std::true_type, std::false_type>...>,
+ "each syscall arg is at most one word");
+ return ConvertError(syscall(nr, args...));
+ }
+
+ // Mesa's amdgpu driver uses kcmp with KCMP_FILE; see also bug
+ // 1624743. This policy restricts it to the process's own pid,
+ // which should be sufficient on its own if we need to remove the
+ // `type` restriction in the future.
+ //
+ // (Note: if we end up with more Mesa-specific hooks needed in
+ // several process types, we could put them into this class's
+ // EvaluateSyscall guarded by a boolean member variable, or
+ // introduce another layer of subclassing.)
+ ResultExpr KcmpPolicyForMesa() const {
+ // The real KCMP_FILE is part of an anonymous enum in
+ // <linux/kcmp.h>, but we can't depend on having that header,
+ // and it's not a #define so the usual #ifndef approach
+ // doesn't work.
+ static const int kKcmpFile = 0;
+ const pid_t myPid = getpid();
+ Arg<pid_t> pid1(0), pid2(1);
+ Arg<int> type(2);
+ return If(AllOf(pid1 == myPid, pid2 == myPid, type == kKcmpFile), Allow())
+ .Else(InvalidSyscall());
+ }
+
+ static intptr_t SchedTrap(ArgsRef aArgs, void* aux) {
+ const pid_t tid = syscall(__NR_gettid);
+ if (aArgs.args[0] == static_cast<uint64_t>(tid)) {
+ return DoSyscall(aArgs.nr, 0, static_cast<uintptr_t>(aArgs.args[1]),
+ static_cast<uintptr_t>(aArgs.args[2]),
+ static_cast<uintptr_t>(aArgs.args[3]),
+ static_cast<uintptr_t>(aArgs.args[4]),
+ static_cast<uintptr_t>(aArgs.args[5]));
+ }
+ return -EPERM;
+ }
+
+ private:
+ // Bug 1093893: Translate tkill to tgkill for pthread_kill; fixed in
+ // bionic commit 10c8ce59a (in JB and up; API level 16 = Android 4.1).
+ // Bug 1376653: musl also needs this, and security-wise it's harmless.
+ static intptr_t TKillCompatTrap(ArgsRef aArgs, void* aux) {
+ auto tid = static_cast<pid_t>(aArgs.args[0]);
+ auto sig = static_cast<int>(aArgs.args[1]);
+ return DoSyscall(__NR_tgkill, getpid(), tid, sig);
+ }
+
+ 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;
+ }
+
+ // 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);
+ }
+
+ 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);
+ }
+
+ 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 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]);
+ if (path && path[0] == '\0') {
+ // If the path is empty, then just fail the call here
+ return -ENOENT;
+ }
+ 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);
+ }
+#endif // __NR_open
+
+ 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("unsupported fd-relative openat(%d, \"%s\", 0%o)", fd, path,
+ flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Open(path, flags);
+ }
+
+ 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.
+ //
+ // Starting with kernel 5.8+ and glibc 2.33, there is faccessat2 that
+ // supports flags, handled below.
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG("unsupported fd-relative faccessat(%d, \"%s\", %d)", fd, path,
+ mode);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Access(path, mode);
+ }
+
+ static intptr_t AccessAt2Trap(ArgsRef aArgs, void* aux) {
+ auto* broker = static_cast<SandboxBrokerClient*>(aux);
+ auto fd = static_cast<int>(aArgs.args[0]);
+ const auto* path = reinterpret_cast<const char*>(aArgs.args[1]);
+ auto mode = static_cast<int>(aArgs.args[2]);
+ auto flags = static_cast<int>(aArgs.args[3]);
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG("unsupported fd-relative faccessat2(%d, \"%s\", %d, %d)", fd,
+ path, mode, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ if ((flags & ~AT_EACCESS) == 0) {
+ return broker->Access(path, mode);
+ }
+ return ConvertError(ENOSYS);
+ }
+
+ 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 && (flags & AT_EMPTY_PATH) && path &&
+ !strcmp(path, "")) {
+#ifdef __NR_fstat64
+ return DoSyscall(__NR_fstat64, fd, buf);
+#else
+ return DoSyscall(__NR_fstat, fd, buf);
+#endif
+ }
+
+ if (!broker) {
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+
+ if (fd != AT_FDCWD && path && path[0] != '/') {
+ SANDBOX_LOG("unsupported fd-relative fstatat(%d, \"%s\", %p, 0x%x)", fd,
+ path, buf, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+
+ int badFlags = flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT);
+ if (badFlags != 0) {
+ SANDBOX_LOG("unsupported flags 0x%x in fstatat(%d, \"%s\", %p, 0x%x)",
+ badFlags, 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 ChmodAtTrap(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<mode_t>(aArgs.args[2]);
+ auto flags = static_cast<int>(aArgs.args[3]);
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG("unsupported fd-relative chmodat(%d, \"%s\", 0%o, %d)", fd,
+ path, mode, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ if (flags != 0) {
+ SANDBOX_LOG("unsupported flags in chmodat(%d, \"%s\", 0%o, %d)", fd, path,
+ mode, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Chmod(path, mode);
+ }
+
+ static intptr_t LinkAtTrap(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 fd2 = static_cast<int>(aArgs.args[2]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[3]);
+ auto flags = static_cast<int>(aArgs.args[4]);
+ if ((fd != AT_FDCWD && path[0] != '/') ||
+ (fd2 != AT_FDCWD && path2[0] != '/')) {
+ SANDBOX_LOG(
+ "unsupported fd-relative linkat(%d, \"%s\", %d, \"%s\", 0x%x)", fd,
+ path, fd2, path2, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ if (flags != 0) {
+ SANDBOX_LOG("unsupported flags in linkat(%d, \"%s\", %d, \"%s\", 0x%x)",
+ fd, path, fd2, path2, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Link(path, path2);
+ }
+
+ static intptr_t SymlinkAtTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto fd2 = static_cast<int>(aArgs.args[1]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[2]);
+ if (fd2 != AT_FDCWD && path2[0] != '/') {
+ SANDBOX_LOG("unsupported fd-relative symlinkat(\"%s\", %d, \"%s\")", path,
+ fd2, path2);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Symlink(path, path2);
+ }
+
+ static intptr_t RenameAtTrap(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 fd2 = static_cast<int>(aArgs.args[2]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[3]);
+ if ((fd != AT_FDCWD && path[0] != '/') ||
+ (fd2 != AT_FDCWD && path2[0] != '/')) {
+ SANDBOX_LOG("unsupported fd-relative renameat(%d, \"%s\", %d, \"%s\")",
+ fd, path, fd2, path2);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Rename(path, path2);
+ }
+
+ static intptr_t MkdirAtTrap(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<mode_t>(aArgs.args[2]);
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG("unsupported fd-relative mkdirat(%d, \"%s\", 0%o)", fd, path,
+ mode);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Mkdir(path, mode);
+ }
+
+ static intptr_t UnlinkAtTrap(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 (path && path[0] == '\0') {
+ // If the path is empty, then just fail the call here
+ return -ENOENT;
+ }
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG("unsupported fd-relative unlinkat(%d, \"%s\", 0x%x)", fd,
+ path, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ int badFlags = flags & ~AT_REMOVEDIR;
+ if (badFlags != 0) {
+ SANDBOX_LOG("unsupported flags 0x%x in unlinkat(%d, \"%s\", 0x%x)",
+ badFlags, fd, path, flags);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return (flags & AT_REMOVEDIR) == 0 ? broker->Unlink(path)
+ : broker->Rmdir(path);
+ }
+
+ static intptr_t ReadlinkAtTrap(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<char*>(aArgs.args[2]);
+ auto size = static_cast<size_t>(aArgs.args[3]);
+ if (fd != AT_FDCWD && path[0] != '/') {
+ SANDBOX_LOG("unsupported fd-relative readlinkat(%d, %s, %p, %d)", fd,
+ path, buf, size);
+ return BlockedSyscallTrap(aArgs, nullptr);
+ }
+ return broker->Readlink(path, buf, size);
+ }
+
+ static intptr_t SocketpairDatagramTrap(ArgsRef aArgs, void* aux) {
+ auto fds = reinterpret_cast<int*>(aArgs.args[3]);
+ // Return sequential packet sockets instead of the expected
+ // datagram sockets; see bug 1355274 for details.
+ return ConvertError(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
+ }
+
+ static intptr_t SocketpairUnpackTrap(ArgsRef aArgs, void* aux) {
+#ifdef __NR_socketpair
+ auto argsPtr = reinterpret_cast<unsigned long*>(aArgs.args[1]);
+ return DoSyscall(__NR_socketpair, argsPtr[0], argsPtr[1], argsPtr[2],
+ argsPtr[3]);
+#else
+ MOZ_CRASH("unreachable?");
+ return -ENOSYS;
+#endif
+ }
+
+ static intptr_t GetSockOptUnpackTrap(ArgsRef aArgs, void* aux) {
+#ifdef __NR_getsockopt
+ auto argsPtr = reinterpret_cast<unsigned long*>(aArgs.args[1]);
+ return DoSyscall(__NR_getsockopt, argsPtr[0], argsPtr[1], argsPtr[2],
+ argsPtr[3], argsPtr[4]);
+#else
+ MOZ_CRASH("unreachable?");
+ return -ENOSYS;
+#endif
+ }
+
+ // This just needs to return something to stand in for the
+ // unconnected socket until ConnectTrap, below, and keep track of
+ // the socket type somehow. Half a socketpair *is* a socket, so it
+ // should result in minimal confusion in the caller.
+ static intptr_t FakeSocketTrapCommon(int domain, int type, int protocol) {
+ int fds[2];
+ // X11 client libs will still try to getaddrinfo() even for a
+ // local connection. Also, WebRTC still has vestigial network
+ // code trying to do things in the content process. Politely tell
+ // them no.
+ if (domain != AF_UNIX) {
+ return -EAFNOSUPPORT;
+ }
+ if (socketpair(domain, type, protocol, fds) != 0) {
+ return -errno;
+ }
+ close(fds[1]);
+ return fds[0];
+ }
+
+ static intptr_t FakeSocketTrap(ArgsRef aArgs, void* aux) {
+ return FakeSocketTrapCommon(static_cast<int>(aArgs.args[0]),
+ static_cast<int>(aArgs.args[1]),
+ static_cast<int>(aArgs.args[2]));
+ }
+
+ static intptr_t FakeSocketTrapLegacy(ArgsRef aArgs, void* aux) {
+ const auto innerArgs = reinterpret_cast<unsigned long*>(aArgs.args[1]);
+
+ return FakeSocketTrapCommon(static_cast<int>(innerArgs[0]),
+ static_cast<int>(innerArgs[1]),
+ static_cast<int>(innerArgs[2]));
+ }
+
+ static Maybe<int> DoGetSockOpt(int fd, int optname) {
+ int optval;
+ socklen_t optlen = sizeof(optval);
+
+ if (getsockopt(fd, SOL_SOCKET, optname, &optval, &optlen) != 0) {
+ return Nothing();
+ }
+ MOZ_RELEASE_ASSERT(static_cast<size_t>(optlen) == sizeof(optval));
+ return Some(optval);
+ }
+
+ // Substitute the newly connected socket from the broker for the
+ // original socket. This is meant to be used on a fd from
+ // FakeSocketTrap, above, but it should also work to simulate
+ // re-connect()ing a real connected socket.
+ //
+ // Warning: This isn't quite right if the socket is dup()ed, because
+ // other duplicates will still be the original socket, but hopefully
+ // nothing we're dealing with does that.
+ static intptr_t ConnectTrapCommon(SandboxBrokerClient* aBroker, int aFd,
+ const struct sockaddr_un* aAddr,
+ socklen_t aLen) {
+ if (aFd < 0) {
+ return -EBADF;
+ }
+ const auto maybeDomain = DoGetSockOpt(aFd, SO_DOMAIN);
+ if (!maybeDomain) {
+ return -errno;
+ }
+ if (*maybeDomain != AF_UNIX) {
+ return -EAFNOSUPPORT;
+ }
+ const auto maybeType = DoGetSockOpt(aFd, SO_TYPE);
+ if (!maybeType) {
+ return -errno;
+ }
+ const int oldFlags = fcntl(aFd, F_GETFL);
+ if (oldFlags == -1) {
+ return -errno;
+ }
+ const int newFd = aBroker->Connect(aAddr, aLen, *maybeType);
+ if (newFd < 0) {
+ return newFd;
+ }
+ // Copy over the nonblocking flag. The connect() won't be
+ // nonblocking in that case, but that shouldn't matter for
+ // AF_UNIX. The other fcntl-settable flags are either irrelevant
+ // for sockets (e.g., O_APPEND) or would be blocked by this
+ // seccomp-bpf policy, so they're ignored.
+ if (fcntl(newFd, F_SETFL, oldFlags & O_NONBLOCK) != 0) {
+ close(newFd);
+ return -errno;
+ }
+ if (dup2(newFd, aFd) < 0) {
+ close(newFd);
+ return -errno;
+ }
+ close(newFd);
+ return 0;
+ }
+
+ static intptr_t ConnectTrap(ArgsRef aArgs, void* aux) {
+ typedef const struct sockaddr_un* AddrPtr;
+
+ return ConnectTrapCommon(static_cast<SandboxBrokerClient*>(aux),
+ static_cast<int>(aArgs.args[0]),
+ reinterpret_cast<AddrPtr>(aArgs.args[1]),
+ static_cast<socklen_t>(aArgs.args[2]));
+ }
+
+ static intptr_t ConnectTrapLegacy(ArgsRef aArgs, void* aux) {
+ const auto innerArgs = reinterpret_cast<unsigned long*>(aArgs.args[1]);
+ typedef const struct sockaddr_un* AddrPtr;
+
+ return ConnectTrapCommon(static_cast<SandboxBrokerClient*>(aux),
+ static_cast<int>(innerArgs[0]),
+ reinterpret_cast<AddrPtr>(innerArgs[1]),
+ static_cast<socklen_t>(innerArgs[2]));
+ }
+
+ static intptr_t StatFsTrap(ArgsRef aArgs, void* aux) {
+ // Warning: the kernel interface is not the C interface. The
+ // structs are different (<asm/statfs.h> vs. <sys/statfs.h>), and
+ // the statfs64 version takes an additional size parameter.
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ int fd = open(path, O_RDONLY | O_LARGEFILE);
+ if (fd < 0) {
+ return -errno;
+ }
+
+ intptr_t rv;
+ switch (aArgs.nr) {
+ case __NR_statfs: {
+ auto buf = reinterpret_cast<void*>(aArgs.args[1]);
+ rv = DoSyscall(__NR_fstatfs, fd, buf);
+ break;
+ }
+#ifdef __NR_statfs64
+ case __NR_statfs64: {
+ auto sz = static_cast<size_t>(aArgs.args[1]);
+ auto buf = reinterpret_cast<void*>(aArgs.args[2]);
+ rv = DoSyscall(__NR_fstatfs64, fd, sz, buf);
+ break;
+ }
+#endif
+ default:
+ MOZ_ASSERT(false);
+ rv = -ENOSYS;
+ }
+
+ close(fd);
+ return rv;
+ }
+
+ public:
+ 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 exact flags used can vary. CLONE_DETACHED is used by musl
+ // and by old versions of Android (<= JB 4.2), but it's been
+ // ignored by the kernel since the beginning of the Git history.
+ //
+ // If we ever need to support Android <= KK 4.4 again, SETTLS
+ // and the *TID flags will need to be made optional.
+ static const int flags_required =
+ CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD |
+ CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID |
+ CLONE_CHILD_CLEARTID;
+ static const int flags_optional = CLONE_DETACHED;
+
+ return If((flags & ~flags_optional) == flags_required, Allow())
+ .Else(failPolicy);
+ }
+
+ virtual ResultExpr PrctlPolicy() const {
+ Arg<int> op(0);
+ Arg<int> arg2(1);
+ return Switch(op)
+ .CASES((PR_SET_VMA), // Tagging of anonymous memory mappings
+ If(arg2 == PR_SET_VMA_ANON_NAME, Allow()).Else(InvalidSyscall()))
+ .CASES((PR_GET_SECCOMP, // BroadcastSetThreadSandbox, etc.
+ PR_SET_NAME, // Thread creation
+ PR_SET_DUMPABLE, // Crash reporting
+ PR_SET_PTRACER), // Debug-mode crash handling
+ Allow())
+ .CASES((PR_CAPBSET_READ), // libcap.so.2 loaded by libpulse.so.0
+ // queries for capabilities
+ Error(EINVAL))
+ .Default(InvalidSyscall());
+ }
+
+ Maybe<ResultExpr> EvaluateSocketCall(int aCall,
+ bool aHasArgs) const override {
+ switch (aCall) {
+ case SYS_RECVMSG:
+ case SYS_SENDMSG:
+ // These next four aren't needed for IPC or other core
+ // functionality at the time of this writing, but they're
+ // subsets of recvmsg/sendmsg so there's nothing gained by not
+ // allowing them here (and simplifying subclasses).
+ case SYS_RECVFROM:
+ case SYS_SENDTO:
+ case SYS_RECV:
+ case SYS_SEND:
+ return Some(Allow());
+
+ case SYS_SOCKETPAIR: {
+ // We try to allow "safe" (always connected) socketpairs when using the
+ // file broker, or for content processes, but we may need to fall back
+ // and allow all socketpairs in some cases, see bug 1066750.
+ if (!mBroker && !mAllowUnsafeSocketPair) {
+ return Nothing();
+ }
+ // See bug 1066750.
+ if (!aHasArgs) {
+ // If this is a socketcall(2) platform, but the kernel also
+ // supports separate syscalls (>= 4.2.0), we can unpack the
+ // arguments and filter them.
+ if (HasSeparateSocketCalls()) {
+ return Some(Trap(SocketpairUnpackTrap, nullptr));
+ }
+ // Otherwise, 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(domain == AF_UNIX,
+ Switch(type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
+ .Case(SOCK_STREAM, Allow())
+ .Case(SOCK_SEQPACKET, Allow())
+ // This is used only by content (and only for
+ // direct PulseAudio, which is deprecated) but it
+ // doesn't increase attack surface:
+ .Case(SOCK_DGRAM, Trap(SocketpairDatagramTrap, nullptr))
+ .Default(InvalidSyscall()))
+ .Else(InvalidSyscall()));
+ }
+
+ case SYS_GETSOCKOPT: {
+ // Best-effort argument filtering as for socketpair(2), above.
+ if (!aHasArgs) {
+ if (HasSeparateSocketCalls()) {
+ return Some(Trap(GetSockOptUnpackTrap, nullptr));
+ }
+ return Some(Allow());
+ }
+ Arg<int> level(1), optname(2);
+ // SO_SNDBUF is used by IPC to avoid constructing
+ // unnecessarily large gather arrays for `sendmsg`.
+ //
+ // SO_DOMAIN and SO_TYPE are needed for connect() brokering,
+ // but they're harmless even when it's not enabled.
+ return Some(If(AllOf(level == SOL_SOCKET,
+ AnyOf(optname == SO_SNDBUF, optname == SO_DOMAIN,
+ optname == SO_TYPE)),
+ Allow())
+ .Else(InvalidSyscall()));
+ }
+
+ // These two cases are for connect() brokering, if enabled.
+ case SYS_SOCKET:
+ if (mBrokeredConnect) {
+ const auto trapFn = aHasArgs ? FakeSocketTrap : FakeSocketTrapLegacy;
+ MOZ_ASSERT(mBroker);
+ return Some(Trap(trapFn, mBroker));
+ }
+ return Nothing();
+
+ case SYS_CONNECT:
+ if (mBrokeredConnect) {
+ const auto trapFn = aHasArgs ? ConnectTrap : ConnectTrapLegacy;
+ MOZ_ASSERT(mBroker);
+ return Some(Trap(trapFn, mBroker));
+ }
+ return Nothing();
+
+ default:
+ return Nothing();
+ }
+ }
+
+ ResultExpr EvaluateSyscall(int sysno) const override {
+ // If a file broker client was provided, route syscalls to it;
+ // otherwise, fall through to the main policy, which will deny
+ // them.
+ if (mBroker) {
+ switch (sysno) {
+#ifdef __NR_open
+ case __NR_open:
+ return Trap(OpenTrap, mBroker);
+ case __NR_access:
+ return Trap(AccessTrap, mBroker);
+ CASES_FOR_stat:
+ return Trap(StatTrap, mBroker);
+ CASES_FOR_lstat:
+ return Trap(LStatTrap, 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);
+#endif
+ case __NR_openat:
+ return Trap(OpenAtTrap, mBroker);
+ case __NR_faccessat:
+ return Trap(AccessAtTrap, mBroker);
+ case __NR_faccessat2:
+ return Trap(AccessAt2Trap, mBroker);
+ CASES_FOR_fstatat:
+ return Trap(StatAtTrap, mBroker);
+ // Used by new libc and Rust's stdlib, if available.
+ // We don't have broker support yet so claim it does not exist.
+ case __NR_statx:
+ return Error(ENOSYS);
+ case __NR_fchmodat:
+ return Trap(ChmodAtTrap, mBroker);
+ case __NR_linkat:
+ return Trap(LinkAtTrap, mBroker);
+ case __NR_mkdirat:
+ return Trap(MkdirAtTrap, mBroker);
+ case __NR_symlinkat:
+ return Trap(SymlinkAtTrap, mBroker);
+ case __NR_renameat:
+ return Trap(RenameAtTrap, mBroker);
+ case __NR_unlinkat:
+ return Trap(UnlinkAtTrap, mBroker);
+ case __NR_readlinkat:
+ return Trap(ReadlinkAtTrap, mBroker);
+ }
+ } else {
+ // In the absence of a broker we still need to handle the
+ // fstat-equivalent subset of fstatat; see bug 1673770.
+ switch (sysno) {
+ // statx may be used for fstat (bug 1867673)
+ case __NR_statx:
+ return Error(ENOSYS);
+ CASES_FOR_fstatat:
+ return Trap(StatAtTrap, nullptr);
+ }
+ }
+
+ switch (sysno) {
+ // Timekeeping
+ //
+ // (Note: the switch needs to start with a literal case, not a
+ // macro; otherwise clang-format gets confused.)
+ case __NR_gettimeofday:
+#ifdef __NR_time
+ case __NR_time:
+#endif
+ case __NR_nanosleep:
+ return Allow();
+
+ CASES_FOR_clock_gettime:
+ CASES_FOR_clock_getres:
+ CASES_FOR_clock_nanosleep: {
+ // clockid_t can encode a pid or tid to monitor another
+ // process or thread's CPU usage (see CPUCLOCK_PID and related
+ // definitions in include/linux/posix-timers.h in the kernel
+ // source). For threads, the kernel allows only tids within
+ // the calling process, so it isn't a problem if we don't
+ // filter those; pids do need to be restricted to the current
+ // process in order to not leak information.
+ Arg<clockid_t> clk_id(0);
+ clockid_t this_process =
+ MAKE_PROCESS_CPUCLOCK(getpid(), CPUCLOCK_SCHED);
+ return If(clk_id == CLOCK_MONOTONIC, Allow())
+#ifdef CLOCK_MONOTONIC_COARSE
+ // Used by SandboxReporter, among other things.
+ .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())
+#ifdef MOZ_GECKO_PROFILER
+ // Allow clock_gettime on the same process.
+ .ElseIf(clk_id == this_process, Allow())
+ // Allow clock_gettime on a thread.
+ .ElseIf((clk_id & 7u) == (CPUCLOCK_PERTHREAD_MASK | CPUCLOCK_SCHED),
+ Allow())
+#endif
+#ifdef CLOCK_BOOTTIME
+ .ElseIf(clk_id == CLOCK_BOOTTIME, Allow())
+#endif
+ .Else(InvalidSyscall());
+ }
+
+ // Thread synchronization
+ CASES_FOR_futex:
+ // FIXME(bug 1441993): This could be more restrictive.
+ return Allow();
+
+ // Asynchronous I/O
+ CASES_FOR_epoll_create:
+ CASES_FOR_epoll_wait:
+ case __NR_epoll_ctl:
+ CASES_FOR_poll:
+ return Allow();
+
+ // Used when requesting a crash dump.
+ CASES_FOR_pipe:
+ return Allow();
+
+ // Metadata of opened files
+ CASES_FOR_fstat:
+ return Allow();
+
+ CASES_FOR_fcntl: {
+ Arg<int> cmd(1);
+ Arg<int> flags(2);
+ // Typical use of F_SETFL is to modify the flags returned by
+ // F_GETFL and write them back, including some flags that
+ // F_SETFL ignores. This is a default-deny policy in case any
+ // new SETFL-able flags are added. (In particular we want to
+ // forbid O_ASYNC; see bug 1328896, but also see bug 1408438.)
+ static const int ignored_flags =
+ O_ACCMODE | O_LARGEFILE_REAL | O_CLOEXEC | FMODE_NONOTIFY;
+ static const int allowed_flags = ignored_flags | O_APPEND | O_NONBLOCK;
+ return Switch(cmd)
+ // Close-on-exec is meaningless when execve isn't allowed, but
+ // NSPR reads the bit and asserts that it has the expected value.
+ .Case(F_GETFD, Allow())
+ .Case(
+ F_SETFD,
+ If((flags & ~FD_CLOEXEC) == 0, Allow()).Else(InvalidSyscall()))
+ // F_GETFL is also used by fdopen
+ .Case(F_GETFL, Allow())
+ .Case(F_SETFL, If((flags & ~allowed_flags) == 0, Allow())
+ .Else(InvalidSyscall()))
+ // Not much different from other forms of dup(), and commonly used.
+ .Case(F_DUPFD_CLOEXEC, Allow())
+ .Default(SandboxPolicyBase::EvaluateSyscall(sysno));
+ }
+
+ // Simple I/O
+ case __NR_pread64:
+ case __NR_write:
+ case __NR_read:
+ case __NR_readv:
+ case __NR_writev: // see SandboxLogging.cpp
+ CASES_FOR_lseek:
+ return Allow();
+
+ CASES_FOR_getdents:
+ return Allow();
+
+ CASES_FOR_ftruncate:
+ case __NR_fallocate:
+ return mMayCreateShmem ? Allow() : InvalidSyscall();
+
+ // Used by our fd/shm classes
+ case __NR_dup:
+ return Allow();
+
+ // Memory mapping
+ CASES_FOR_mmap:
+ case __NR_munmap:
+ return Allow();
+
+ // Shared memory
+ case __NR_memfd_create:
+ return Allow();
+
+ // ipc::Shmem; also, glibc when creating threads:
+ case __NR_mprotect:
+ return Allow();
+
+#if !defined(MOZ_MEMORY)
+ // No jemalloc means using a system allocator like glibc
+ // that might use brk.
+ case __NR_brk:
+ return Allow();
+
+ // Similarly, mremap (bugs: 1047620, 1286119, 1860267)
+ case __NR_mremap: {
+ Arg<int> flags(3);
+ return If((flags & ~MREMAP_MAYMOVE) == 0, Allow())
+ .Else(SandboxPolicyBase::EvaluateSyscall(sysno));
+ }
+#endif
+
+ // madvise hints used by malloc; see bug 1303813 and bug 1364533
+ case __NR_madvise: {
+ Arg<int> advice(2);
+ // The GMP specific sandbox duplicates this logic, so when adding
+ // allowed values here also add them to the GMP sandbox rules.
+ return If(advice == MADV_DONTNEED, Allow())
+ .ElseIf(advice == MADV_FREE, Allow())
+ .ElseIf(advice == MADV_HUGEPAGE, Allow())
+ .ElseIf(advice == MADV_NOHUGEPAGE, Allow())
+#ifdef MOZ_ASAN
+ .ElseIf(advice == MADV_DONTDUMP, Allow())
+#endif
+ .ElseIf(advice == MADV_MERGEABLE, Error(EPERM)) // bug 1705045
+ .Else(InvalidSyscall());
+ }
+
+ // musl libc will set this up in pthreads support.
+ case __NR_membarrier:
+ return Allow();
+
+ // Signal handling
+ case __NR_sigaltstack:
+ 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());
+ }
+
+ // Polyfill with tgkill; see above.
+ case __NR_tkill:
+ return Trap(TKillCompatTrap, nullptr);
+
+ // Yield
+ case __NR_sched_yield:
+ return Allow();
+
+ // Thread creation.
+ case __NR_clone:
+ return ClonePolicy(InvalidSyscall());
+
+ case __NR_clone3:
+ return Error(ENOSYS);
+
+ // 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: {
+ // WARNING: do not handle __NR_prctl directly in subclasses;
+ // override PrctlPolicy instead. The special handling of
+ // PR_SET_NO_NEW_PRIVS is used to detect that a thread already
+ // has the policy applied; see also bug 1257361.
+
+ 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);
+
+ // Identifies the processor and node where this thread or process is
+ // running. This is used by "Awake" profiler markers.
+ case __NR_getcpu:
+ return Allow();
+
+ // 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();
+
+ case __NR_getrandom:
+ return Allow();
+
+ // Used by almost every process: GMP needs them for Clearkey
+ // because of bug 1576006 (but may not need them for other
+ // plugin types; see bug 1737092). Given that fstat is
+ // allowed, the uid/gid are probably available anyway.
+ CASES_FOR_getuid:
+ CASES_FOR_getgid:
+ CASES_FOR_geteuid:
+ CASES_FOR_getegid:
+ return Allow();
+
+#ifdef DESKTOP
+ // Bug 1543858: glibc's qsort calls sysinfo to check the
+ // memory size; it falls back to assuming there's enough RAM.
+ case __NR_sysinfo:
+ return Error(EPERM);
+#endif
+
+ // Bug 1651701: an API for restartable atomic sequences and
+ // per-CPU data; exposing information about CPU numbers and
+ // when threads are migrated or preempted isn't great but the
+ // risk should be relatively low.
+ case __NR_rseq:
+ return Allow();
+
+ case __NR_ioctl: {
+ Arg<unsigned long> request(1);
+#ifdef MOZ_ASAN
+ Arg<int> fd(0);
+#endif // MOZ_ASAN
+ // Make isatty() return false, because none of the terminal
+ // ioctls will be allowed; libraries sometimes call this for
+ // various reasons (e.g., to decide whether to emit ANSI/VT
+ // color codes when logging to stderr). glibc uses TCGETS and
+ // musl uses TIOCGWINSZ.
+ //
+ // This is required by ffmpeg
+ return If(AnyOf(request == TCGETS, request == TIOCGWINSZ),
+ Error(ENOTTY))
+#ifdef MOZ_ASAN
+ // ASAN's error reporter wants to know if stderr is a tty.
+ .ElseIf(fd == STDERR_FILENO, Error(ENOTTY))
+#endif // MOZ_ASAN
+ .Else(SandboxPolicyBase::EvaluateSyscall(sysno));
+ }
+
+ CASES_FOR_dup2: // See ConnectTrapCommon
+ if (mBrokeredConnect) {
+ return Allow();
+ }
+ return SandboxPolicyBase::EvaluateSyscall(sysno);
+
+#ifdef MOZ_ASAN
+ // ...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 // MOZ_ASAN
+
+ // Replace statfs with open (which may be brokered) and
+ // fstatfs (which is not allowed in this policy, but may be
+ // allowed by subclasses if they wish to enable statfs).
+ CASES_FOR_statfs:
+ return Trap(StatFsTrap, nullptr);
+
+ default:
+ return SandboxPolicyBase::EvaluateSyscall(sysno);
+ }
+ }
+};
+
+// The process-type-specific syscall rules start here:
+
+// 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 {
+ private:
+ ContentProcessSandboxParams mParams;
+ bool mAllowSysV;
+ bool mUsingRenderDoc;
+
+ bool BelowLevel(int aLevel) const { return mParams.mLevel < aLevel; }
+ ResultExpr AllowBelowLevel(int aLevel, ResultExpr aOrElse) const {
+ return BelowLevel(aLevel) ? Allow() : std::move(aOrElse);
+ }
+ ResultExpr AllowBelowLevel(int aLevel) const {
+ return AllowBelowLevel(aLevel, InvalidSyscall());
+ }
+
+ 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:
+ ContentSandboxPolicy(SandboxBrokerClient* aBroker,
+ ContentProcessSandboxParams&& aParams)
+ : mParams(std::move(aParams)),
+ mAllowSysV(PR_GetEnv("MOZ_SANDBOX_ALLOW_SYSV") != nullptr),
+ mUsingRenderDoc(PR_GetEnv("RENDERDOC_CAPTUREOPTS") != nullptr) {
+ mBroker = aBroker;
+ mMayCreateShmem = true;
+ mAllowUnsafeSocketPair = true;
+ mBrokeredConnect = true;
+ }
+
+ ~ContentSandboxPolicy() override = default;
+
+ Maybe<ResultExpr> EvaluateSocketCall(int aCall,
+ bool aHasArgs) const override {
+ switch (aCall) {
+ case SYS_SENDMMSG: // libresolv via libasyncns; see bug 1355274
+ return Some(Allow());
+
+#ifdef ANDROID
+ case SYS_SOCKET:
+ return Some(Error(EACCES));
+#else // #ifdef DESKTOP
+ case SYS_SOCKET:
+ case SYS_CONNECT:
+ if (BelowLevel(4)) {
+ return Some(Allow());
+ }
+ return SandboxPolicyCommon::EvaluateSocketCall(aCall, aHasArgs);
+
+ // FIXME (bug 1761134): sockopts should be filtered
+ case SYS_GETSOCKOPT:
+ case SYS_SETSOCKOPT:
+ // These next 3 were needed for X11; they may not be needed
+ // with X11 lockdown, but there's not much attack surface here.
+ case SYS_GETSOCKNAME:
+ case SYS_GETPEERNAME:
+ case SYS_SHUTDOWN:
+ return Some(Allow());
+
+ case SYS_ACCEPT:
+ case SYS_ACCEPT4:
+ if (mUsingRenderDoc) {
+ return Some(Allow());
+ }
+ [[fallthrough]];
+#endif
+ default:
+ return SandboxPolicyCommon::EvaluateSocketCall(aCall, aHasArgs);
+ }
+ }
+
+#ifdef DESKTOP
+ Maybe<ResultExpr> EvaluateIpcCall(int aCall, int aArgShift) const override {
+ switch (aCall) {
+ // These are a problem: SysV IPC follows the Unix "same uid
+ // policy" and can't be restricted/brokered like file access.
+ // We're not using it directly, but there are some library
+ // dependencies that do; see ContentNeedsSysVIPC() in
+ // SandboxLaunch.cpp. Also, Cairo as used by GTK will sometimes
+ // try to use MIT-SHM, so shmget() is a non-fatal error. See
+ // also bug 1376910 and bug 1438401.
+ case SHMGET:
+ return Some(mAllowSysV ? Allow() : Error(EPERM));
+ case SHMCTL:
+ case SHMAT:
+ case SHMDT:
+ case SEMGET:
+ case SEMCTL:
+ case SEMOP:
+ if (mAllowSysV) {
+ return Some(Allow());
+ }
+ return SandboxPolicyCommon::EvaluateIpcCall(aCall, aArgShift);
+ default:
+ return SandboxPolicyCommon::EvaluateIpcCall(aCall, aArgShift);
+ }
+ }
+#endif
+
+#ifdef MOZ_PULSEAUDIO
+ ResultExpr PrctlPolicy() const override {
+ if (BelowLevel(4)) {
+ Arg<int> op(0);
+ return If(op == PR_GET_NAME, Allow())
+ .Else(SandboxPolicyCommon::PrctlPolicy());
+ }
+ return SandboxPolicyCommon::PrctlPolicy();
+ }
+#endif
+
+ ResultExpr EvaluateSyscall(int sysno) const override {
+ // Straight allow for anything that got overriden via prefs
+ const auto& whitelist = mParams.mSyscallWhitelist;
+ if (std::find(whitelist.begin(), whitelist.end(), sysno) !=
+ whitelist.end()) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("Allowing syscall nr %d via whitelist", sysno);
+ }
+ return Allow();
+ }
+
+ // Level 1 has been removed. If seccomp-bpf is used, then we're
+ // necessarily at level >= 2 and filesystem access is brokered.
+ MOZ_ASSERT(!BelowLevel(2));
+ MOZ_ASSERT(mBroker);
+
+ switch (sysno) {
+#ifdef DESKTOP
+ case __NR_getppid:
+ return Trap(GetPPidTrap, nullptr);
+
+ // GTK's theme parsing tries to getcwd() while sandboxed, but
+ // only during Talos runs.
+ case __NR_getcwd:
+ return Error(ENOENT);
+
+# ifdef MOZ_PULSEAUDIO
+ CASES_FOR_fchown:
+ case __NR_fchmod:
+ return AllowBelowLevel(4);
+# endif
+ CASES_FOR_fstatfs: // fontconfig, pulseaudio, GIO (see also statfs)
+ case __NR_flock: // graphics
+ return Allow();
+
+ // Bug 1354731: proprietary GL drivers try to mknod() their devices
+# ifdef __NR_mknod
+ case __NR_mknod:
+# endif
+ case __NR_mknodat: {
+ Arg<mode_t> mode(sysno == __NR_mknodat ? 2 : 1);
+ return If((mode & S_IFMT) == S_IFCHR, Error(EPERM))
+ .Else(InvalidSyscall());
+ }
+ // Bug 1438389: ...and nvidia GL will sometimes try to chown the devices
+# ifdef __NR_chown
+ case __NR_chown:
+# endif
+ case __NR_fchownat:
+ return Error(EPERM);
+#endif
+
+ CASES_FOR_select:
+ return Allow();
+
+ case __NR_writev:
+#ifdef DESKTOP
+ case __NR_pwrite64:
+ case __NR_readahead:
+#endif
+ return Allow();
+
+ case __NR_ioctl: {
+#ifdef MOZ_ALSA
+ if (BelowLevel(4)) {
+ return Allow();
+ }
+#endif
+ Arg<unsigned long> request(1);
+ auto shifted_type = request & kIoctlTypeMask;
+
+ // Rust's stdlib seems to use FIOCLEX instead of equivalent fcntls.
+ return If(request == FIOCLEX, Allow())
+ // Rust's stdlib also uses FIONBIO instead of equivalent fcntls.
+ .ElseIf(request == FIONBIO, Allow())
+ // Allow anything that isn't a tty ioctl, for now; bug 1302711
+ // will cover changing this to a default-deny policy.
+ .ElseIf(shifted_type != kTtyIoctls, Allow())
+ .Else(SandboxPolicyCommon::EvaluateSyscall(sysno));
+ }
+
+ CASES_FOR_fcntl: {
+ Arg<int> cmd(1);
+ return Switch(cmd)
+ // Nvidia GL and fontconfig (newer versions) use fcntl file locking.
+ .Case(F_SETLK, Allow())
+#ifdef F_SETLK64
+ .Case(F_SETLK64, Allow())
+#endif
+ // Pulseaudio uses F_SETLKW, as does fontconfig.
+ .Case(F_SETLKW, Allow())
+#ifdef F_SETLKW64
+ .Case(F_SETLKW64, Allow())
+#endif
+ // Wayland client libraries use file seals
+ .Case(F_ADD_SEALS, Allow())
+ .Case(F_GET_SEALS, Allow())
+ .Default(SandboxPolicyCommon::EvaluateSyscall(sysno));
+ }
+
+ case __NR_brk:
+ // FIXME(bug 1510861) are we using any hints that aren't allowed
+ // in SandboxPolicyCommon now?
+ case __NR_madvise:
+ return Allow();
+
+ // wasm uses mremap (always with zero flags)
+ case __NR_mremap: {
+ Arg<int> flags(3);
+ return If(flags == 0, Allow())
+ .Else(SandboxPolicyCommon::EvaluateSyscall(sysno));
+ }
+
+ // Bug 1462640: Mesa libEGL uses mincore to test whether values
+ // are pointers, for reasons.
+ case __NR_mincore: {
+ Arg<size_t> length(1);
+ return If(length == getpagesize(), Allow())
+ .Else(SandboxPolicyCommon::EvaluateSyscall(sysno));
+ }
+
+#ifdef __NR_set_thread_area
+ case __NR_set_thread_area:
+ return Allow();
+#endif
+
+ case __NR_getrusage:
+ case __NR_times:
+ return Allow();
+
+ case __NR_fsync:
+ case __NR_msync:
+ return Allow();
+
+ case __NR_getpriority:
+ case __NR_setpriority:
+ case __NR_sched_getattr:
+ case __NR_sched_setattr:
+ 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_sched_setaffinity:
+ return Error(EPERM);
+#endif
+
+#ifdef DESKTOP
+ case __NR_pipe2: {
+ // Restrict the flags; O_NOTIFICATION_PIPE in particular
+ // exposes enough attack surface to be a cause for concern
+ // (bug 1808320). O_DIRECT isn't known to be used currently
+ // (Try passes with it blocked), but should be low-risk, and
+ // Chromium allows it.
+ static constexpr int allowed_flags = O_CLOEXEC | O_NONBLOCK | O_DIRECT;
+ Arg<int> flags(1);
+ return If((flags & ~allowed_flags) == 0, Allow())
+ .Else(InvalidSyscall());
+ }
+
+ CASES_FOR_getrlimit:
+ CASES_FOR_getresuid:
+ CASES_FOR_getresgid:
+ return Allow();
+
+ case __NR_prlimit64: {
+ // Allow only the getrlimit() use case. (glibc seems to use
+ // only pid 0 to indicate the current process; pid == getpid()
+ // is equivalent and could also be allowed if needed.)
+ Arg<pid_t> pid(0);
+ // This is really a const struct ::rlimit*, but Arg<> doesn't
+ // work with pointers, only integer types.
+ Arg<uintptr_t> new_limit(2);
+ return If(AllOf(pid == 0, new_limit == 0), Allow())
+ .Else(InvalidSyscall());
+ }
+
+ // PulseAudio calls umask, even though it's unsafe in
+ // multithreaded applications. But, allowing it here doesn't
+ // really do anything one way or the other, now that file
+ // accesses are brokered to another process.
+ case __NR_umask:
+ return AllowBelowLevel(4);
+
+ case __NR_kill: {
+ if (BelowLevel(4)) {
+ Arg<int> sig(1);
+ // PulseAudio uses kill(pid, 0) to check if purported owners of
+ // shared memory files are still alive; see bug 1397753 for more
+ // details.
+ return If(sig == 0, Error(EPERM)).Else(InvalidSyscall());
+ }
+ return InvalidSyscall();
+ }
+
+ case __NR_wait4:
+# ifdef __NR_waitpid
+ case __NR_waitpid:
+# endif
+ // NSPR will start a thread to wait for child processes even if
+ // fork() fails; see bug 227246 and bug 1299581.
+ return Error(ECHILD);
+
+ case __NR_eventfd2:
+ return Allow();
+
+# 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));
+
+ case __NR_clone3:
+ return Error(ENOSYS);
+
+# 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();
+
+ // Required by libnuma for FFmpeg
+ case __NR_set_mempolicy:
+ return Error(ENOSYS);
+
+ case __NR_kcmp:
+ return KcmpPolicyForMesa();
+
+#endif // DESKTOP
+
+ // 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, ContentProcessSandboxParams&& aParams) {
+ return MakeUnique<ContentSandboxPolicy>(aMaybeBroker, std::move(aParams));
+}
+
+// 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) {
+ const auto files = static_cast<const SandboxOpenedFiles*>(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 ((flags & O_ACCMODE) != O_RDONLY) {
+ SANDBOX_LOG("non-read-only open of file %s attempted (flags=0%o)", path,
+ flags);
+ return -EROFS;
+ }
+ int fd = files->GetDesc(path);
+ if (fd < 0) {
+ // SandboxOpenedFile::GetDesc already logged about this, if appropriate.
+ return -ENOENT;
+ }
+ return fd;
+ }
+
+ static intptr_t UnameTrap(const sandbox::arch_seccomp_data& aArgs,
+ void* aux) {
+ const auto buf = reinterpret_cast<struct utsname*>(aArgs.args[0]);
+ PodZero(buf);
+ // The real uname() increases fingerprinting risk for no benefit.
+ // This is close enough.
+ strcpy(buf->sysname, "Linux");
+ strcpy(buf->version, "3");
+ return 0;
+ }
+
+ static intptr_t FcntlTrap(const sandbox::arch_seccomp_data& aArgs,
+ void* aux) {
+ const auto cmd = static_cast<int>(aArgs.args[1]);
+ switch (cmd) {
+ // This process can't exec, so the actual close-on-exec flag
+ // doesn't matter; have it always read as true and ignore writes.
+ case F_GETFD:
+ return O_CLOEXEC;
+ case F_SETFD:
+ return 0;
+ default:
+ return -ENOSYS;
+ }
+ }
+
+ const SandboxOpenedFiles* mFiles;
+
+ public:
+ explicit GMPSandboxPolicy(const SandboxOpenedFiles* aFiles) : mFiles(aFiles) {
+ // Used by the profiler to send data back to the parent process;
+ // we are not enabling the file broker, so this will only work if
+ // memfd_create is available.
+ mMayCreateShmem = true;
+ }
+
+ ~GMPSandboxPolicy() override = default;
+
+ 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, mFiles);
+
+ case __NR_brk:
+ return Allow();
+ case __NR_sched_get_priority_min:
+ case __NR_sched_get_priority_max:
+ return Allow();
+ case __NR_sched_getparam:
+ case __NR_sched_getscheduler:
+ 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();
+
+ // Bug 1372428
+ case __NR_uname:
+ return Trap(UnameTrap, nullptr);
+ CASES_FOR_fcntl:
+ return Trap(FcntlTrap, nullptr);
+
+ // Allow the same advice values as the default policy, but return
+ // Error(ENOSYS) for other values. Because the Widevine CDM may probe
+ // advice arguments, including invalid values, we don't want to return
+ // InvalidSyscall(), as this will crash the process. So instead just
+ // indicate such calls are not available.
+ case __NR_madvise: {
+ Arg<int> advice(2);
+ return If(advice == MADV_DONTNEED, Allow())
+ .ElseIf(advice == MADV_FREE, Allow())
+ .ElseIf(advice == MADV_HUGEPAGE, Allow())
+ .ElseIf(advice == MADV_NOHUGEPAGE, Allow())
+#ifdef MOZ_ASAN
+ .ElseIf(advice == MADV_DONTDUMP, Allow())
+#endif
+ .ElseIf(advice == MADV_MERGEABLE, Error(EPERM)) // bug 1705045
+ .Else(Error(ENOSYS));
+ }
+
+ // The profiler will try to readlink /proc/self/exe for native
+ // stackwalking, but that's broken for several other reasons;
+ // see discussion in bug 1770905. (That can be emulated by
+ // pre-recording the result if/when we need it.)
+#ifdef __NR_readlink
+ case __NR_readlink:
+#endif
+ case __NR_readlinkat:
+ return Error(EINVAL);
+
+ default:
+ return SandboxPolicyCommon::EvaluateSyscall(sysno);
+ }
+ }
+};
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetMediaSandboxPolicy(
+ const SandboxOpenedFiles* aFiles) {
+ return UniquePtr<sandbox::bpf_dsl::Policy>(new GMPSandboxPolicy(aFiles));
+}
+
+// The policy for the data decoder process is similar to the one for
+// media plugins, but the codec code is all in-tree so it's better
+// behaved and doesn't need special exceptions (or the ability to load
+// a plugin file). However, it does directly create shared memory
+// segments, so it may need file brokering.
+class RDDSandboxPolicy final : public SandboxPolicyCommon {
+ public:
+ explicit RDDSandboxPolicy(SandboxBrokerClient* aBroker) {
+ mBroker = aBroker;
+ mMayCreateShmem = true;
+ }
+
+#ifndef ANDROID
+ Maybe<ResultExpr> EvaluateIpcCall(int aCall, int aArgShift) const override {
+ // The Intel media driver uses SysV IPC (semaphores and shared
+ // memory) on newer hardware models; it always uses this fixed
+ // key, so we can restrict semget and shmget. Unfortunately, the
+ // calls that operate on these resources take "identifiers", which
+ // are unpredictable (by us) but guessable (by an adversary).
+ static constexpr key_t kIntelKey = 'D' << 24 | 'V' << 8 | 'X' << 0;
+
+ switch (aCall) {
+ case SEMGET:
+ case SHMGET: {
+ Arg<key_t> key(0 + aArgShift);
+ return Some(If(key == kIntelKey, Allow()).Else(InvalidSyscall()));
+ }
+
+ case SEMCTL:
+ case SEMOP:
+ case SEMTIMEDOP:
+ case SHMCTL:
+ case SHMAT:
+ case SHMDT:
+ return Some(Allow());
+
+ default:
+ return SandboxPolicyCommon::EvaluateIpcCall(aCall, aArgShift);
+ }
+ }
+#endif
+
+ Maybe<ResultExpr> EvaluateSocketCall(int aCall,
+ bool aHasArgs) const override {
+ switch (aCall) {
+ // These are for X11.
+ case SYS_GETSOCKNAME:
+ case SYS_GETPEERNAME:
+ case SYS_SHUTDOWN:
+ return Some(Allow());
+
+#ifdef MOZ_ENABLE_V4L2
+ case SYS_SOCKET:
+ // Hardware-accelerated decode uses EGL to manage hardware surfaces.
+ // When initialised it tries to connect to the Wayland server over a
+ // UNIX socket. It still works fine if it can't connect to Wayland, so
+ // don't let it create the socket (but don't kill the process for
+ // trying).
+ return Some(Error(EACCES));
+#endif
+
+ default:
+ return SandboxPolicyCommon::EvaluateSocketCall(aCall, aHasArgs);
+ }
+ }
+
+ ResultExpr EvaluateSyscall(int sysno) const override {
+ switch (sysno) {
+ case __NR_getrusage:
+ return Allow();
+
+ case __NR_ioctl: {
+ Arg<unsigned long> request(1);
+ auto shifted_type = request & kIoctlTypeMask;
+ static constexpr unsigned long kDrmType =
+ static_cast<unsigned long>('d') << _IOC_TYPESHIFT;
+ // Note: 'b' is also the Binder device on Android.
+ static constexpr unsigned long kDmaBufType =
+ static_cast<unsigned long>('b') << _IOC_TYPESHIFT;
+#ifdef MOZ_ENABLE_V4L2
+ // Type 'V' for V4L2, used for hw accelerated decode
+ static constexpr unsigned long kVideoType =
+ static_cast<unsigned long>('V') << _IOC_TYPESHIFT;
+#endif
+ // nvidia uses some ioctls from this range (but not actual
+ // fbdev ioctls; nvidia uses values >= 200 for the NR field
+ // (low 8 bits))
+ static constexpr unsigned long kFbDevType =
+ static_cast<unsigned long>('F') << _IOC_TYPESHIFT;
+
+ // Allow DRI and DMA-Buf for VA-API. Also allow V4L2 if enabled
+ return If(shifted_type == kDrmType, Allow())
+ .ElseIf(shifted_type == kDmaBufType, Allow())
+#ifdef MOZ_ENABLE_V4L2
+ .ElseIf(shifted_type == kVideoType, Allow())
+#endif
+ // Hack for nvidia, which isn't supported yet:
+ .ElseIf(shifted_type == kFbDevType, Error(ENOTTY))
+ .Else(SandboxPolicyCommon::EvaluateSyscall(sysno));
+ }
+
+ // Mesa/amdgpu
+ case __NR_kcmp:
+ return KcmpPolicyForMesa();
+
+ // We use this in our DMABuf support code.
+ case __NR_eventfd2:
+ return Allow();
+
+ // Allow the sched_* syscalls for the current thread only.
+ // Mesa attempts to use them to optimize performance; often
+ // this involves passing other threads' tids, which we can't
+ // safely allow, but maybe a future Mesa version could fix that.
+ case __NR_sched_getaffinity:
+ case __NR_sched_setaffinity:
+ case __NR_sched_getparam:
+ case __NR_sched_setparam:
+ case __NR_sched_getscheduler:
+ case __NR_sched_setscheduler:
+ case __NR_sched_getattr:
+ case __NR_sched_setattr: {
+ Arg<pid_t> pid(0);
+ return If(pid == 0, Allow()).Else(Trap(SchedTrap, nullptr));
+ }
+
+ // The priority bounds are also used, sometimes (bug 1838675):
+ case __NR_sched_get_priority_min:
+ case __NR_sched_get_priority_max:
+ return Allow();
+
+ // Mesa sometimes wants to know the OS version.
+ case __NR_uname:
+ return Allow();
+
+ // nvidia tries to mknod(!) its devices; that won't work anyway,
+ // so quietly reject it.
+#ifdef __NR_mknod
+ case __NR_mknod:
+#endif
+ case __NR_mknodat:
+ return Error(EPERM);
+
+ // Used by the nvidia GPU driver, including in multi-GPU
+ // systems when we intend to use a non-nvidia GPU. (Also used
+ // by Mesa for its shader cache, but we disable that in this
+ // process.)
+ CASES_FOR_fstatfs:
+ return Allow();
+
+ // Pass through the common policy.
+ default:
+ return SandboxPolicyCommon::EvaluateSyscall(sysno);
+ }
+ }
+};
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetDecoderSandboxPolicy(
+ SandboxBrokerClient* aMaybeBroker) {
+ return UniquePtr<sandbox::bpf_dsl::Policy>(
+ new RDDSandboxPolicy(aMaybeBroker));
+}
+
+// Basically a clone of RDDSandboxPolicy until we know exactly what
+// the SocketProcess sandbox looks like.
+class SocketProcessSandboxPolicy final : public SandboxPolicyCommon {
+ public:
+ explicit SocketProcessSandboxPolicy(SandboxBrokerClient* aBroker) {
+ mBroker = aBroker;
+ mMayCreateShmem = true;
+ }
+
+ static intptr_t FcntlTrap(const sandbox::arch_seccomp_data& aArgs,
+ void* aux) {
+ const auto cmd = static_cast<int>(aArgs.args[1]);
+ switch (cmd) {
+ // This process can't exec, so the actual close-on-exec flag
+ // doesn't matter; have it always read as true and ignore writes.
+ case F_GETFD:
+ return O_CLOEXEC;
+ case F_SETFD:
+ return 0;
+ default:
+ return -ENOSYS;
+ }
+ }
+
+ Maybe<ResultExpr> EvaluateSocketCall(int aCall,
+ bool aHasArgs) const override {
+ switch (aCall) {
+ case SYS_SOCKET:
+ case SYS_CONNECT:
+ case SYS_BIND:
+ return Some(Allow());
+
+ // FIXME(bug 1641401) do we really need this?
+ case SYS_SENDMMSG:
+ return Some(Allow());
+
+ case SYS_GETSOCKOPT:
+ case SYS_SETSOCKOPT:
+ case SYS_GETSOCKNAME:
+ case SYS_GETPEERNAME:
+ case SYS_SHUTDOWN:
+ case SYS_ACCEPT:
+ case SYS_ACCEPT4:
+ return Some(Allow());
+
+ default:
+ return SandboxPolicyCommon::EvaluateSocketCall(aCall, aHasArgs);
+ }
+ }
+
+ ResultExpr PrctlPolicy() const override {
+ Arg<int> op(0);
+ Arg<int> arg2(1);
+ return Switch(op)
+ .CASES((PR_SET_VMA), // Tagging of anonymous memory mappings
+ If(arg2 == PR_SET_VMA_ANON_NAME, Allow()).Else(InvalidSyscall()))
+ .CASES((PR_SET_NAME, // Thread creation
+ PR_SET_DUMPABLE, // Crash reporting
+ PR_SET_PTRACER), // Debug-mode crash handling
+ Allow())
+ .Default(InvalidSyscall());
+ }
+
+ ResultExpr EvaluateSyscall(int sysno) const override {
+ switch (sysno) {
+ case __NR_getrusage:
+ return Allow();
+
+ case __NR_ioctl: {
+ Arg<unsigned long> request(1);
+ auto shifted_type = request & kIoctlTypeMask;
+
+ // Rust's stdlib seems to use FIOCLEX instead of equivalent fcntls.
+ return If(request == FIOCLEX, Allow())
+ // Rust's stdlib also uses FIONBIO instead of equivalent fcntls.
+ .ElseIf(request == FIONBIO, Allow())
+ // This is used by PR_Available in nsSocketInputStream::Available.
+ .ElseIf(request == FIONREAD, Allow())
+ // Allow anything that isn't a tty ioctl, for now; bug 1302711
+ // will cover changing this to a default-deny policy.
+ .ElseIf(shifted_type != kTtyIoctls, Allow())
+ .Else(SandboxPolicyCommon::EvaluateSyscall(sysno));
+ }
+
+ CASES_FOR_fcntl: {
+ Arg<int> cmd(1);
+ return Switch(cmd)
+ .Case(F_DUPFD_CLOEXEC, Allow())
+ // Nvidia GL and fontconfig (newer versions) use fcntl file locking.
+ .Case(F_SETLK, Allow())
+#ifdef F_SETLK64
+ .Case(F_SETLK64, Allow())
+#endif
+ // Pulseaudio uses F_SETLKW, as does fontconfig.
+ .Case(F_SETLKW, Allow())
+#ifdef F_SETLKW64
+ .Case(F_SETLKW64, Allow())
+#endif
+ .Default(SandboxPolicyCommon::EvaluateSyscall(sysno));
+ }
+
+#ifdef DESKTOP
+ // This section is borrowed from ContentSandboxPolicy
+ CASES_FOR_getrlimit:
+ CASES_FOR_getresuid:
+ CASES_FOR_getresgid:
+ return Allow();
+
+ case __NR_prlimit64: {
+ // Allow only the getrlimit() use case. (glibc seems to use
+ // only pid 0 to indicate the current process; pid == getpid()
+ // is equivalent and could also be allowed if needed.)
+ Arg<pid_t> pid(0);
+ // This is really a const struct ::rlimit*, but Arg<> doesn't
+ // work with pointers, only integer types.
+ Arg<uintptr_t> new_limit(2);
+ return If(AllOf(pid == 0, new_limit == 0), Allow())
+ .Else(InvalidSyscall());
+ }
+#endif // DESKTOP
+
+ // Bug 1640612
+ case __NR_uname:
+ return Allow();
+
+ default:
+ return SandboxPolicyCommon::EvaluateSyscall(sysno);
+ }
+ }
+};
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetSocketProcessSandboxPolicy(
+ SandboxBrokerClient* aMaybeBroker) {
+ return UniquePtr<sandbox::bpf_dsl::Policy>(
+ new SocketProcessSandboxPolicy(aMaybeBroker));
+}
+
+class UtilitySandboxPolicy : public SandboxPolicyCommon {
+ public:
+ explicit UtilitySandboxPolicy(SandboxBrokerClient* aBroker) {
+ mBroker = aBroker;
+ mMayCreateShmem = true;
+ }
+
+ ResultExpr PrctlPolicy() const override {
+ Arg<int> op(0);
+ Arg<int> arg2(1);
+ return Switch(op)
+ .CASES((PR_SET_VMA), // Tagging of anonymous memory mappings
+ If(arg2 == PR_SET_VMA_ANON_NAME, Allow()).Else(InvalidSyscall()))
+ .CASES((PR_SET_NAME, // Thread creation
+ PR_SET_DUMPABLE, // Crash reporting
+ PR_SET_PTRACER, // Debug-mode crash handling
+ PR_GET_PDEATHSIG), // PGO profiling, cf
+ // https://reviews.llvm.org/D29954
+ Allow())
+ .Default(InvalidSyscall());
+ }
+
+ ResultExpr EvaluateSyscall(int sysno) const override {
+ switch (sysno) {
+ case __NR_getrusage:
+ return Allow();
+
+ // Required by FFmpeg
+ case __NR_get_mempolicy:
+ return Allow();
+
+ // Required by libnuma for FFmpeg
+ case __NR_sched_getaffinity: {
+ Arg<pid_t> pid(0);
+ return If(pid == 0, Allow()).Else(Trap(SchedTrap, nullptr));
+ }
+
+ // Required by libnuma for FFmpeg
+ case __NR_set_mempolicy:
+ return Error(ENOSYS);
+
+ // Pass through the common policy.
+ default:
+ return SandboxPolicyCommon::EvaluateSyscall(sysno);
+ }
+ }
+};
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetUtilitySandboxPolicy(
+ SandboxBrokerClient* aMaybeBroker) {
+ return UniquePtr<sandbox::bpf_dsl::Policy>(
+ new UtilitySandboxPolicy(aMaybeBroker));
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/SandboxFilter.h b/security/sandbox/linux/SandboxFilter.h
new file mode 100644
index 0000000000..04a37a32d4
--- /dev/null
+++ b/security/sandbox/linux/SandboxFilter.h
@@ -0,0 +1,46 @@
+/* -*- 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 <vector>
+#include "mozilla/Atomics.h"
+#include "mozilla/Range.h"
+#include "mozilla/UniquePtr.h"
+
+namespace sandbox {
+namespace bpf_dsl {
+class Policy;
+}
+} // namespace sandbox
+
+namespace mozilla {
+class SandboxBrokerClient;
+
+struct ContentProcessSandboxParams;
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetContentSandboxPolicy(
+ SandboxBrokerClient* aMaybeBroker, ContentProcessSandboxParams&& aParams);
+
+class SandboxOpenedFiles;
+
+// The SandboxOpenedFiles object must live until the process exits.
+UniquePtr<sandbox::bpf_dsl::Policy> GetMediaSandboxPolicy(
+ const SandboxOpenedFiles* aFiles);
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetDecoderSandboxPolicy(
+ SandboxBrokerClient* aMaybeBroker);
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetSocketProcessSandboxPolicy(
+ SandboxBrokerClient* aMaybeBroker);
+
+UniquePtr<sandbox::bpf_dsl::Policy> GetUtilitySandboxPolicy(
+ SandboxBrokerClient* aMaybeBroker);
+
+} // namespace mozilla
+
+#endif
diff --git a/security/sandbox/linux/SandboxFilterUtil.cpp b/security/sandbox/linux/SandboxFilterUtil.cpp
new file mode 100644
index 0000000000..de065d5483
--- /dev/null
+++ b/security/sandbox/linux/SandboxFilterUtil.cpp
@@ -0,0 +1,142 @@
+/* -*- 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 <sys/socket.h>
+#include <sys/syscall.h>
+#include <unistd.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, false);
+ // 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, 1);
+ // 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
+ // clang-format off
+#define DISPATCH_SOCKETCALL(sysnum, socketnum) \
+ case sysnum: \
+ return EvaluateSocketCall(socketnum, true).valueOr(InvalidSyscall())
+ 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);
+#undef DISPATCH_SOCKETCALL
+#ifndef __NR_socketcall
+#ifndef ANDROID
+#define DISPATCH_SYSVCALL(sysnum, ipcnum) \
+ case sysnum: \
+ return EvaluateIpcCall(ipcnum, 0).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
+ // clang-format on
+ default:
+ return InvalidSyscall();
+ }
+}
+
+/* static */ bool SandboxPolicyBase::HasSeparateSocketCalls() {
+#ifdef __NR_socketcall
+ // If we have both syscalls, dynamically detect (and cache).
+ static const bool kCache = [] {
+ int fd = syscall(__NR_socket, AF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ MOZ_DIAGNOSTIC_ASSERT(errno == ENOSYS);
+ return false;
+ }
+ close(fd);
+ return true;
+ }();
+ return kCache;
+#else // no socketcall; must be separate syscalls
+ return true;
+#endif
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/SandboxFilterUtil.h b/security/sandbox/linux/SandboxFilterUtil.h
new file mode 100644
index 0000000000..6e9180bb95
--- /dev/null
+++ b/security/sandbox/linux/SandboxFilterUtil.h
@@ -0,0 +1,248 @@
+/* -*- 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. (As of kernel 4.2.0, x86 also has regular system
+// calls, but userland will typically still use socketcall.)
+//
+// 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;
+
+ // aHasArgs is true if this is a normal syscall, where the arguments
+ // can be inspected by seccomp-bpf, rather than a case of socketcall().
+ virtual Maybe<ResultExpr> EvaluateSocketCall(int aCall, bool aHasArgs) const {
+ return Nothing();
+ }
+
+ // Android doesn't use SysV IPC (and doesn't define the selector
+ // constants in its headers), so this isn't implemented there.
+#ifndef ANDROID
+ // aArgShift is the offset to add the argument index when
+ // constructing `Arg` objects: it's 0 for separate syscalls and 1
+ // for ipc().
+ virtual Maybe<ResultExpr> EvaluateIpcCall(int aCall, int aArgShift) const {
+ return Nothing();
+ }
+#endif
+
+ // Returns true if the running kernel supports separate syscalls for
+ // socket operations, or false if it supports only socketcall(2).
+ static bool HasSeparateSocketCalls();
+};
+
+} // 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
+// 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_lseek case __NR_lseek
+# define CASES_FOR_ftruncate case __NR_ftruncate
+#endif
+
+// getdents is not like the other FS-related syscalls with a "64" variant
+#ifdef __NR_getdents
+# define CASES_FOR_getdents \
+ case __NR_getdents64: \
+ case __NR_getdents
+#else
+# define CASES_FOR_getdents case __NR_getdents64
+#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_clock_gettime64
+# define CASES_FOR_clock_gettime \
+ case __NR_clock_gettime: \
+ case __NR_clock_gettime64
+# define CASES_FOR_clock_getres \
+ case __NR_clock_getres: \
+ case __NR_clock_getres_time64
+# define CASES_FOR_clock_nanosleep \
+ case __NR_clock_nanosleep: \
+ case __NR_clock_nanosleep_time64
+# define CASES_FOR_pselect6 \
+ case __NR_pselect6: \
+ case __NR_pselect6_time64
+# define CASES_FOR_ppoll \
+ case __NR_ppoll: \
+ case __NR_ppoll_time64
+# define CASES_FOR_futex \
+ case __NR_futex: \
+ case __NR_futex_time64
+#else
+# define CASES_FOR_clock_gettime case __NR_clock_gettime
+# define CASES_FOR_clock_getres case __NR_clock_getres
+# define CASES_FOR_clock_nanosleep case __NR_clock_nanosleep
+# define CASES_FOR_pselect6 case __NR_pselect6
+# define CASES_FOR_ppoll case __NR_ppoll
+# define CASES_FOR_futex case __NR_futex
+#endif
+
+#if defined(__NR__newselect)
+# define CASES_FOR_select \
+ case __NR__newselect: \
+ CASES_FOR_pselect6
+#elif defined(__NR_select)
+# define CASES_FOR_select \
+ case __NR_select: \
+ CASES_FOR_pselect6
+#else
+# define CASES_FOR_select CASES_FOR_pselect6
+#endif
+
+#ifdef __NR_poll
+# define CASES_FOR_poll \
+ case __NR_poll: \
+ CASES_FOR_ppoll
+#else
+# define CASES_FOR_poll CASES_FOR_ppoll
+#endif
+
+#ifdef __NR_epoll_create
+# define CASES_FOR_epoll_create \
+ case __NR_epoll_create: \
+ case __NR_epoll_create1
+#else
+# define CASES_FOR_epoll_create case __NR_epoll_create1
+#endif
+
+#ifdef __NR_epoll_wait
+# define CASES_FOR_epoll_wait \
+ case __NR_epoll_wait: \
+ case __NR_epoll_pwait
+#else
+# define CASES_FOR_epoll_wait case __NR_epoll_pwait
+#endif
+
+#ifdef __NR_pipe
+# define CASES_FOR_pipe \
+ case __NR_pipe: \
+ case __NR_pipe2
+#else
+# define CASES_FOR_pipe case __NR_pipe2
+#endif
+
+#ifdef __NR_dup2
+# define CASES_FOR_dup2 \
+ case __NR_dup2: \
+ case __NR_dup3
+#else
+# define CASES_FOR_dup2 case __NR_dup3
+#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 0000000000..eb4da9b87b
--- /dev/null
+++ b/security/sandbox/linux/SandboxHooks.cpp
@@ -0,0 +1,110 @@
+/* -*- 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 "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Types.h"
+
+#include <dlfcn.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/inotify.h>
+#ifdef MOZ_X11
+# include <X11/Xlib.h>
+#endif
+
+// Signal number used to enable seccomp on each thread.
+extern mozilla::Atomic<int> gSeccompTsyncBroadcastSignum;
+
+static bool SigSetNeedsFixup(const sigset_t* aSet) {
+ int tsyncSignum = gSeccompTsyncBroadcastSignum;
+
+ return aSet != nullptr &&
+ (sigismember(aSet, SIGSYS) ||
+ (tsyncSignum != 0 && sigismember(aSet, tsyncSignum)));
+}
+
+static void SigSetFixup(sigset_t* aSet) {
+ int tsyncSignum = gSeccompTsyncBroadcastSignum;
+ int rv = sigdelset(aSet, SIGSYS);
+ MOZ_RELEASE_ASSERT(rv == 0);
+ if (tsyncSignum != 0) {
+ rv = sigdelset(aSet, tsyncSignum);
+ MOZ_RELEASE_ASSERT(rv == 0);
+ }
+}
+
+// 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 (aHow == SIG_UNBLOCK || !SigSetNeedsFixup(aSet)) {
+ return aRealFunc(aHow, aSet, aOldSet);
+ }
+
+ sigset_t newSet = *aSet;
+ SigSetFixup(&newSet);
+ 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);
+}
+
+extern "C" MOZ_EXPORT int sigaction(int signum, const struct sigaction* act,
+ struct sigaction* oldact) {
+ static auto sRealFunc =
+ (int (*)(int, const struct sigaction*, struct sigaction*))dlsym(
+ RTLD_NEXT, "sigaction");
+
+ if (!sRealFunc) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (act == nullptr || !SigSetNeedsFixup(&act->sa_mask)) {
+ return sRealFunc(signum, act, oldact);
+ }
+
+ struct sigaction newact = *act;
+ SigSetFixup(&newact.sa_mask);
+ return sRealFunc(signum, &newact, oldact);
+}
+
+extern "C" MOZ_EXPORT int inotify_init(void) { return inotify_init1(0); }
+
+extern "C" MOZ_EXPORT int inotify_init1(int flags) {
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/security/sandbox/linux/SandboxInfo.cpp b/security/sandbox/linux/SandboxInfo.cpp
new file mode 100644
index 0000000000..3d71e55921
--- /dev/null
+++ b/security/sandbox/linux/SandboxInfo.cpp
@@ -0,0 +1,201 @@
+/* -*- 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 "mozilla/SandboxSettings.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 {
+
+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';
+ }
+
+ // Bug 1434528: In addition to CLONE_NEWUSER, do something that uses
+ // the new capabilities (in this case, cloning another namespace) to
+ // detect AppArmor policies that allow CLONE_NEWUSER but don't allow
+ // doing anything useful with it.
+ pid_t pid = syscall(__NR_clone, SIGCHLD | CLONE_NEWUSER | CLONE_NEWPID,
+ 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 */
+const 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;
+ }
+ }
+
+ if (HasUserNamespaceSupport()) {
+ flags |= kHasPrivilegedUserNamespaces;
+ if (CanCreateUserNamespace()) {
+ flags |= kHasUserNamespaces;
+ }
+ }
+
+ // We can't use mozilla::IsContentSandboxEnabled() here because a)
+ // libmozsandbox can't depend on libxul, and b) this is called in a static
+ // initializer before the prefences service is ready.
+ if (!getenv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+ flags |= kEnabledForContent;
+ }
+ if (getenv("MOZ_PERMISSIVE_CONTENT_SANDBOX")) {
+ flags |= kPermissive;
+ }
+ if (!getenv("MOZ_DISABLE_GMP_SANDBOX")) {
+ flags |= kEnabledForMedia;
+ }
+ if (getenv("MOZ_SANDBOX_LOGGING")) {
+ flags |= kVerbose;
+ }
+
+ 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 0000000000..fb20d206a3
--- /dev/null
+++ b/security/sandbox/linux/SandboxInfo.h
@@ -0,0 +1,70 @@
+/* -*- 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) = default;
+
+ // 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,
+ // Whether to use a sandbox for content processes; env var
+ // MOZ_DISABLE_CONTENT_SANDBOX
+ kEnabledForContent = 1 << 1,
+ // Whether to use a sandbox for GMP processes; env var
+ // MOZ_DISABLE_GMP_SANDBOX.
+ kEnabledForMedia = 1 << 2,
+ // Env var MOZ_SANDBOX_LOGGING.
+ 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,
+ // (1 << 8) was kUnexpectedThreads
+ };
+
+ 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; }
+
+ private:
+ enum Flags mFlags;
+ static const 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 0000000000..27934f9a8e
--- /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*, const 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 0000000000..dc9ac0c062
--- /dev/null
+++ b/security/sandbox/linux/SandboxLogging.cpp
@@ -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/. */
+
+#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"
+
+#ifdef __GLIBC_PREREQ
+# if __GLIBC_PREREQ(2, 32)
+# define HAVE_STRERRORNAME_NP 1
+# endif
+#endif
+
+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 char logPrefixProcess[16];
+ static const ssize_t printfSize =
+ ::base::strings::SafeSPrintf(logPrefixProcess, "[%d] ", getpid());
+ static const size_t pidSize =
+ std::clamp(static_cast<size_t>(printfSize), static_cast<size_t>(0),
+ static_cast<size_t>(sizeof(logPrefixProcess) - 1));
+ static const char logPrefix[] = "Sandbox: ", logSuffix[] = "\n";
+ struct iovec iovs[4] = {
+ {const_cast<char*>(logPrefixProcess), pidSize},
+ {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, 4));
+ if (written <= 0) {
+ break;
+ }
+ IOVecDrop(iovs, 4, static_cast<size_t>(written));
+ }
+}
+
+ssize_t GetLibcErrorName(char* aBuf, size_t aSize, int aErr) {
+ const char* errStr;
+
+#ifdef HAVE_STRERRORNAME_NP
+ // This is the function we'd like to have, but it's a glibc
+ // extension and present only in newer versions.
+ errStr = strerrorname_np(aErr);
+#else
+ // Otherwise, handle most of the basic / common errors with a big
+ // switch statement.
+ switch (aErr) {
+
+# define HANDLE_ERR(x) \
+ case x: \
+ errStr = #x; \
+ break
+
+ // errno-base.h
+ HANDLE_ERR(EPERM);
+ HANDLE_ERR(ENOENT);
+ HANDLE_ERR(ESRCH);
+ HANDLE_ERR(EINTR);
+ HANDLE_ERR(EIO);
+ HANDLE_ERR(ENXIO);
+ HANDLE_ERR(E2BIG);
+ HANDLE_ERR(ENOEXEC);
+ HANDLE_ERR(EBADF);
+ HANDLE_ERR(ECHILD);
+ HANDLE_ERR(EAGAIN);
+ HANDLE_ERR(ENOMEM);
+ HANDLE_ERR(EACCES);
+ HANDLE_ERR(EFAULT);
+ HANDLE_ERR(ENOTBLK);
+ HANDLE_ERR(EBUSY);
+ HANDLE_ERR(EEXIST);
+ HANDLE_ERR(EXDEV);
+ HANDLE_ERR(ENODEV);
+ HANDLE_ERR(ENOTDIR);
+ HANDLE_ERR(EISDIR);
+ HANDLE_ERR(EINVAL);
+ HANDLE_ERR(ENFILE);
+ HANDLE_ERR(EMFILE);
+ HANDLE_ERR(ENOTTY);
+ HANDLE_ERR(ETXTBSY);
+ HANDLE_ERR(EFBIG);
+ HANDLE_ERR(ENOSPC);
+ HANDLE_ERR(ESPIPE);
+ HANDLE_ERR(EROFS);
+ HANDLE_ERR(EMLINK);
+ HANDLE_ERR(EPIPE);
+ HANDLE_ERR(EDOM);
+ HANDLE_ERR(ERANGE);
+
+ // selected other errors
+ HANDLE_ERR(ENAMETOOLONG);
+ HANDLE_ERR(ENOSYS);
+ HANDLE_ERR(ENOTEMPTY);
+ HANDLE_ERR(ELOOP);
+ HANDLE_ERR(ENOTSOCK);
+ HANDLE_ERR(EMSGSIZE);
+ HANDLE_ERR(ECONNRESET);
+ HANDLE_ERR(ECONNREFUSED);
+ HANDLE_ERR(EHOSTUNREACH);
+ HANDLE_ERR(ESTALE);
+
+# undef HANDLE_ERR
+
+ default:
+ errStr = nullptr;
+ }
+#endif // no strerrorname_np
+
+ if (errStr) {
+ return base::strings::SafeSNPrintf(aBuf, aSize, "%s", errStr);
+ }
+
+ return base::strings::SafeSNPrintf(aBuf, aSize, "error %d", aErr);
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/SandboxLogging.h b/security/sandbox/linux/SandboxLogging.h
new file mode 100644
index 0000000000..94467ececc
--- /dev/null
+++ b/security/sandbox/linux/SandboxLogging.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_SandboxLogging_h
+#define mozilla_SandboxLogging_h
+
+// This header defines the SANDBOX_LOG 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
+
+#include <errno.h>
+
+namespace mozilla {
+// Logs the formatted string (marked with "error" severity, if supported).
+void SandboxLogError(const char* aMessage);
+
+// Writes into aBuf the identifier for an error number (e.g., "EINVAL"
+// rather than "Invalid argument"); may fall back to "error N" (with
+// the number) for unhandled errors.
+//
+// Bounds are handled like snprintf: the return value is the length
+// the string would have (not counting the null terminator) ignoring
+// buffer size, and the string written into the buffer may be
+// truncated to fit but is always null terminated.
+ssize_t GetLibcErrorName(char* aBuf, size_t aSize, int aErr);
+} // namespace mozilla
+
+#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(fmt, args...) \
+ do { \
+ char _sandboxLogBuf[SANDBOX_LOG_LEN]; \
+ ::base::strings::SafeSPrintf(_sandboxLogBuf, fmt, ##args); \
+ ::mozilla::SandboxLogError(_sandboxLogBuf); \
+ } while (0)
+
+#define SANDBOX_LOG_WITH_ERROR(errnum, fmt, args...) \
+ do { \
+ char _sandboxLogBuf[SANDBOX_LOG_LEN]; \
+ ssize_t _sandboxLogOff = \
+ ::base::strings::SafeSPrintf(_sandboxLogBuf, fmt ": ", ##args); \
+ if (static_cast<size_t>(_sandboxLogOff) < sizeof(_sandboxLogBuf)) { \
+ ::mozilla::GetLibcErrorName(_sandboxLogBuf + _sandboxLogOff, \
+ sizeof(_sandboxLogBuf) - _sandboxLogOff, \
+ errnum); \
+ } \
+ ::mozilla::SandboxLogError(_sandboxLogBuf); \
+ } while (0)
+
+#define SANDBOX_LOG_ERRNO(fmt, args...) \
+ SANDBOX_LOG_WITH_ERROR(errno, fmt, ##args)
+
+#endif // mozilla_SandboxLogging_h
diff --git a/security/sandbox/linux/SandboxOpenedFiles.cpp b/security/sandbox/linux/SandboxOpenedFiles.cpp
new file mode 100644
index 0000000000..8c26f7f206
--- /dev/null
+++ b/security/sandbox/linux/SandboxOpenedFiles.cpp
@@ -0,0 +1,77 @@
+/* -*- 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 "SandboxOpenedFiles.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <utility>
+
+#include "SandboxLogging.h"
+
+namespace mozilla {
+
+// The default move constructor almost works, but Atomic isn't
+// move-constructable and the fd needs some special handling.
+SandboxOpenedFile::SandboxOpenedFile(SandboxOpenedFile&& aMoved)
+ : mPath(std::move(aMoved.mPath)),
+ mMaybeFd(aMoved.TakeDesc()),
+ mDup(aMoved.mDup),
+ mExpectError(aMoved.mExpectError) {}
+
+SandboxOpenedFile::SandboxOpenedFile(const char* aPath, Dup aDup)
+ : mPath(aPath), mDup(aDup == Dup::YES), mExpectError(false) {
+ MOZ_ASSERT(aPath[0] == '/', "path should be absolute");
+
+ int fd = open(aPath, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ mExpectError = true;
+ }
+ mMaybeFd = fd;
+}
+
+SandboxOpenedFile::SandboxOpenedFile(const char* aPath, Error)
+ : mPath(aPath), mMaybeFd(-1), mDup(false), mExpectError(true) {}
+
+int SandboxOpenedFile::GetDesc() const {
+ int fd;
+ if (mDup) {
+ fd = mMaybeFd;
+ if (fd >= 0) {
+ fd = dup(fd);
+ if (fd < 0) {
+ SANDBOX_LOG_ERRNO("dup");
+ }
+ }
+ } else {
+ fd = TakeDesc();
+ }
+ if (fd < 0 && !mExpectError) {
+ SANDBOX_LOG("unexpected multiple open of file %s", Path());
+ }
+ return fd;
+}
+
+SandboxOpenedFile::~SandboxOpenedFile() {
+ int fd = TakeDesc();
+ if (fd >= 0) {
+ close(fd);
+ }
+}
+
+int SandboxOpenedFiles::GetDesc(const char* aPath) const {
+ for (const auto& file : mFiles) {
+ if (strcmp(file.Path(), aPath) == 0) {
+ return file.GetDesc();
+ }
+ }
+ SANDBOX_LOG("attempt to open unexpected file %s", aPath);
+ return -1;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/SandboxOpenedFiles.h b/security/sandbox/linux/SandboxOpenedFiles.h
new file mode 100644
index 0000000000..24881b93f0
--- /dev/null
+++ b/security/sandbox/linux/SandboxOpenedFiles.h
@@ -0,0 +1,97 @@
+/* -*- 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_SandboxOpenedFiles_h
+#define mozilla_SandboxOpenedFiles_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Range.h"
+#include "mozilla/UniquePtr.h"
+
+#include <vector>
+#include <string>
+
+// The use of C++ standard library containers here should be safe; the
+// standard (section container.requirements.dataraces) requires that
+// using const methods/pointers not introduce data races (e.g., from
+// interior mutability or global state).
+//
+// Reentrancy isn't guaranteed, and the library could use async signal
+// unsafe mutexes for "read-only" operations, but I'm assuming that that's
+// not the case at least for simple containers like string and vector.
+
+namespace mozilla {
+
+// This class represents a file that's been pre-opened for a media
+// plugin. It can be move-constructed but not copied.
+class SandboxOpenedFile final {
+ public:
+ enum class Dup { NO, YES };
+ struct Error {};
+
+ // This constructor opens the named file and saves the descriptor.
+ // If the open fails, IsOpen() will return false and GetDesc() will
+ // quietly return -1. If aDup is Dup::YES, GetDesc() will return a
+ // dup() of the descriptor every time it's called; otherwise, the
+ // first call will return the descriptor and any further calls will
+ // log an error message and return -1.
+ explicit SandboxOpenedFile(const char* aPath, Dup aDup = Dup::NO);
+
+ // This constructor is for files which the process will try to open
+ // but we don't want to grant access: using it will always fail
+ // (GetDesc will return -1) without logging.
+ SandboxOpenedFile(const char* aPath, Error);
+
+ // Simulates opening the pre-opened file; see the constructor's
+ // comment for details. Does not set errno on error, but may modify
+ // it as a side-effect. Thread-safe and intended to be async signal safe.
+ int GetDesc() const;
+
+ const char* Path() const { return mPath.c_str(); }
+
+ bool IsOpen() const { return mMaybeFd >= 0; }
+
+ ~SandboxOpenedFile();
+
+ MOZ_IMPLICIT SandboxOpenedFile(SandboxOpenedFile&& aMoved);
+
+ private:
+ std::string mPath;
+ mutable Atomic<int> mMaybeFd;
+ bool mDup;
+ bool mExpectError;
+
+ int TakeDesc() const { return mMaybeFd.exchange(-1); }
+};
+
+// This class represents a collection of files to be used to handle
+// open() calls from the media plugin (and the dynamic loader).
+// Because the seccomp-bpf policy exists until the process exits, this
+// object must not be destroyed after the syscall filter is installed.
+class SandboxOpenedFiles {
+ public:
+ SandboxOpenedFiles() = default;
+
+ template <typename... Args>
+ void Add(Args&&... aArgs) {
+ mFiles.emplace_back(std::forward<Args>(aArgs)...);
+ }
+
+ int GetDesc(const char* aPath) const;
+
+ private:
+ std::vector<SandboxOpenedFile> mFiles;
+
+ // We could allow destroying instances of this class that aren't
+ // used with seccomp-bpf (e.g., for unit testing) by having the
+ // destructor check a flag set by the syscall policy and crash,
+ // but let's not write that code until we actually need it.
+ ~SandboxOpenedFiles() = delete;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxOpenedFiles_h
diff --git a/security/sandbox/linux/SandboxReporterClient.cpp b/security/sandbox/linux/SandboxReporterClient.cpp
new file mode 100644
index 0000000000..6359c2cb9a
--- /dev/null
+++ b/security/sandbox/linux/SandboxReporterClient.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 "SandboxReporterClient.h"
+#include "SandboxLogging.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/PodOperations.h"
+#include "prenv.h"
+#include "sandbox/linux/bpf_dsl/seccomp_macros.h"
+#ifdef ANDROID
+# include "sandbox/linux/system_headers/linux_ucontext.h"
+#else
+# include <ucontext.h>
+#endif
+
+namespace mozilla {
+
+SandboxReporterClient::SandboxReporterClient(SandboxReport::ProcType aProcType,
+ int aFd)
+ : mProcType(aProcType), mFd(aFd) {
+ // Unfortunately, there isn't a good way to check that the fd is a
+ // socket connected to the right thing without attempting some kind
+ // of in-band handshake. However, the crash reporter (which also
+ // uses a "magic number" fd) doesn't do any kind of checking either,
+ // so it's probably okay to skip it here.
+}
+
+SandboxReporterClient::SandboxReporterClient(SandboxReport::ProcType aProcType)
+ : SandboxReporterClient(aProcType, kSandboxReporterFileDesc) {
+ MOZ_RELEASE_ASSERT(PR_GetEnv("MOZ_SANDBOXED") != nullptr);
+}
+
+SandboxReport SandboxReporterClient::MakeReport(const void* aContext) {
+ SandboxReport report;
+ const auto ctx = static_cast<const ucontext_t*>(aContext);
+
+ // Zero the entire struct; some memory safety analyses care about
+ // sending uninitialized alignment padding to another process.
+ PodZero(&report);
+
+ clock_gettime(CLOCK_MONOTONIC_COARSE, &report.mTime);
+ report.mPid = getpid();
+ report.mTid = syscall(__NR_gettid);
+ report.mProcType = mProcType;
+ report.mSyscall = SECCOMP_SYSCALL(ctx);
+ report.mArgs[0] = SECCOMP_PARM1(ctx);
+ report.mArgs[1] = SECCOMP_PARM2(ctx);
+ report.mArgs[2] = SECCOMP_PARM3(ctx);
+ report.mArgs[3] = SECCOMP_PARM4(ctx);
+ report.mArgs[4] = SECCOMP_PARM5(ctx);
+ report.mArgs[5] = SECCOMP_PARM6(ctx);
+ // Named Return Value Optimization allows the compiler to optimize
+ // out the copy here (and the one in MakeReportAndSend).
+ return report;
+}
+
+void SandboxReporterClient::SendReport(const SandboxReport& aReport) {
+ // The "common" seccomp-bpf policy allows sendmsg but not send(to),
+ // so just use sendmsg even though send would suffice for this.
+ struct iovec iov;
+ struct msghdr msg;
+
+ iov.iov_base = const_cast<void*>(static_cast<const void*>(&aReport));
+ iov.iov_len = sizeof(SandboxReport);
+ PodZero(&msg);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ const auto sent = sendmsg(mFd, &msg, MSG_NOSIGNAL);
+
+ if (sent != sizeof(SandboxReport)) {
+ MOZ_DIAGNOSTIC_ASSERT(sent == -1);
+ SANDBOX_LOG_ERRNO("Failed to report rejected syscall");
+ }
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/SandboxReporterClient.h b/security/sandbox/linux/SandboxReporterClient.h
new file mode 100644
index 0000000000..335472a7f9
--- /dev/null
+++ b/security/sandbox/linux/SandboxReporterClient.h
@@ -0,0 +1,47 @@
+/* -*- 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_SandboxReporterClient_h
+#define mozilla_SandboxReporterClient_h
+
+#include "reporter/SandboxReporterCommon.h"
+
+namespace mozilla {
+
+// This class is instantiated in child processes in Sandbox.cpp to
+// send reports from the SIGSYS handler to the SandboxReporter
+// instance in the parent.
+class SandboxReporterClient {
+ public:
+ // Note: this does not take ownership of the file descriptor; if
+ // it's not kSandboxReporterFileDesc (e.g., for unit testing), the
+ // caller will need to close it to avoid leaks.
+ SandboxReporterClient(SandboxReport::ProcType aProcType, int aFd);
+
+ // This constructor uses the default fd (kSandboxReporterFileDesc)
+ // for a sandboxed child process.
+ explicit SandboxReporterClient(SandboxReport::ProcType aProcType);
+
+ // Constructs a report from a signal context (the ucontext_t* passed
+ // as void* to an sa_sigaction handler); uses the caller's pid and tid.
+ SandboxReport MakeReport(const void* aContext);
+
+ void SendReport(const SandboxReport& aReport);
+
+ SandboxReport MakeReportAndSend(const void* aContext) {
+ SandboxReport report = MakeReport(aContext);
+ SendReport(report);
+ return report;
+ }
+
+ private:
+ SandboxReport::ProcType mProcType;
+ int mFd;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxReporterClient_h
diff --git a/security/sandbox/linux/broker/SandboxBroker.cpp b/security/sandbox/linux/broker/SandboxBroker.cpp
new file mode 100644
index 0000000000..75ab52979e
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -0,0 +1,1104 @@
+/* -*- 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 <sys/un.h>
+#include <unistd.h>
+
+#ifdef XP_LINUX
+# include <sys/prctl.h>
+#endif
+
+#include <utility>
+
+#include "GeckoProfiler.h"
+#include "SpecialSystemDirectory.h"
+#include "base/string_util.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+namespace mozilla {
+
+// Default/fallback temporary directory
+static const nsLiteralCString tempDirPrefix("/tmp");
+
+// This constructor signals failure by setting mFileDesc and aClientFd to -1.
+SandboxBroker::SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
+ int& aClientFd)
+ : mChildPid(aChildPid), mPolicy(std::move(aPolicy)) {
+ int fds[2];
+ if (0 != socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, fds)) {
+ SANDBOX_LOG_ERRNO("SandboxBroker: socketpair failed");
+ mFileDesc = -1;
+ aClientFd = -1;
+ return;
+ }
+ mFileDesc = fds[0];
+ aClientFd = fds[1];
+
+ if (!PlatformThread::Create(0, this, &mThread)) {
+ SANDBOX_LOG_ERRNO("SandboxBroker: thread creation failed");
+ close(mFileDesc);
+ close(aClientFd);
+ mFileDesc = -1;
+ aClientFd = -1;
+ }
+#if defined(MOZ_CONTENT_TEMP_DIR)
+ nsCOMPtr<nsIFile> tmpDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+ getter_AddRefs(tmpDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = tmpDir->GetNativePath(mContentTempPath);
+ if (NS_FAILED(rv)) {
+ mContentTempPath.Truncate();
+ }
+ }
+#endif
+}
+
+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(std::move(aPolicy), aChildPid, clientFd));
+ if (clientFd < 0) {
+ rv = nullptr;
+ } else {
+ // FileDescriptor can be constructed from an int, but that dup()s
+ // the fd; instead, transfer ownership:
+ aClientFdOut = ipc::FileDescriptor(UniqueFileHandle(clientFd));
+ }
+ return 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() = default;
+SandboxBroker::Policy::~Policy() = default;
+
+SandboxBroker::Policy::Policy(const Policy& aOther)
+ : mMap(aOther.mMap.Clone()) {}
+
+// 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);
+ if (aCond == AddIfExistsNow) {
+ struct stat statBuf;
+ if (lstat(aPath, &statBuf) != 0) {
+ return;
+ }
+ }
+ auto& perms = mMap.LookupOrInsert(path, MAY_ACCESS);
+ MOZ_ASSERT(perms & MAY_ACCESS);
+
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("policy for %s: %d -> %d", aPath, perms, perms | aPerms);
+ }
+ perms |= aPerms;
+}
+
+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;
+ }
+
+ Policy::AddDirInternal(aPerms, aPath);
+}
+
+void SandboxBroker::Policy::AddFutureDir(int aPerms, const char* aPath) {
+ Policy::AddDirInternal(aPerms, aPath);
+}
+
+void SandboxBroker::Policy::AddDirInternal(int aPerms, const char* aPath) {
+ // Add a Prefix permission on things inside the dir.
+ nsDependentCString path(aPath);
+ MOZ_ASSERT(path.Length() <= kMaxPathLen - 1);
+ // Enforce trailing / on aPath
+ if (path.Last() != '/') {
+ path.Append('/');
+ }
+ Policy::AddPrefixInternal(aPerms, path);
+
+ // Add a path permission on the dir itself so it can
+ // be opened. We're guaranteed to have a trailing / now,
+ // so just cut that.
+ path.Truncate(path.Length() - 1);
+ if (!path.IsEmpty()) {
+ Policy::AddPath(aPerms, path.get(), AddAlways);
+ }
+}
+
+void SandboxBroker::Policy::AddPrefix(int aPerms, const char* aPath) {
+ Policy::AddPrefixInternal(aPerms, nsDependentCString(aPath));
+}
+
+void SandboxBroker::Policy::AddPrefixInternal(int aPerms,
+ const nsACString& aPath) {
+ auto& perms = mMap.LookupOrInsert(aPath, MAY_ACCESS);
+ MOZ_ASSERT(perms & MAY_ACCESS);
+
+ int newPerms = perms | aPerms | RECURSIVE;
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("policy for %s: %d -> %d", PromiseFlatCString(aPath).get(),
+ perms, newPerms);
+ }
+ perms = newPerms;
+}
+
+void SandboxBroker::Policy::AddFilePrefix(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 (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0 &&
+ 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);
+}
+
+void SandboxBroker::Policy::AddDynamic(int aPerms, const char* aPath) {
+ struct stat statBuf;
+ bool exists = (stat(aPath, &statBuf) == 0);
+
+ if (!exists) {
+ AddPrefix(aPerms, aPath);
+ } else {
+ size_t len = strlen(aPath);
+ if (!len) return;
+ if (aPath[len - 1] == '/') {
+ AddDir(aPerms, aPath);
+ } else {
+ AddPath(aPerms, aPath);
+ }
+ }
+}
+
+void SandboxBroker::Policy::AddAncestors(const char* aPath, int aPerms) {
+ nsAutoCString path(aPath);
+
+ while (true) {
+ const auto lastSlash = path.RFindCharInSet("/");
+ if (lastSlash <= 0) {
+ MOZ_ASSERT(lastSlash == 0);
+ return;
+ }
+ path.Truncate(lastSlash);
+ AddPath(aPerms, path.get());
+ }
+}
+
+void SandboxBroker::Policy::FixRecursivePermissions() {
+ // This builds an entirely new hashtable in order to avoid iterator
+ // invalidation problems.
+ PathPermissionMap oldMap;
+ mMap.SwapElements(oldMap);
+
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("fixing recursive policy entries");
+ }
+
+ for (const auto& entry : oldMap) {
+ const nsACString& path = entry.GetKey();
+ const int& localPerms = entry.GetData();
+ int inheritedPerms = 0;
+
+ nsAutoCString ancestor(path);
+ // This is slightly different from the loop in AddAncestors: it
+ // leaves the trailing slashes attached so they'll match AddDir
+ // entries.
+ while (true) {
+ // Last() release-asserts that the string is not empty. We
+ // should never have empty keys in the map, and the Truncate()
+ // below will always give us a non-empty string.
+ if (ancestor.Last() == '/') {
+ ancestor.Truncate(ancestor.Length() - 1);
+ }
+ const auto lastSlash = ancestor.RFindCharInSet("/");
+ if (lastSlash < 0) {
+ MOZ_ASSERT(ancestor.IsEmpty());
+ break;
+ }
+ ancestor.Truncate(lastSlash + 1);
+ const int ancestorPerms = oldMap.Get(ancestor);
+ if (ancestorPerms & RECURSIVE) {
+ // if a child is set with FORCE_DENY, do not compute inheritedPerms
+ if ((localPerms & FORCE_DENY) == FORCE_DENY) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("skip inheritence policy for %s: %d",
+ PromiseFlatCString(path).get(), localPerms);
+ }
+ } else {
+ inheritedPerms |= ancestorPerms & ~RECURSIVE;
+ }
+ }
+ }
+
+ const int newPerms = localPerms | inheritedPerms;
+ if ((newPerms & ~RECURSIVE) == inheritedPerms) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("removing redundant %s: %d -> %d",
+ PromiseFlatCString(path).get(), localPerms, newPerms);
+ }
+ // Skip adding this entry to the new map.
+ continue;
+ }
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("new policy for %s: %d -> %d", PromiseFlatCString(path).get(),
+ localPerms, newPerms);
+ }
+ mMap.InsertOrUpdate(path, newPerms);
+ }
+}
+
+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 (const auto& entry : mMap) {
+ const nsACString& whiteListPath = entry.GetKey();
+ const int& perms = entry.GetData();
+
+ 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 directory access (creation).
+ 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 | X_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_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;
+ }
+ // Linux allows O_TRUNC even with O_RDONLY
+ if (aReqFlags & O_TRUNC) {
+ needed |= SandboxBroker::MAY_WRITE;
+ }
+ return (aPerms & needed) == needed;
+}
+
+static int DoStat(const char* aPath, statstruct* aBuff, int aFlags) {
+ if (aFlags & O_NOFOLLOW) {
+ return lstatsyscall(aPath, aBuff);
+ }
+ return statsyscall(aPath, aBuff);
+}
+
+static int DoLink(const char* aPath, const char* aPath2,
+ SandboxBrokerCommon::Operation aOper) {
+ if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) {
+ return link(aPath, aPath2);
+ }
+ if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
+ return symlink(aPath, aPath2);
+ }
+ MOZ_CRASH("SandboxBroker: Unknown link operation");
+}
+
+static int DoConnect(const char* aPath, size_t aLen, int aType,
+ bool aIsAbstract) {
+ // Deny SOCK_DGRAM for the same reason it's denied for socketpair.
+ if (aType != SOCK_STREAM && aType != SOCK_SEQPACKET) {
+ errno = EACCES;
+ return -1;
+ }
+ // Ensure that the address is a pathname. (An empty string
+ // resulting from an abstract address probably shouldn't have made
+ // it past the policy check, but check explicitly just in case.)
+ if (aPath[0] == '\0') {
+ errno = ENETUNREACH;
+ return -1;
+ }
+
+ // Try to copy the name into a normal-sized sockaddr_un, with
+ // null-termination. Specifically, from man page:
+ //
+ // When the address of an abstract socket is returned, the returned addrlen is
+ // greater than sizeof(sa_family_t) (i.e., greater than 2), and the name of
+ // the socket is contained in the first (addrlen - sizeof(sa_family_t)) bytes
+ // of sun_path.
+ //
+ // As mentionned in `SandboxBrokerClient::Connect()`, `DoCall` expects a
+ // null-terminated string while abstract socket are not. So we receive a copy
+ // here and we have to put things back correctly as a real abstract socket to
+ // perform the brokered `connect()` call.
+ struct sockaddr_un sun;
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ char* sunPath = sun.sun_path;
+ size_t sunLen = sizeof(sun.sun_path);
+ size_t addrLen = sizeof(sun);
+ if (aIsAbstract) {
+ *sunPath++ = '\0';
+ sunLen--;
+ addrLen = offsetof(struct sockaddr_un, sun_path) + aLen + 1;
+ }
+ if (aLen + 1 > sunLen) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ memcpy(sunPath, aPath, aLen);
+
+ // Finally, the actual socket connection.
+ const int fd = socket(AF_UNIX, aType | SOCK_CLOEXEC, 0);
+ if (fd < 0) {
+ return -1;
+ }
+ if (connect(fd, reinterpret_cast<struct sockaddr*>(&sun), addrLen) < 0) {
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+size_t SandboxBroker::RealPath(char* aPath, size_t aBufSize, size_t aPathLen) {
+ char* result = realpath(aPath, nullptr);
+ if (result != nullptr) {
+ base::strlcpy(aPath, result, aBufSize);
+ free(result);
+ // Size changed, but guaranteed to be 0 terminated
+ aPathLen = strlen(aPath);
+ }
+ return aPathLen;
+}
+
+size_t SandboxBroker::ConvertRelativePath(char* aPath, size_t aBufSize,
+ size_t aPathLen) {
+ if (strstr(aPath, "..") != nullptr) {
+ return RealPath(aPath, aBufSize, aPathLen);
+ }
+ return aPathLen;
+}
+
+#if defined(MOZ_CONTENT_TEMP_DIR)
+size_t SandboxBroker::RemapTempDirs(char* aPath, size_t aBufSize,
+ size_t aPathLen) {
+ nsAutoCString path(aPath);
+
+ size_t prefixLen = 0;
+ if (!mTempPath.IsEmpty() && StringBeginsWith(path, mTempPath)) {
+ prefixLen = mTempPath.Length();
+ } else if (StringBeginsWith(path, tempDirPrefix)) {
+ prefixLen = tempDirPrefix.Length();
+ }
+
+ if (prefixLen) {
+ const nsDependentCSubstring cutPath =
+ Substring(path, prefixLen, path.Length() - prefixLen);
+
+ // Only now try to get the content process temp dir
+ if (!mContentTempPath.IsEmpty()) {
+ nsAutoCString tmpPath;
+ tmpPath.Assign(mContentTempPath);
+ tmpPath.Append(cutPath);
+ base::strlcpy(aPath, tmpPath.get(), aBufSize);
+ return strlen(aPath);
+ }
+ }
+
+ return aPathLen;
+}
+#endif
+
+nsCString SandboxBroker::ReverseSymlinks(const nsACString& aPath) {
+ // Revert any symlinks we previously resolved.
+ int32_t cutLength = aPath.Length();
+ nsCString cutPath(Substring(aPath, 0, cutLength));
+
+ for (;;) {
+ nsCString orig;
+ bool found = mSymlinkMap.Get(cutPath, &orig);
+ if (found) {
+ orig.Append(Substring(aPath, cutLength, aPath.Length() - cutLength));
+ return orig;
+ }
+ // Not found? Remove a path component and try again.
+ int32_t pos = cutPath.RFindChar('/');
+ if (pos == kNotFound || pos <= 0) {
+ // will be empty
+ return orig;
+ } else {
+ // Cut until just before the /
+ cutLength = pos;
+ cutPath.Assign(Substring(cutPath, 0, cutLength));
+ }
+ }
+}
+
+int SandboxBroker::SymlinkPermissions(const char* aPath,
+ const size_t aPathLen) {
+ // Work on a temporary copy, so we can reverse it.
+ // Because we bail on a writable dir, SymlinkPath
+ // might not restore the callers' path exactly.
+ char pathBufSymlink[kMaxPathLen + 1];
+ strcpy(pathBufSymlink, aPath);
+
+ nsCString orig =
+ ReverseSymlinks(nsDependentCString(pathBufSymlink, aPathLen));
+ if (!orig.IsEmpty()) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("Reversing %s -> %s", aPath, orig.get());
+ }
+ base::strlcpy(pathBufSymlink, orig.get(), sizeof(pathBufSymlink));
+ }
+
+ int perms = 0;
+ // Resolve relative paths, propagate permissions and
+ // fail if a symlink is in a writable path. The output is in perms.
+ char* result =
+ SandboxBroker::SymlinkPath(mPolicy.get(), pathBufSymlink, NULL, &perms);
+ if (result != NULL) {
+ free(result);
+ // We finished the translation, so we have a usable return in "perms".
+ return perms;
+ } else {
+ // Empty path means we got a writable dir in the chain or tried
+ // to back out of a link target.
+ return 0;
+ }
+}
+
+void SandboxBroker::ThreadMain(void) {
+ // Create a nsThread wrapper for the current platform thread, and register it
+ // with the thread manager.
+ (void)NS_GetCurrentThread();
+
+ char threadName[16];
+ SprintfLiteral(threadName, "FSBroker%d", mChildPid);
+ PlatformThread::SetName(threadName);
+
+ AUTO_PROFILER_REGISTER_THREAD(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);
+
+#if defined(MOZ_CONTENT_TEMP_DIR)
+ // Find the current temporary directory
+ nsCOMPtr<nsIFile> tmpDir;
+ nsresult rv =
+ GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(tmpDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = tmpDir->GetNativePath(mTempPath);
+ if (NS_SUCCEEDED(rv)) {
+ // Make sure there's no terminating /
+ if (mTempPath.Last() == '/') {
+ mTempPath.Truncate(mTempPath.Length() - 1);
+ }
+ }
+ }
+ // If we can't find it, we aren't bothered much: we will
+ // always try /tmp anyway in the substitution code
+ if (NS_FAILED(rv) || mTempPath.IsEmpty()) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("Tempdir: /tmp");
+ }
+ } else {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("Tempdir: %s", mTempPath.get());
+ }
+ // If it's /tmp, clear it here so we don't compare against
+ // it twice. Just let the fallback code do the work.
+ if (mTempPath.Equals(tempDirPrefix)) {
+ mTempPath.Truncate();
+ }
+ }
+#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 = 0;
+ size_t pathLen2 = 0;
+ 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("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_ERRNO("bad read from pid %d", mChildPid);
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+ if (recvd < static_cast<ssize_t>(sizeof(req))) {
+ SANDBOX_LOG("bad read from pid %d (%d < %d)", mChildPid, recvd,
+ sizeof(req));
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+ if (respfd == -1) {
+ SANDBOX_LOG("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("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);
+
+ // First string is guaranteed to be 0-terminated.
+ pathLen = first_len;
+
+ // Look up the first pathname but first translate relative paths.
+ pathLen = ConvertRelativePath(pathBuf, sizeof(pathBuf), pathLen);
+ perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
+
+ // We don't have permissions on the requested dir.
+#if defined(MOZ_CONTENT_TEMP_DIR)
+ if (!perms) {
+ // Was it a tempdir that we can remap?
+ pathLen = RemapTempDirs(pathBuf, sizeof(pathBuf), pathLen);
+ perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
+ }
+#endif
+ if (!perms) {
+ // Did we arrive from a symlink in a path that is not writable?
+ // Then try to figure out the original path and see if that is
+ // readable. Work on the original path, this reverses
+ // ConvertRelative above.
+ int symlinkPerms = SymlinkPermissions(recvBuf, first_len);
+ if (symlinkPerms > 0) {
+ perms = symlinkPerms;
+ }
+ }
+ if (!perms) {
+ // Now try the opposite case: translate symlinks to their
+ // actual destination file. Firefox always resolves symlinks,
+ // and in most cases we have whitelisted fixed paths that
+ // libraries will rely on and try to open. So this codepath
+ // is mostly useful for Mesa which had its kernel interface
+ // moved around.
+ pathLen = RealPath(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 = ConvertRelativePath(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 ((perms & FORCE_DENY) == FORCE_DENY) {
+ resp.mError = -EACCES;
+ } 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)) {
+ if (access(pathBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_STAT:
+ MOZ_ASSERT(req.mBufSize == sizeof(statstruct));
+ if (DoStat(pathBuf, (statstruct*)&respBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ ios[1].iov_base = &respBuf;
+ ios[1].iov_len = sizeof(statstruct);
+ } 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 | X_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 | X_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 {
+ struct stat sb;
+ // This doesn't need an additional policy check because
+ // MAY_ACCESS is required to even enter this switch statement.
+ if (lstat(pathBuf, &sb) == 0) {
+ resp.mError = -EEXIST;
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ }
+ break;
+
+ case SANDBOX_FILE_UNLINK:
+ if (permissive || AllowOperation(W_OK | X_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) {
+ if (respSize > 0) {
+ // Record the mapping so we can invert the file to the original
+ // symlink.
+ nsDependentCString orig(pathBuf, pathLen);
+ nsDependentCString xlat(respBuf, respSize);
+ if (!orig.Equals(xlat) && xlat[0] == '/') {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("Recording mapping %s -> %s", xlat.get(),
+ orig.get());
+ }
+ mSymlinkMap.InsertOrUpdate(xlat, orig);
+ }
+ // Make sure we can invert a fully resolved mapping too. If our
+ // caller is realpath, and there's a relative path involved, the
+ // client side will try to open this one.
+ char* resolvedBuf = realpath(pathBuf, nullptr);
+ if (resolvedBuf) {
+ nsDependentCString resolvedXlat(resolvedBuf);
+ if (!orig.Equals(resolvedXlat) &&
+ !xlat.Equals(resolvedXlat)) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("Recording mapping %s -> %s",
+ resolvedXlat.get(), orig.get());
+ }
+ mSymlinkMap.InsertOrUpdate(resolvedXlat, orig);
+ }
+ free(resolvedBuf);
+ }
+ }
+ // Truncate the reply to the size of the client's
+ // buffer, matching the real readlink()'s behavior in
+ // that case, and being careful with the input data.
+ ssize_t callerSize =
+ std::max(AssertedCast<ssize_t>(req.mBufSize), ssize_t(0));
+ respSize = std::min(respSize, callerSize);
+ resp.mError = AssertedCast<int>(respSize);
+ ios[1].iov_base = &respBuf;
+ ios[1].iov_len = ReleaseAssertedCast<size_t>(respSize);
+ MOZ_RELEASE_ASSERT(ios[1].iov_len <= sizeof(respBuf));
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_SOCKET_CONNECT:
+ case SANDBOX_SOCKET_CONNECT_ABSTRACT:
+ if (permissive || (perms & MAY_CONNECT) != 0) {
+ openedFd = DoConnect(pathBuf, pathLen, req.mFlags,
+ req.mOp == SANDBOX_SOCKET_CONNECT_ABSTRACT);
+ if (openedFd >= 0) {
+ resp.mError = 0;
+ } 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;
+ const ssize_t sent = SendWithFd(respfd, ios, numIO, openedFd);
+ if (sent < 0) {
+ SANDBOX_LOG_ERRNO("failed to send broker response to pid %d", mChildPid);
+ } else {
+ MOZ_ASSERT(static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len);
+ }
+
+ // Work around Linux kernel bug: recvmsg checks for pending data
+ // and then checks for EOF or shutdown, without synchronization;
+ // if the sendmsg and last close occur between those points, it
+ // will see no pending data (before) and a closed socket (after),
+ // and incorrectly return EOF even though there is a message to be
+ // read. To avoid this, we send an extra message with a reference
+ // to respfd, so the last close can't happen until after the real
+ // response is read.
+ //
+ // See also: https://bugzil.la/1243108#c48
+ const struct Response fakeResp = {-4095};
+ const struct iovec fakeIO = {const_cast<Response*>(&fakeResp),
+ sizeof(fakeResp)};
+ // If the client has already read the real response and closed its
+ // socket then this will fail, but that's fine.
+ if (SendWithFd(respfd, &fakeIO, 1, respfd) < 0) {
+ MOZ_ASSERT(errno == EPIPE || errno == ECONNREFUSED || errno == ENOTCONN);
+ }
+
+ close(respfd);
+
+ 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_ERRNO(
+ "SandboxBroker: would have denied op=%s rflags=%o perms=%d path=%s for "
+ "pid=%d permissive=1; real status",
+ OperationDescription[aOp], aFlags, aPerms, aPath, mChildPid);
+}
+
+void SandboxBroker::AuditDenial(int aOp, int aFlags, int aPerms,
+ const char* aPath) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG(
+ "SandboxBroker: denied op=%s rflags=%o perms=%d path=%s for pid=%d",
+ OperationDescription[aOp], aFlags, aPerms, aPath, mChildPid);
+ }
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBroker.h b/security/sandbox/linux/broker/SandboxBroker.h
new file mode 100644
index 0000000000..ad3d4b7d49
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBroker.h
@@ -0,0 +1,180 @@
+/* -*- 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 "nsTHashMap.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,
+ // Allow Unix-domain socket connections to a path
+ MAY_CONNECT = 1 << 6,
+ // This flag is for adding a deny rule, so that we can e.g., allow read
+ // access to ~/.config/ but still deny access to ~/.config/mozilla/.
+ // It will bypass other checks.
+ FORCE_DENY = 1 << 7,
+ };
+ // 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 nsTHashMap<nsCStringHashKey, int> PathPermissionMap;
+
+ class Policy {
+ PathPermissionMap mMap;
+
+ public:
+ Policy();
+ Policy(const Policy& aOther);
+ ~Policy();
+
+ // Add permissions from AddDir/AddDynamic rules to any rules that
+ // exist for their descendents, and remove any descendent rules
+ // made redundant by this process.
+ //
+ // Call this after adding rules and before using the policy to
+ // prevent the descendent rules from shadowing the ancestor rules
+ // and removing permissions that we expect the file to have.
+ void FixRecursivePermissions();
+
+ 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);
+ // A directory, and all files and directories under it, even those
+ // added after creation (the dir itself may not exist).
+ void AddFutureDir(int aPerms, const char* aPath);
+ // All files in a directory with a given prefix; useful for devices.
+ void AddFilePrefix(int aPerms, const char* aDir, const char* aPrefix);
+ // Everything starting with the given path, even those files/dirs
+ // added after creation. The file or directory may or may not exist.
+ void AddPrefix(int aPerms, const char* aPath);
+ // Adds a file or dir (end with /) if it exists, and a prefix otherwhise.
+ void AddDynamic(int aPerms, const char* aPath);
+ // Adds permissions on all ancestors of a path. (This doesn't
+ // include the root directory, but if the path is given with a
+ // trailing slash it includes the path without the slash.)
+ void AddAncestors(const char* aPath, int aPerms = MAY_ACCESS);
+ // 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));
+ }
+
+ bool IsEmpty() const { return mMap.Count() == 0; }
+
+ 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;
+ void AddPrefixInternal(int aPerms, const nsACString& aPath);
+ void AddDirInternal(int aPerms, const char* aPath);
+ };
+
+ // 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;
+#if defined(MOZ_CONTENT_TEMP_DIR)
+ nsCString mTempPath;
+ nsCString mContentTempPath;
+#endif
+
+ typedef nsTHashMap<nsCStringHashKey, nsCString> PathMap;
+ PathMap mSymlinkMap;
+
+ 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 ConvertRelativePath(char* aPath, size_t aBufSize, size_t aPathLen);
+ size_t RealPath(char* aPath, size_t aBufSize, size_t aPathLen);
+#if defined(MOZ_CONTENT_TEMP_DIR)
+ // Remap references to /tmp and friends to the content process tempdir
+ size_t RemapTempDirs(char* aPath, size_t aBufSize, size_t aPathLen);
+#endif
+ nsCString ReverseSymlinks(const nsACString& aPath);
+ // Retrieves permissions for the path the original symlink sits in.
+ int SymlinkPermissions(const char* aPath, const size_t aPathLen);
+ // In SandboxBrokerRealPath.cpp
+ char* SymlinkPath(const Policy* aPolicy, const char* __restrict aPath,
+ char* __restrict aResolved, int* aPermission);
+
+ // 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 0000000000..baba0b25a5
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
@@ -0,0 +1,159 @@
+/* -*- 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"
+
+// This file is built both within libxul and as a separate libmozsandbox
+// library. We can only use profiler annotations within libxul.
+#ifdef MOZILLA_INTERNAL_API
+# include "mozilla/ProfilerThreadSleep.h"
+#else
+# define AUTO_PROFILER_THREAD_SLEEP
+#endif
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.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 {
+
+const char* SandboxBrokerCommon::OperationDescription[] = {
+ "open",
+ "access",
+ "stat",
+ "chmod",
+ "link",
+ "symlink",
+ "mkdir",
+ "rename",
+ "rmdir",
+ "unlink",
+ "readlink",
+ "connect",
+ "connect-abstract",
+};
+
+/* 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.)
+ AUTO_PROFILER_THREAD_SLEEP;
+ 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]);
+ }
+ // In theory, the kernel should delete the message instead of
+ // giving us an empty one, if errors prevent transferring the
+ // fd.
+ MOZ_DIAGNOSTIC_ASSERT(cmsg->cmsg_len != 0);
+ errno = EPROTO;
+ 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;
+ }
+ // MSG_CTRUNC usually means the attached fd was dropped due to fd
+ // exhaustion in the receiving process, so map that to `EMFILE`.
+ // (It could also happen if the other process maliciously sends
+ // too many fds.)
+ //
+ // MSG_TRUNC (truncation of the data part) shouldn't ever happen.
+ // However, it has happened in the past, due to accidentally
+ // sending more data than the receiver was expecting. We assert
+ // that that doesn't happen (and, if it does, try to map it to a
+ // vaguely sensible error code).
+ MOZ_DIAGNOSTIC_ASSERT((msg.msg_flags & MSG_TRUNC) == 0);
+ errno = (msg.msg_flags & MSG_CTRUNC) ? EMFILE : EPROTO;
+ 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))];
+ memset(cmsg_buf, 0, sizeof(cmsg_buf));
+ 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 0000000000..b6b69e2a36
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -0,0 +1,77 @@
+/* -*- 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,
+ SANDBOX_SOCKET_CONNECT,
+ SANDBOX_SOCKET_CONNECT_ABSTRACT,
+ };
+ // String versions of the above
+ static const char* OperationDescription[];
+
+ struct Request {
+ Operation mOp;
+ // For open, flags; for access, "mode"; for stat, O_NOFOLLOW for lstat.
+ // For connect, the socket type.
+ 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 0000000000..0e5358e68e
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -0,0 +1,1083 @@
+/* -*- 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 "SandboxLogging.h"
+
+#include "base/shared_memory.h"
+#include "mozilla/Array.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SandboxLaunch.h"
+#include "mozilla/SandboxSettings.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "SpecialSystemDirectory.h"
+#include "nsReadableUtils.h"
+#include "nsIFileStreams.h"
+#include "nsILineInputStream.h"
+#include "nsIFile.h"
+
+#include "nsNetCID.h"
+#include "prenv.h"
+
+#ifdef ANDROID
+# include "cutils/properties.h"
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+# include "mozilla/WidgetUtilsGtk.h"
+# include <glib.h>
+#endif
+
+#ifdef MOZ_ENABLE_V4L2
+# include <linux/videodev2.h>
+# include <sys/ioctl.h>
+# include <fcntl.h>
+#endif // MOZ_ENABLE_V4L2
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#ifndef ANDROID
+# include <glob.h>
+#endif
+
+namespace mozilla {
+
+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;
+static const int access = SandboxBroker::MAY_ACCESS;
+static const int deny = SandboxBroker::FORCE_DENY;
+} // namespace
+
+using CacheE = std::pair<nsCString, int>;
+using FileCacheT = nsTArray<CacheE>;
+
+static void AddDriPaths(SandboxBroker::Policy* aPolicy) {
+ // Bug 1401666: Mesa driver loader part 2: Mesa <= 12 using libudev
+ // Used by libdrm, which is used by Mesa, and
+ // Intel(R) Media Driver for VAAPI.
+ if (auto dir = opendir("/dev/dri")) {
+ while (auto entry = readdir(dir)) {
+ if (entry->d_name[0] != '.') {
+ nsPrintfCString devPath("/dev/dri/%s", entry->d_name);
+ struct stat sb;
+ if (stat(devPath.get(), &sb) == 0 && S_ISCHR(sb.st_mode)) {
+ // For both the DRI node and its parent (the physical
+ // device), allow reading the "uevent" file.
+ static const Array<nsCString, 2> kSuffixes = {""_ns, "/device"_ns};
+ nsPrintfCString prefix("/sys/dev/char/%u:%u", major(sb.st_rdev),
+ minor(sb.st_rdev));
+ for (const auto& suffix : kSuffixes) {
+ nsCString sysPath(prefix + suffix);
+
+ // libudev will expand the symlink but not do full
+ // canonicalization, so it will leave in ".." path
+ // components that will be realpath()ed in the
+ // broker. To match this, allow the canonical paths.
+ UniqueFreePtr<char[]> realSysPath(realpath(sysPath.get(), nullptr));
+ if (realSysPath) {
+ // https://gitlab.freedesktop.org/mesa/drm/-/commit/3988580e4c0f4b3647a0c6af138a3825453fe6e0
+ // > term = strrchr(real_path, '/');
+ // > if (term && strncmp(term, "/virtio", 7) == 0)
+ // > *term = 0;
+ char* term = strrchr(realSysPath.get(), '/');
+ if (term && strncmp(term, "/virtio", 7) == 0) {
+ *term = 0;
+ }
+
+ aPolicy->AddFilePrefix(rdonly, realSysPath.get(), "");
+ // Allowing stat-ing and readlink-ing the parent dirs
+ nsPrintfCString basePath("%s/", realSysPath.get());
+ aPolicy->AddAncestors(basePath.get(), rdonly);
+ }
+ }
+
+ // https://gitlab.freedesktop.org/mesa/drm/-/commit/a02900133b32dd4a7d6da4966f455ab337e80dfc
+ // > strncpy(path, device_path, PATH_MAX);
+ // > strncat(path, "/subsystem", PATH_MAX);
+ // >
+ // > if (readlink(path, link, PATH_MAX) < 0)
+ // > return -errno;
+ nsCString subsystemPath(prefix + "/device/subsystem"_ns);
+ aPolicy->AddPath(rdonly, subsystemPath.get());
+ aPolicy->AddAncestors(subsystemPath.get(), rdonly);
+ }
+ }
+ }
+ closedir(dir);
+ }
+
+ // https://gitlab.freedesktop.org/mesa/mesa/-/commit/04bdbbcab3c4862bf3f54ce60fcc1d2007776f80
+ aPolicy->AddPath(rdonly, "/usr/share/drirc.d");
+
+ // https://dri.freedesktop.org/wiki/ConfigurationInfrastructure/
+ aPolicy->AddPath(rdonly, "/etc/drirc");
+
+ nsCOMPtr<nsIFile> drirc;
+ nsresult rv =
+ GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(drirc));
+ if (NS_SUCCEEDED(rv)) {
+ rv = drirc->AppendNative(".drirc"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = drirc->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ aPolicy->AddPath(rdonly, tmpPath.get());
+ }
+ }
+ }
+}
+
+static void JoinPathIfRelative(const nsACString& aCwd, const nsACString& inPath,
+ nsACString& outPath) {
+ if (inPath.Length() < 1) {
+ outPath.Assign(aCwd);
+ SANDBOX_LOG("Unjoinable path: %s", PromiseFlatCString(aCwd).get());
+ return;
+ }
+ const char* startChar = inPath.BeginReading();
+ if (*startChar != '/') {
+ // Relative path, copy basepath in front
+ outPath.Assign(aCwd);
+ outPath.Append("/");
+ outPath.Append(inPath);
+ } else {
+ // Absolute path, it's ok like this
+ outPath.Assign(inPath);
+ }
+}
+
+static void CachePathsFromFile(FileCacheT& aCache, const nsACString& aPath);
+
+static void CachePathsFromFileInternal(FileCacheT& aCache,
+ const nsACString& aCwd,
+ const nsACString& aPath) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> ldconfig(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = ldconfig->InitWithNativePath(aPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ nsCOMPtr<nsIFileInputStream> fileStream(
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ rv = fileStream->Init(ldconfig, -1, -1, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsAutoCString line;
+ bool more = true;
+ do {
+ rv = lineStream->ReadLine(line, &more);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ // Cut off any comments at the end of the line, also catches lines
+ // that are entirely a comment
+ int32_t hash = line.FindChar('#');
+ if (hash >= 0) {
+ line = Substring(line, 0, hash);
+ }
+ // Simplify our following parsing by trimming whitespace
+ line.CompressWhitespace(true, true);
+ if (line.IsEmpty()) {
+ // Skip comment lines
+ continue;
+ }
+ // Check for any included files and recursively process
+ nsACString::const_iterator start, end, token_end;
+
+ line.BeginReading(start);
+ line.EndReading(end);
+ token_end = end;
+
+ if (FindInReadable("include "_ns, start, token_end)) {
+ nsAutoCString includes(Substring(token_end, end));
+ for (const nsACString& includeGlob : includes.Split(' ')) {
+ // Glob path might be relative, so add cwd if so.
+ nsAutoCString includeFile;
+ JoinPathIfRelative(aCwd, includeGlob, includeFile);
+ glob_t globbuf;
+ if (!glob(PromiseFlatCString(includeFile).get(), GLOB_NOSORT, nullptr,
+ &globbuf)) {
+ for (size_t fileIdx = 0; fileIdx < globbuf.gl_pathc; fileIdx++) {
+ nsAutoCString filePath(globbuf.gl_pathv[fileIdx]);
+ CachePathsFromFile(aCache, filePath);
+ }
+ globfree(&globbuf);
+ }
+ }
+ }
+
+ // Cut off anything behind an = sign, used by dirname=TYPE directives
+ int32_t equals = line.FindChar('=');
+ if (equals >= 0) {
+ line = Substring(line, 0, equals);
+ }
+ char* resolvedPath = realpath(line.get(), nullptr);
+ if (resolvedPath) {
+ aCache.AppendElement(std::make_pair(nsCString(resolvedPath), rdonly));
+ free(resolvedPath);
+ }
+ } while (more);
+}
+
+static void CachePathsFromFile(FileCacheT& aCache, const nsACString& aPath) {
+ // Find the new base path where that file sits in.
+ nsresult rv;
+ nsCOMPtr<nsIFile> includeFile(
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = includeFile->InitWithNativePath(aPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("Adding paths from %s to policy.",
+ PromiseFlatCString(aPath).get());
+ }
+
+ // Find the parent dir where this file sits in.
+ nsCOMPtr<nsIFile> parentDir;
+ rv = includeFile->GetParent(getter_AddRefs(parentDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ nsAutoCString parentPath;
+ rv = parentDir->GetNativePath(parentPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG("Parent path is %s", PromiseFlatCString(parentPath).get());
+ }
+ CachePathsFromFileInternal(aCache, parentPath, aPath);
+}
+
+static void AddLdconfigPaths(SandboxBroker::Policy* aPolicy) {
+ static StaticMutex sMutex;
+ StaticMutexAutoLock lock(sMutex);
+
+ static FileCacheT ldConfigCache{};
+ static bool ldConfigCachePopulated = false;
+ if (!ldConfigCachePopulated) {
+ CachePathsFromFile(ldConfigCache, "/etc/ld.so.conf"_ns);
+ ldConfigCachePopulated = true;
+ RunOnShutdown([&] {
+ ldConfigCache.Clear();
+ MOZ_ASSERT(ldConfigCache.IsEmpty(), "ldconfig cache should be empty");
+ });
+ }
+ for (const CacheE& e : ldConfigCache) {
+ aPolicy->AddDir(e.second, e.first.get());
+ }
+}
+
+static void AddLdLibraryEnvPaths(SandboxBroker::Policy* aPolicy) {
+ nsAutoCString LdLibraryEnv(PR_GetEnv("LD_LIBRARY_PATH"));
+ // The items in LD_LIBRARY_PATH can be separated by either colons or
+ // semicolons, according to the ld.so(8) man page, and empirically it
+ // seems to be allowed to mix them (i.e., a:b;c is a list with 3 elements).
+ // There is no support for escaping the delimiters, fortunately (for us).
+ LdLibraryEnv.ReplaceChar(';', ':');
+ for (const nsACString& libPath : LdLibraryEnv.Split(':')) {
+ char* resolvedPath = realpath(PromiseFlatCString(libPath).get(), nullptr);
+ if (resolvedPath) {
+ aPolicy->AddDir(rdonly, resolvedPath);
+ free(resolvedPath);
+ }
+ }
+}
+
+static void AddSharedMemoryPaths(SandboxBroker::Policy* aPolicy, pid_t aPid) {
+ std::string shmPath("/dev/shm");
+ if (base::SharedMemory::AppendPosixShmPrefix(&shmPath, aPid)) {
+ aPolicy->AddPrefix(rdwrcr, shmPath.c_str());
+ }
+}
+
+static void AddMemoryReporting(SandboxBroker::Policy* aPolicy, pid_t aPid) {
+ // Bug 1198552: memory reporting.
+ // Bug 1647957: memory reporting.
+ aPolicy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get());
+ aPolicy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get());
+}
+
+static void AddDynamicPathList(SandboxBroker::Policy* policy,
+ const char* aPathListPref, int perms) {
+ nsAutoCString pathList;
+ nsresult rv = Preferences::GetCString(aPathListPref, pathList);
+ if (NS_SUCCEEDED(rv)) {
+ for (const nsACString& path : pathList.Split(',')) {
+ nsCString trimPath(path);
+ trimPath.Trim(" ", true, true);
+ policy->AddDynamic(perms, trimPath.get());
+ }
+ }
+}
+
+static void AddX11Dependencies(SandboxBroker::Policy* policy) {
+ // Allow Primus to contact the Bumblebee daemon to manage GPU
+ // switching on NVIDIA Optimus systems.
+ const char* bumblebeeSocket = PR_GetEnv("BUMBLEBEE_SOCKET");
+ if (bumblebeeSocket == nullptr) {
+ bumblebeeSocket = "/var/run/bumblebee.socket";
+ }
+ policy->AddPath(SandboxBroker::MAY_CONNECT, bumblebeeSocket);
+
+#if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11)
+ // Allow local X11 connections, for several purposes:
+ //
+ // * for content processes to use WebGL when the browser is in headless
+ // mode, by opening the X display if/when needed
+ //
+ // * if Primus or VirtualGL is used, to contact the secondary X server
+ static const bool kIsX11 =
+ !mozilla::widget::GdkIsWaylandDisplay() && PR_GetEnv("DISPLAY");
+ if (kIsX11) {
+ policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X");
+ if (auto* const xauth = PR_GetEnv("XAUTHORITY")) {
+ policy->AddPath(rdonly, xauth);
+ } else if (auto* const home = PR_GetEnv("HOME")) {
+ // This follows the logic in libXau: append "/.Xauthority",
+ // even if $HOME ends in a slash, except in the special case
+ // where HOME=/ because POSIX allows implementations to treat
+ // an initial double slash specially.
+ nsAutoCString xauth(home);
+ if (xauth != "/"_ns) {
+ xauth.Append('/');
+ }
+ xauth.AppendLiteral(".Xauthority");
+ policy->AddPath(rdonly, xauth.get());
+ }
+ }
+#endif
+}
+
+static void AddGLDependencies(SandboxBroker::Policy* policy) {
+ // Devices
+ policy->AddDir(rdwr, "/dev/dri");
+ policy->AddFilePrefix(rdwr, "/dev", "nvidia");
+
+ // Hardware info
+ AddDriPaths(policy);
+
+ // /etc and /usr/share (glvnd, libdrm, drirc, ...?)
+ policy->AddDir(rdonly, "/etc");
+ policy->AddDir(rdonly, "/usr/share");
+ policy->AddDir(rdonly, "/usr/local/share");
+
+ // Snap puts the usual /usr/share things in a different place, and
+ // we'll fail to load the library if we don't have (at least) the
+ // glvnd config:
+ if (const char* snapDesktopDir = PR_GetEnv("SNAP_DESKTOP_RUNTIME")) {
+ nsAutoCString snapDesktopShare(snapDesktopDir);
+ snapDesktopShare.AppendLiteral("/usr/share");
+ policy->AddDir(rdonly, snapDesktopShare.get());
+ }
+
+ // Note: This function doesn't do anything about Mesa's shader
+ // cache, because the details can vary by process type, including
+ // whether caching is enabled.
+
+ // This also doesn't include permissions for connecting to a display
+ // server, because headless GL (e.g., Mesa GBM) may not need it.
+}
+
+void SandboxBrokerPolicyFactory::InitContentPolicy() {
+ const bool headless =
+ StaticPrefs::security_sandbox_content_headless_AtStartup();
+
+ // Policy entries that are the same in every process go here, and
+ // are cached over the lifetime of the factory.
+ SandboxBroker::Policy* policy = new SandboxBroker::Policy;
+ // Write permssions
+
+ // Bug 1575985: WASM library sandbox needs RW access to /dev/null
+ policy->AddPath(rdwr, "/dev/null");
+
+ if (!headless) {
+ AddGLDependencies(policy);
+ AddX11Dependencies(policy);
+ }
+
+ // Read permissions
+ policy->AddPath(rdonly, "/dev/urandom");
+ policy->AddPath(rdonly, "/dev/random");
+ policy->AddPath(rdonly, "/proc/sys/crypto/fips_enabled");
+ policy->AddPath(rdonly, "/proc/cpuinfo");
+ policy->AddPath(rdonly, "/proc/meminfo");
+ policy->AddDir(rdonly, "/sys/devices/cpu");
+ policy->AddDir(rdonly, "/sys/devices/system/cpu");
+ policy->AddDir(rdonly, "/lib");
+ policy->AddDir(rdonly, "/lib64");
+ policy->AddDir(rdonly, "/usr/lib");
+ policy->AddDir(rdonly, "/usr/lib32");
+ policy->AddDir(rdonly, "/usr/lib64");
+ policy->AddDir(rdonly, "/etc");
+ policy->AddDir(rdonly, "/usr/share");
+ policy->AddDir(rdonly, "/usr/local/share");
+ // Various places where fonts reside
+ policy->AddDir(rdonly, "/usr/X11R6/lib/X11/fonts");
+ policy->AddDir(rdonly, "/nix/store");
+ // https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/e434e680d22260f277f4a30ec4660ed32b591d16/files/fontconfig-flatpak.conf
+ policy->AddDir(rdonly, "/run/host/fonts");
+ policy->AddDir(rdonly, "/run/host/user-fonts");
+ policy->AddDir(rdonly, "/run/host/local-fonts");
+ policy->AddDir(rdonly, "/var/cache/fontconfig");
+
+ // Bug 1848615
+ policy->AddPath(rdonly, "/usr");
+ policy->AddPath(rdonly, "/nix");
+
+ AddLdconfigPaths(policy);
+ AddLdLibraryEnvPaths(policy);
+
+ if (!headless) {
+ // Bug 1385715: NVIDIA PRIME support
+ policy->AddPath(rdonly, "/proc/modules");
+ }
+
+ // XDG directories might be non existent according to specs:
+ // https://specifications.freedesktop.org/basedir-spec/0.8/ar01s04.html
+ //
+ // > If, when attempting to write a file, the destination directory is
+ // > non-existent an attempt should be made to create it with permission 0700.
+ //
+ // For that we use AddPath(, SandboxBroker::Policy::AddCondition::AddAlways).
+ //
+ // Allow access to XDG_CONFIG_HOME and XDG_CONFIG_DIRS
+ nsAutoCString xdgConfigHome(PR_GetEnv("XDG_CONFIG_HOME"));
+ if (!xdgConfigHome.IsEmpty()) { // AddPath will fail on empty strings
+ policy->AddFutureDir(rdonly, xdgConfigHome.get());
+ }
+
+ nsAutoCString xdgConfigDirs(PR_GetEnv("XDG_CONFIG_DIRS"));
+ for (const auto& path : xdgConfigDirs.Split(':')) {
+ if (!path.IsEmpty()) { // AddPath will fail on empty strings
+ policy->AddFutureDir(rdonly, PromiseFlatCString(path).get());
+ }
+ }
+
+ // Allow fonts subdir in XDG_DATA_HOME
+ nsAutoCString xdgDataHome(PR_GetEnv("XDG_DATA_HOME"));
+ if (!xdgDataHome.IsEmpty()) {
+ nsAutoCString fontPath(xdgDataHome);
+ fontPath.Append("/fonts");
+ policy->AddFutureDir(rdonly, PromiseFlatCString(fontPath).get());
+ }
+
+ // Any font subdirs in XDG_DATA_DIRS
+ nsAutoCString xdgDataDirs(PR_GetEnv("XDG_DATA_DIRS"));
+ for (const auto& path : xdgDataDirs.Split(':')) {
+ nsAutoCString fontPath(path);
+ fontPath.Append("/fonts");
+ policy->AddFutureDir(rdonly, PromiseFlatCString(fontPath).get());
+ }
+
+ // Extra configuration/cache dirs in the homedir that we want to allow read
+ // access to.
+ std::vector<const char*> extraConfDirsAllow = {
+ ".themes",
+ ".fonts",
+ ".cache/fontconfig",
+ };
+
+ // Fallback if XDG_CONFIG_HOME isn't set
+ if (xdgConfigHome.IsEmpty()) {
+ extraConfDirsAllow.emplace_back(".config");
+ }
+
+ nsCOMPtr<nsIFile> homeDir;
+ nsresult rv =
+ GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> confDir;
+
+ for (const auto& dir : extraConfDirsAllow) {
+ rv = homeDir->Clone(getter_AddRefs(confDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendRelativeNativePath(nsDependentCString(dir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+ }
+ }
+
+ // ~/.config/mozilla/ needs to be manually blocked, because the previous
+ // loop will allow for ~/.config/ access.
+ {
+ // If $XDG_CONFIG_HOME is set, we need to account for it.
+ // FIXME: Bug 1722272: Maybe this should just be handled with
+ // GetSpecialSystemDirectory(Unix_XDG_ConfigHome) ?
+ nsCOMPtr<nsIFile> confDirOrXDGConfigHomeDir;
+ if (!xdgConfigHome.IsEmpty()) {
+ rv = NS_NewNativeLocalFile(xdgConfigHome, true,
+ getter_AddRefs(confDirOrXDGConfigHomeDir));
+ // confDirOrXDGConfigHomeDir = nsIFile($XDG_CONFIG_HOME)
+ } else {
+ rv = homeDir->Clone(getter_AddRefs(confDirOrXDGConfigHomeDir));
+ if (NS_SUCCEEDED(rv)) {
+ // since we will use that later, we dont need to care about trailing
+ // slash
+ rv = confDirOrXDGConfigHomeDir->AppendNative(".config"_ns);
+ // confDirOrXDGConfigHomeDir = nsIFile($HOME/.config/)
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDirOrXDGConfigHomeDir->AppendNative("mozilla"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDirOrXDGConfigHomeDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddFutureDir(deny, tmpPath.get());
+ }
+ }
+ }
+ }
+
+ // ~/.local/share (for themes)
+ rv = homeDir->Clone(getter_AddRefs(confDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative(".local"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative("share"_ns);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+ }
+
+ // ~/.fonts.conf (Fontconfig)
+ rv = homeDir->Clone(getter_AddRefs(confDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative(".fonts.conf"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddPath(rdonly, tmpPath.get());
+ }
+ }
+ }
+
+ // .pangorc
+ rv = homeDir->Clone(getter_AddRefs(confDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative(".pangorc"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddPath(rdonly, tmpPath.get());
+ }
+ }
+ }
+ }
+
+ // Firefox binary dir.
+ // Note that unlike the previous cases, we use NS_GetSpecialDirectory
+ // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
+ // system, which may not be the case for some tests. For querying for the
+ // location of XPCOM things, we can use it anyway.
+ nsCOMPtr<nsIFile> ffDir;
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = ffDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+
+ if (!mozilla::IsPackagedBuild()) {
+ // If this is not a packaged build the resources are likely symlinks to
+ // outside the binary dir. Therefore in non-release builds we allow reads
+ // from the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run.
+ const char* developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
+ if (developer_repo_dir) {
+ policy->AddDir(rdonly, developer_repo_dir);
+ }
+ }
+
+#ifdef DEBUG
+ char* bloatLog = PR_GetEnv("XPCOM_MEM_BLOAT_LOG");
+ // XPCOM_MEM_BLOAT_LOG has the format
+ // /tmp/tmpd0YzFZ.mozrunner/runtests_leaks.log
+ // but stores into /tmp/tmpd0YzFZ.mozrunner/runtests_leaks_tab_pid3411.log
+ // So cut the .log part and whitelist the prefix.
+ if (bloatLog != nullptr) {
+ size_t bloatLen = strlen(bloatLog);
+ if (bloatLen >= 4) {
+ nsAutoCString bloatStr(bloatLog);
+ bloatStr.Truncate(bloatLen - 4);
+ policy->AddPrefix(rdwrcr, bloatStr.get());
+ }
+ }
+#endif
+
+ if (!headless) {
+ AddX11Dependencies(policy);
+ }
+
+ // Bug 1732580: when packaged as a strictly confined snap, may need
+ // read-access to configuration files under $SNAP/.
+ const char* snap = PR_GetEnv("SNAP");
+ if (snap) {
+ // When running as a snap, the directory pointed to by $SNAP is guaranteed
+ // to exist before the app is launched, but unit tests need to create it
+ // dynamically, hence the use of AddFutureDir().
+ policy->AddDir(rdonly, snap);
+ }
+
+ // Read any extra paths that will get write permissions,
+ // configured by the user or distro
+ AddDynamicPathList(policy, "security.sandbox.content.write_path_whitelist",
+ rdwr);
+
+ // Whitelisted for reading by the user/distro
+ AddDynamicPathList(policy, "security.sandbox.content.read_path_whitelist",
+ rdonly);
+
+#if defined(MOZ_CONTENT_TEMP_DIR)
+ // Add write permissions on the content process specific temporary dir.
+ nsCOMPtr<nsIFile> tmpDir;
+ rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+ getter_AddRefs(tmpDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = tmpDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdwrcr, tmpPath.get());
+ }
+ }
+#endif
+
+ // userContent.css and the extensions dir sit in the profile, which is
+ // normally blocked.
+ nsCOMPtr<nsIFile> profileDir;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> workDir;
+ rv = profileDir->Clone(getter_AddRefs(workDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = workDir->AppendNative("chrome"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = workDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+ }
+ rv = profileDir->Clone(getter_AddRefs(workDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = workDir->AppendNative("extensions"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = workDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ bool exists;
+ rv = workDir->Exists(&exists);
+ if (NS_SUCCEEDED(rv)) {
+ if (!exists) {
+ policy->AddPrefix(rdonly, tmpPath.get());
+ policy->AddPath(rdonly, tmpPath.get());
+ } else {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ const int level = GetEffectiveContentSandboxLevel();
+ bool allowPulse = false;
+ bool allowAlsa = false;
+ if (level < 4) {
+#ifdef MOZ_PULSEAUDIO
+ allowPulse = true;
+#endif
+#ifdef MOZ_ALSA
+ allowAlsa = true;
+#endif
+ }
+
+ if (allowAlsa) {
+ // Bug 1309098: ALSA support
+ policy->AddDir(rdwr, "/dev/snd");
+ }
+
+ if (allowPulse) {
+ policy->AddDir(rdwrcr, "/dev/shm");
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ if (const auto userDir = g_get_user_runtime_dir()) {
+ // Bug 1321134: DConf's single bit of shared memory
+ // The leaf filename is "user" by default, but is configurable.
+ nsPrintfCString shmPath("%s/dconf/", userDir);
+ policy->AddPrefix(rdwrcr, shmPath.get());
+ policy->AddAncestors(shmPath.get());
+ if (allowPulse) {
+ // PulseAudio, if it can't get server info from X11, will break
+ // unless it can open this directory (or create it, but in our use
+ // case we know it already exists). See bug 1335329.
+ nsPrintfCString pulsePath("%s/pulse", userDir);
+ policy->AddPath(rdonly, pulsePath.get());
+ }
+ }
+#endif // MOZ_WIDGET_GTK
+
+ if (allowPulse) {
+ // PulseAudio also needs access to read the $XAUTHORITY file (see
+ // bug 1384986 comment #1), but that's already allowed for hybrid
+ // GPU drivers (see above).
+ policy->AddPath(rdonly, "/var/lib/dbus/machine-id");
+ }
+
+ // Bug 1434711 - AMDGPU-PRO crashes if it can't read it's marketing ids
+ // and various other things
+ if (!headless && HasAtiDrivers()) {
+ policy->AddDir(rdonly, "/opt/amdgpu/share");
+ policy->AddPath(rdonly, "/sys/module/amdgpu");
+ }
+
+ mCommonContentPolicy.reset(policy);
+}
+
+UniquePtr<SandboxBroker::Policy> SandboxBrokerPolicyFactory::GetContentPolicy(
+ int aPid, bool aFileProcess) {
+ // Policy entries that vary per-process (because they depend on the
+ // pid or content subtype) are added here.
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ const int level = GetEffectiveContentSandboxLevel();
+ // The file broker is used at level 2 and up.
+ if (level <= 1) {
+ // Level 1 has been removed.
+ MOZ_ASSERT(level == 0);
+ return nullptr;
+ }
+
+ std::call_once(mContentInited, [this] { InitContentPolicy(); });
+ MOZ_ASSERT(mCommonContentPolicy);
+ UniquePtr<SandboxBroker::Policy> policy(
+ new SandboxBroker::Policy(*mCommonContentPolicy));
+
+ // No read blocking at level 2 and below.
+ // file:// processes also get global read permissions
+ if (level <= 2 || aFileProcess) {
+ policy->AddDir(rdonly, "/");
+ // Any other read-only rules will be removed as redundant by
+ // Policy::FixRecursivePermissions, so there's no need to
+ // early-return here.
+ }
+
+ // Access to /dev/shm is restricted to a per-process prefix to
+ // prevent interfering with other processes or with services outside
+ // the browser (e.g., PulseAudio).
+ AddSharedMemoryPaths(policy.get(), aPid);
+
+ // Bug 1198550: the profiler's replacement for dl_iterate_phdr
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/maps", aPid).get());
+
+ // Bug 1736040: CPU use telemetry
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/stat", aPid).get());
+
+ // Bug 1198552: memory reporting.
+ AddMemoryReporting(policy.get(), aPid);
+
+ // Bug 1384804, notably comment 15
+ // Used by libnuma, included by x265/ffmpeg, who falls back
+ // to get_mempolicy if this fails
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/status", aPid).get());
+
+ // Finalize the policy.
+ policy->FixRecursivePermissions();
+ return policy;
+}
+
+#ifdef MOZ_ENABLE_V4L2
+static void AddV4l2Dependencies(SandboxBroker::Policy* policy) {
+ // For V4L2 hardware-accelerated video decode, RDD needs access to certain
+ // /dev/video* devices but don't want to allow it access to webcams etc.
+ // So we only allow it access to M2M video devices (encoders and decoders).
+ DIR* dir = opendir("/dev");
+ if (!dir) {
+ SANDBOX_LOG("Couldn't list /dev");
+ return;
+ }
+
+ struct dirent* dir_entry;
+ while ((dir_entry = readdir(dir))) {
+ if (strncmp(dir_entry->d_name, "video", 5)) {
+ // Not a /dev/video* device, so ignore it
+ continue;
+ }
+
+ nsCString path = "/dev/"_ns;
+ path += nsDependentCString(dir_entry->d_name);
+
+ int fd = open(path.get(), O_RDWR | O_NONBLOCK, 0);
+ if (fd < 0) {
+ // Couldn't open this device, so ignore it.
+ SANDBOX_LOG("Couldn't open video device %s", path.get());
+ continue;
+ }
+
+ // Query device capabilities
+ struct v4l2_capability cap;
+ int result = ioctl(fd, VIDIOC_QUERYCAP, &cap);
+ if (result < 0) {
+ // Couldn't query capabilities of this device, so ignore it
+ SANDBOX_LOG("Couldn't query capabilities of video device %s", path.get());
+ close(fd);
+ continue;
+ }
+
+ if ((cap.device_caps & V4L2_CAP_VIDEO_M2M) ||
+ (cap.device_caps & V4L2_CAP_VIDEO_M2M_MPLANE)) {
+ // This is an M2M device (i.e. not a webcam), so allow access
+ policy->AddPath(rdwr, path.get());
+ }
+
+ close(fd);
+ }
+ closedir(dir);
+
+ // FFmpeg V4L2 needs to list /dev to find V4L2 devices.
+ policy->AddPath(rdonly, "/dev");
+}
+#endif // MOZ_ENABLE_V4L2
+
+/* static */ UniquePtr<SandboxBroker::Policy>
+SandboxBrokerPolicyFactory::GetRDDPolicy(int aPid) {
+ auto policy = MakeUnique<SandboxBroker::Policy>();
+
+ AddSharedMemoryPaths(policy.get(), aPid);
+
+ policy->AddPath(rdonly, "/dev/urandom");
+ // FIXME (bug 1662321): we should fix nsSystemInfo so that every
+ // child process doesn't need to re-read these files to get the info
+ // the parent process already has.
+ policy->AddPath(rdonly, "/proc/cpuinfo");
+ policy->AddPath(rdonly,
+ "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq");
+ policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cache/index2/size");
+ policy->AddPath(rdonly, "/sys/devices/system/cpu/cpu0/cache/index3/size");
+ policy->AddDir(rdonly, "/sys/devices/cpu");
+ policy->AddDir(rdonly, "/sys/devices/system/cpu");
+ policy->AddDir(rdonly, "/sys/devices/system/node");
+ policy->AddDir(rdonly, "/lib");
+ policy->AddDir(rdonly, "/lib64");
+ policy->AddDir(rdonly, "/usr/lib");
+ policy->AddDir(rdonly, "/usr/lib32");
+ policy->AddDir(rdonly, "/usr/lib64");
+ policy->AddDir(rdonly, "/run/opengl-driver/lib");
+ policy->AddDir(rdonly, "/nix/store");
+
+ // Bug 1647957: memory reporting.
+ AddMemoryReporting(policy.get(), aPid);
+
+ // Firefox binary dir.
+ // Note that unlike the previous cases, we use NS_GetSpecialDirectory
+ // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
+ // system, which may not be the case for some tests. For querying for the
+ // location of XPCOM things, we can use it anyway.
+ nsCOMPtr<nsIFile> ffDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = ffDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+
+ if (!mozilla::IsPackagedBuild()) {
+ // If this is not a packaged build the resources are likely symlinks to
+ // outside the binary dir. Therefore in non-release builds we allow reads
+ // from the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run.
+ const char* developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
+ if (developer_repo_dir) {
+ policy->AddDir(rdonly, developer_repo_dir);
+ }
+ }
+
+ // VA-API needs GPU access and GL context creation (but not display
+ // server access, as of bug 1769499).
+ AddGLDependencies(policy.get());
+
+ // FFmpeg and GPU drivers may need general-case library loading
+ AddLdconfigPaths(policy.get());
+ AddLdLibraryEnvPaths(policy.get());
+
+#ifdef MOZ_ENABLE_V4L2
+ AddV4l2Dependencies(policy.get());
+#endif // MOZ_ENABLE_V4L2
+
+ if (policy->IsEmpty()) {
+ policy = nullptr;
+ }
+ return policy;
+}
+
+/* static */ UniquePtr<SandboxBroker::Policy>
+SandboxBrokerPolicyFactory::GetSocketProcessPolicy(int aPid) {
+ auto policy = MakeUnique<SandboxBroker::Policy>();
+
+ policy->AddPath(rdonly, "/dev/urandom");
+ policy->AddPath(rdonly, "/dev/random");
+ policy->AddPath(rdonly, "/proc/sys/crypto/fips_enabled");
+ policy->AddPath(rdonly, "/proc/cpuinfo");
+ policy->AddPath(rdonly, "/proc/meminfo");
+ policy->AddDir(rdonly, "/sys/devices/cpu");
+ policy->AddDir(rdonly, "/sys/devices/system/cpu");
+ policy->AddDir(rdonly, "/lib");
+ policy->AddDir(rdonly, "/lib64");
+ policy->AddDir(rdonly, "/usr/lib");
+ policy->AddDir(rdonly, "/usr/lib32");
+ policy->AddDir(rdonly, "/usr/lib64");
+ policy->AddDir(rdonly, "/usr/share");
+ policy->AddDir(rdonly, "/usr/local/share");
+ policy->AddDir(rdonly, "/etc");
+
+ // glibc will try to stat64("/") while populating nsswitch database
+ // https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_database.c;h=cf0306adc47f12d9bc761ab1b013629f4482b7e6;hb=9826b03b747b841f5fc6de2054bf1ef3f5c4bdf3#l396
+ // denying will make getaddrinfo() return ENONAME
+ policy->AddDir(access, "/");
+
+ AddLdconfigPaths(policy.get());
+
+ // Socket process sandbox needs to allow shmem in order to support
+ // profiling. See Bug 1626385.
+ AddSharedMemoryPaths(policy.get(), aPid);
+
+ // Bug 1647957: memory reporting.
+ AddMemoryReporting(policy.get(), aPid);
+
+ // Firefox binary dir.
+ // Note that unlike the previous cases, we use NS_GetSpecialDirectory
+ // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
+ // system, which may not be the case for some tests. For querying for the
+ // location of XPCOM things, we can use it anyway.
+ nsCOMPtr<nsIFile> ffDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = ffDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+
+ if (policy->IsEmpty()) {
+ policy = nullptr;
+ }
+ return policy;
+}
+
+/* static */ UniquePtr<SandboxBroker::Policy>
+SandboxBrokerPolicyFactory::GetUtilityProcessPolicy(int aPid) {
+ auto policy = MakeUnique<SandboxBroker::Policy>();
+
+ policy->AddPath(rdonly, "/dev/urandom");
+ policy->AddPath(rdonly, "/proc/cpuinfo");
+ policy->AddPath(rdonly, "/proc/meminfo");
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/exe", aPid).get());
+ policy->AddDir(rdonly, "/sys/devices/cpu");
+ policy->AddDir(rdonly, "/sys/devices/system/cpu");
+ policy->AddDir(rdonly, "/lib");
+ policy->AddDir(rdonly, "/lib64");
+ policy->AddDir(rdonly, "/usr/lib");
+ policy->AddDir(rdonly, "/usr/lib32");
+ policy->AddDir(rdonly, "/usr/lib64");
+ policy->AddDir(rdonly, "/usr/share");
+ policy->AddDir(rdonly, "/usr/local/share");
+ policy->AddDir(rdonly, "/etc");
+ // Required to make sure ffmpeg loads properly, this is already existing on
+ // Content and RDD
+ policy->AddDir(rdonly, "/nix/store");
+
+ // glibc will try to stat64("/") while populating nsswitch database
+ // https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_database.c;h=cf0306adc47f12d9bc761ab1b013629f4482b7e6;hb=9826b03b747b841f5fc6de2054bf1ef3f5c4bdf3#l396
+ // denying will make getaddrinfo() return ENONAME
+ policy->AddDir(access, "/");
+
+ AddLdconfigPaths(policy.get());
+ AddLdLibraryEnvPaths(policy.get());
+
+ // Utility process sandbox needs to allow shmem in order to support
+ // profiling. See Bug 1626385.
+ AddSharedMemoryPaths(policy.get(), aPid);
+
+ // Bug 1647957: memory reporting.
+ AddMemoryReporting(policy.get(), aPid);
+
+ // Firefox binary dir.
+ // Note that unlike the previous cases, we use NS_GetSpecialDirectory
+ // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
+ // system, which may not be the case for some tests. For querying for the
+ // location of XPCOM things, we can use it anyway.
+ nsCOMPtr<nsIFile> ffDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = ffDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+
+ if (policy->IsEmpty()) {
+ policy = nullptr;
+ }
+ return policy;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h
new file mode 100644
index 0000000000..aff3487ef9
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h
@@ -0,0 +1,36 @@
+/* -*- 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"
+
+#include <mutex>
+
+namespace mozilla {
+
+class SandboxBrokerPolicyFactory {
+ public:
+ SandboxBrokerPolicyFactory() = default;
+
+ UniquePtr<SandboxBroker::Policy> GetContentPolicy(int aPid,
+ bool aFileProcess);
+
+ static UniquePtr<SandboxBroker::Policy> GetRDDPolicy(int aPid);
+ static UniquePtr<SandboxBroker::Policy> GetSocketProcessPolicy(int aPid);
+ static UniquePtr<SandboxBroker::Policy> GetUtilityProcessPolicy(int aPid);
+
+ private:
+ UniquePtr<const SandboxBroker::Policy> mCommonContentPolicy;
+ std::once_flag mContentInited;
+
+ void InitContentPolicy();
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxBrokerPolicyFactory_h
diff --git a/security/sandbox/linux/broker/SandboxBrokerRealpath.cpp b/security/sandbox/linux/broker/SandboxBrokerRealpath.cpp
new file mode 100644
index 0000000000..b129af57fd
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerRealpath.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The names of the authors may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This is originally from:
+ * android-n-mr2-preview-1-303-gccec0f4c1
+ * libc/upstream-freebsd/lib/libc/stdlib/realpath.c
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)realpath.c 8.1 (Berkeley) 2/16/94";
+#endif /* LIBC_SCCS and not lint */
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "base/string_util.h"
+#include "SandboxBroker.h"
+
+// Original copy in, but not usable from here:
+// toolkit/crashreporter/google-breakpad/src/common/linux/linux_libc_support.cc
+static size_t my_strlcat(char* s1, const char* s2, size_t len) {
+ size_t pos1 = 0;
+
+ while (pos1 < len && s1[pos1] != '\0') pos1++;
+
+ if (pos1 == len) return pos1;
+
+ return pos1 + base::strlcpy(s1 + pos1, s2, len - pos1);
+}
+
+namespace mozilla {
+
+/*
+ * Original: realpath
+ * Find the real name of path, by removing all ".", ".." and symlink
+ * components. Returns (resolved) on success, or (NULL) on failure,
+ * in which case the path which caused trouble is left in (resolved).
+ * Changes:
+ * Resolve relative paths, but don't allow backing out of a symlink
+ * target. Fail with permission error if any dir is writable.
+ */
+char* SandboxBroker::SymlinkPath(const Policy* policy,
+ const char* __restrict path,
+ char* __restrict resolved, int* perms) {
+ struct stat sb;
+ char *p, *q, *s;
+ size_t left_len, resolved_len, backup_allowed;
+ unsigned symlinks;
+ int m, slen;
+ char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
+
+ if (*perms) {
+ *perms = 0;
+ }
+ if (path == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ if (path[0] == '\0') {
+ errno = ENOENT;
+ return (NULL);
+ }
+ if (resolved == NULL) {
+ resolved = (char*)malloc(PATH_MAX);
+ if (resolved == NULL) return (NULL);
+ m = 1;
+ } else
+ m = 0;
+ symlinks = 0;
+ backup_allowed = PATH_MAX;
+ if (path[0] == '/') {
+ resolved[0] = '/';
+ resolved[1] = '\0';
+ if (path[1] == '\0') return (resolved);
+ resolved_len = 1;
+ left_len = base::strlcpy(left, path + 1, sizeof(left));
+ } else {
+ if (getcwd(resolved, PATH_MAX) == NULL) {
+ if (m)
+ free(resolved);
+ else {
+ resolved[0] = '.';
+ resolved[1] = '\0';
+ }
+ return (NULL);
+ }
+ resolved_len = strlen(resolved);
+ left_len = base::strlcpy(left, path, sizeof(left));
+ }
+ if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) {
+ if (m) free(resolved);
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+
+ /*
+ * Iterate over path components in `left'.
+ */
+ while (left_len != 0) {
+ /*
+ * Extract the next path component and adjust `left'
+ * and its length.
+ */
+ p = strchr(left, '/');
+ s = p ? p : left + left_len;
+ if (s - left >= (ssize_t)sizeof(next_token)) {
+ if (m) free(resolved);
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+ memcpy(next_token, left, s - left);
+ next_token[s - left] = '\0';
+ left_len -= s - left;
+ if (p != NULL) memmove(left, s + 1, left_len + 1);
+ if (resolved[resolved_len - 1] != '/') {
+ if (resolved_len + 1 >= PATH_MAX) {
+ if (m) free(resolved);
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+ resolved[resolved_len++] = '/';
+ resolved[resolved_len] = '\0';
+ }
+ if (next_token[0] == '\0') {
+ /* Handle consequential slashes. */
+ continue;
+ } else if (strcmp(next_token, ".") == 0)
+ continue;
+ else if (strcmp(next_token, "..") == 0) {
+ /*
+ * Strip the last path component except when we have
+ * single "/"
+ */
+ if (resolved_len > 1) {
+ if (backup_allowed > 0) {
+ resolved[resolved_len - 1] = '\0';
+ q = strrchr(resolved, '/') + 1;
+ *q = '\0';
+ resolved_len = q - resolved;
+ backup_allowed--;
+ } else {
+ // Backing out past a symlink target.
+ // We don't allow this, because it can eliminate
+ // permissions we accumulated while descending.
+ if (m) free(resolved);
+ errno = EPERM;
+ return (NULL);
+ }
+ }
+ continue;
+ }
+
+ /*
+ * Append the next path component and lstat() it.
+ */
+ resolved_len = my_strlcat(resolved, next_token, PATH_MAX);
+ backup_allowed++;
+ if (resolved_len >= PATH_MAX) {
+ if (m) free(resolved);
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+ if (lstat(resolved, &sb) != 0) {
+ if (m) free(resolved);
+ return (NULL);
+ }
+ if (S_ISLNK(sb.st_mode)) {
+ if (symlinks++ > MAXSYMLINKS) {
+ if (m) free(resolved);
+ errno = ELOOP;
+ return (NULL);
+ }
+ /* Our changes start here:
+ * It's a symlink, check for write permissions on the path where
+ * it sits in, in which case we won't resolve and just error out. */
+ int link_path_perms = policy->Lookup(resolved);
+ if (link_path_perms & MAY_WRITE) {
+ if (m) free(resolved);
+ errno = EPERM;
+ return (NULL);
+ } else {
+ /* Accumulate permissions so far */
+ *perms |= link_path_perms;
+ }
+ /* Original symlink lookup code */
+ slen = readlink(resolved, symlink, sizeof(symlink) - 1);
+ if (slen < 0) {
+ if (m) free(resolved);
+ return (NULL);
+ }
+ symlink[slen] = '\0';
+ if (symlink[0] == '/') {
+ resolved[1] = 0;
+ resolved_len = 1;
+ } else if (resolved_len > 1) {
+ /* Strip the last path component. */
+ resolved[resolved_len - 1] = '\0';
+ q = strrchr(resolved, '/') + 1;
+ *q = '\0';
+ resolved_len = q - resolved;
+ }
+
+ /*
+ * If there are any path components left, then
+ * append them to symlink. The result is placed
+ * in `left'.
+ */
+ if (p != NULL) {
+ if (symlink[slen - 1] != '/') {
+ if (slen + 1 >= (ssize_t)sizeof(symlink)) {
+ if (m) free(resolved);
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+ symlink[slen] = '/';
+ symlink[slen + 1] = 0;
+ }
+ left_len = my_strlcat(symlink, left, sizeof(symlink));
+ if (left_len >= sizeof(left)) {
+ if (m) free(resolved);
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+ }
+ left_len = base::strlcpy(left, symlink, sizeof(left));
+ backup_allowed = 0;
+ } else if (!S_ISDIR(sb.st_mode) && p != NULL) {
+ if (m) free(resolved);
+ errno = ENOTDIR;
+ return (NULL);
+ }
+ }
+
+ /*
+ * Remove trailing slash except when the resolved pathname
+ * is a single "/".
+ */
+ if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
+ resolved[resolved_len - 1] = '\0';
+
+ /* Accumulate permissions. */
+ *perms |= policy->Lookup(resolved);
+
+ return (resolved);
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBrokerUtils.h b/security/sandbox/linux/broker/SandboxBrokerUtils.h
new file mode 100644
index 0000000000..89b028bece
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerUtils.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_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) || defined(__NR_fstatat64)
+typedef struct stat64 statstruct;
+# define statsyscall stat64
+# define lstatsyscall lstat64
+# define fstatsyscall fstat64
+#elif defined(__NR_stat) || defined(__NR_newfstatat)
+typedef struct stat statstruct;
+# define statsyscall stat
+# define lstatsyscall lstat
+# define fstatsyscall fstat
+#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 0000000000..4ad2dfcb3b
--- /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",
+]
+
+UNIFIED_SOURCES += [
+ "SandboxBroker.cpp",
+ "SandboxBrokerCommon.cpp",
+ "SandboxBrokerPolicyFactory.cpp",
+ "SandboxBrokerRealpath.cpp",
+]
+
+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",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["GLIB_CFLAGS"]
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+FINAL_LIBRARY = "xul"
diff --git a/security/sandbox/linux/glue/SandboxCrash.cpp b/security/sandbox/linux/glue/SandboxCrash.cpp
new file mode 100644
index 0000000000..9942925dbb
--- /dev/null
+++ b/security/sandbox/linux/glue/SandboxCrash.cpp
@@ -0,0 +1,118 @@
+/* -*- 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/StackWalk.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Exceptions.h"
+#include "nsContentUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsIException.h" // for nsIStackFrame
+#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);
+ frame->GetFilename(cx, fileName);
+ lineNumber = frame->GetLineNumber(cx);
+ funName.SetIsVoid(true);
+ frame->GetName(cx, funName);
+
+ if (!funName.IsVoid() || !fileName.IsVoid()) {
+ SANDBOX_LOG("JS frame %d: %s %s line %d", i,
+ funName.IsVoid() ? "(anonymous)"
+ : NS_ConvertUTF16toUTF8(funName).get(),
+ fileName.IsVoid() ? "(no file)"
+ : NS_ConvertUTF16toUTF8(fileName).get(),
+ lineNumber);
+ }
+
+ frame = frame->GetCaller(cx);
+ }
+}
+
+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("frame %s", buf);
+}
+
+static void SandboxLogCStack(const void* aFirstFramePC) {
+ // 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, aFirstFramePC, /* max */ 0, nullptr);
+ SANDBOX_LOG("end of stack.");
+}
+
+static void SandboxCrash(int nr, siginfo_t* info, void* void_context,
+ const void* aFirstFramePC) {
+ pid_t pid = getpid(), tid = syscall(__NR_gettid);
+ bool dumped = CrashReporter::WriteMinidumpForSigInfo(nr, info, void_context);
+
+ if (!dumped) {
+ SANDBOX_LOG(
+ "crash reporter is disabled (or failed);"
+ " trying stack trace:");
+ SandboxLogCStack(aFirstFramePC);
+ }
+
+ // 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/SandboxPrefBridge.cpp b/security/sandbox/linux/glue/SandboxPrefBridge.cpp
new file mode 100644
index 0000000000..9782e58817
--- /dev/null
+++ b/security/sandbox/linux/glue/SandboxPrefBridge.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "mozilla/Preferences.h"
+#include "mozilla/SandboxSettings.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h" // for FILE_REMOTE_TYPE
+
+namespace mozilla {
+
+/* static */ ContentProcessSandboxParams
+ContentProcessSandboxParams::ForThisProcess(
+ const Maybe<ipc::FileDescriptor>& aBroker) {
+ ContentProcessSandboxParams params;
+ params.mLevel = GetEffectiveContentSandboxLevel();
+
+ if (aBroker.isSome()) {
+ auto fd = aBroker.value().ClonePlatformHandle();
+ params.mBrokerFd = fd.release();
+ // brokerFd < 0 means to allow direct filesystem access, so
+ // make absolutely sure that doesn't happen if the parent
+ // didn't intend it.
+ MOZ_RELEASE_ASSERT(params.mBrokerFd >= 0);
+ }
+ // (Otherwise, mBrokerFd will remain -1 from the default ctor.)
+
+ auto* cc = dom::ContentChild::GetSingleton();
+ params.mFileProcess = cc->GetRemoteType() == FILE_REMOTE_TYPE;
+
+ nsAutoCString extraSyscalls;
+ nsresult rv = Preferences::GetCString(
+ "security.sandbox.content.syscall_whitelist", extraSyscalls);
+ if (NS_SUCCEEDED(rv)) {
+ for (const nsACString& callNrString : extraSyscalls.Split(',')) {
+ int callNr = PromiseFlatCString(callNrString).ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ params.mSyscallWhitelist.push_back(callNr);
+ }
+ }
+ }
+
+ return params;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/glue/moz.build b/security/sandbox/linux/glue/moz.build
new file mode 100644
index 0000000000..e44a561d69
--- /dev/null
+++ b/security/sandbox/linux/glue/moz.build
@@ -0,0 +1,35 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ "../SandboxLogging.cpp",
+ "SandboxCrash.cpp",
+ "SandboxPrefBridge.cpp",
+]
+
+SOURCES += [
+ "../../chromium/base/strings/safe_sprintf.cc",
+]
+
+# 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"]
+
+# Need this for mozilla::ipc::FileDescriptor etc.
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ # Need this for safe_sprintf.h used by SandboxLogging.h,
+ # but it has to be after ipc/chromium/src.
+ "/security/sandbox/chromium",
+ "/security/sandbox/linux",
+]
+
+USE_LIBS += [
+ "mozsandbox",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/security/sandbox/linux/gtest/TestBroker.cpp b/security/sandbox/linux/gtest/TestBroker.cpp
new file mode 100644
index 0000000000..07fcaa889a
--- /dev/null
+++ b/security/sandbox/linux/gtest/TestBroker.cpp
@@ -0,0 +1,689 @@
+/* -*- 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/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/FileDescriptor.h"
+
+namespace mozilla {
+
+class SandboxBrokerTest : public ::testing::Test {
+ 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;
+
+ 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);
+ }
+
+ void SetUp() override {
+ ipc::FileDescriptor fd;
+
+ mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd);
+ ASSERT_NE(mServer, nullptr);
+ ASSERT_TRUE(fd.IsValid());
+ auto rawFD = fd.TakePlatformHandle();
+ 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 (pthread_t& thread : threads) {
+ StartThread<C, Main>(&thread);
+ }
+ for (pthread_t thread : threads) {
+ void* retval;
+ ASSERT_EQ(pthread_join(thread, &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);
+ // This should be non-writable by the user running the test:
+ policy->AddPath(MAY_READ | MAY_WRITE, "/etc", AddAlways);
+
+ return std::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, BadFlags) {
+ int fd;
+
+ fd = Open("/dev/null", O_RDWR | O_ASYNC);
+ EXPECT_EQ(-EACCES, fd) << "O_ASYNC is banned.";
+
+ fd = Open("/dev/null", O_RDWR | 0x40000000);
+ EXPECT_EQ(-EACCES, fd) << "Unknown flag 0x40000000 is banned.";
+}
+
+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));
+
+ EXPECT_EQ(0, Access("/tmp", X_OK));
+ EXPECT_EQ(0, Access("/tmp", R_OK | X_OK));
+ EXPECT_EQ(0, Access("/tmp", R_OK | W_OK | X_OK));
+ EXPECT_EQ(0, Access("/proc/self", X_OK));
+
+ EXPECT_EQ(0, Access("/etc", R_OK | X_OK));
+ EXPECT_EQ(-EACCES, Access("/etc", W_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);
+
+#if defined(__clang__) || defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wnonnull"
+#endif
+ EXPECT_EQ(-1, statsyscall(nullptr, &realStat));
+ EXPECT_EQ(errno, EFAULT);
+
+ EXPECT_EQ(-EFAULT, Stat(nullptr, &brokeredStat));
+#if defined(__clang__) || defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ 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));
+ 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"));
+ EXPECT_EQ(-EEXIST, Mkdir("/proc/self", 0600))
+ << "Creating uncreatable dir that already exists didn't fail correctly.";
+ EXPECT_EQ(-EEXIST, Mkdir("/dev/zero", 0600))
+ << "Creating uncreatable dir over preexisting file didn't fail "
+ "correctly.";
+
+ 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
+
+// Check for fd leaks when creating/destroying a broker instance (bug
+// 1719391).
+//
+// (This uses a different test group because it doesn't use the
+// fixture class, and gtest doesn't allow mixing TEST and TEST_F in
+// the same group.)
+TEST(SandboxBrokerMisc, LeakCheck)
+{
+ // If this value is increased in the future, check that it won't
+ // cause the test to take an excessive amount of time:
+ static constexpr size_t kCycles = 4096;
+ struct rlimit oldLimit;
+ bool changedLimit = false;
+
+ // At the time of this writing, we raise the fd soft limit to 4096
+ // (or to the hard limit, if less than that), but we don't lower it
+ // if it was higher than 4096. To allow for that case, or if
+ // Gecko's preferred limit changes, the limit is reduced while this
+ // test is running:
+ ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno);
+ if (oldLimit.rlim_cur == RLIM_INFINITY ||
+ oldLimit.rlim_cur > static_cast<rlim_t>(kCycles)) {
+ struct rlimit newLimit = oldLimit;
+ newLimit.rlim_cur = static_cast<rlim_t>(kCycles);
+ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &newLimit), 0) << strerror(errno);
+ changedLimit = true;
+ }
+
+ pid_t pid = getpid();
+ for (size_t i = 0; i < kCycles; ++i) {
+ auto policy = MakeUnique<SandboxBroker::Policy>();
+ // Currently nothing in `Create` tries to check for or
+ // special-case an empty policy, but just in case:
+ policy->AddPath(SandboxBroker::MAY_READ, "/dev/null",
+ SandboxBroker::Policy::AddAlways);
+ ipc::FileDescriptor fd;
+ auto broker = SandboxBroker::Create(std::move(policy), pid, fd);
+ ASSERT_TRUE(broker);
+ ASSERT_TRUE(fd.IsValid());
+ }
+
+ if (changedLimit) {
+ ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno);
+ }
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/gtest/TestBrokerPolicy.cpp b/security/sandbox/linux/gtest/TestBrokerPolicy.cpp
new file mode 100644
index 0000000000..d881e6d290
--- /dev/null
+++ b/security/sandbox/linux/gtest/TestBrokerPolicy.cpp
@@ -0,0 +1,95 @@
+/* -*- 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/TestLogging.cpp b/security/sandbox/linux/gtest/TestLogging.cpp
new file mode 100644
index 0000000000..eb0c9b747e
--- /dev/null
+++ b/security/sandbox/linux/gtest/TestLogging.cpp
@@ -0,0 +1,56 @@
+/* -*- 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 "SandboxLogging.h"
+
+#include <errno.h>
+
+namespace mozilla {
+
+TEST(SandboxLogging, ErrorName1)
+{
+ char buf[32];
+ ssize_t n = GetLibcErrorName(buf, sizeof(buf), EINVAL);
+ EXPECT_EQ(n, 6);
+ EXPECT_STREQ(buf, "EINVAL");
+}
+
+TEST(SandboxLogging, ErrorName2)
+{
+ char buf[32];
+ ssize_t n = GetLibcErrorName(buf, sizeof(buf), EIO);
+ EXPECT_EQ(n, 3);
+ EXPECT_STREQ(buf, "EIO");
+}
+
+TEST(SandboxLogging, ErrorName3)
+{
+ char buf[32];
+ ssize_t n = GetLibcErrorName(buf, sizeof(buf), ESTALE);
+ EXPECT_EQ(n, 6);
+ EXPECT_STREQ(buf, "ESTALE");
+}
+
+TEST(SandboxLogging, ErrorNameFail)
+{
+ char buf[32];
+ ssize_t n = GetLibcErrorName(buf, sizeof(buf), 4095);
+ EXPECT_EQ(n, 10);
+ EXPECT_STREQ(buf, "error 4095");
+}
+
+TEST(SandboxLogging, ErrorNameTrunc)
+{
+ char buf[] = "++++++++";
+ ssize_t n = GetLibcErrorName(buf, 3, EINVAL);
+ EXPECT_EQ(n, 6);
+ EXPECT_STREQ(buf, "EI");
+ EXPECT_STREQ(buf + 3, "+++++");
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/gtest/moz.build b/security/sandbox/linux/gtest/moz.build
new file mode 100644
index 0000000000..f80482aee3
--- /dev/null
+++ b/security/sandbox/linux/gtest/moz.build
@@ -0,0 +1,26 @@
+# -*- 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")
+
+UNIFIED_SOURCES = [
+ "../SandboxBrokerClient.cpp",
+ "TestBroker.cpp",
+ "TestBrokerPolicy.cpp",
+ "TestLogging.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/interfaces/moz.build b/security/sandbox/linux/interfaces/moz.build
new file mode 100644
index 0000000000..4ff62e8aba
--- /dev/null
+++ b/security/sandbox/linux/interfaces/moz.build
@@ -0,0 +1,11 @@
+# -*- 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/.
+
+XPIDL_MODULE = "sandbox"
+
+XPIDL_SOURCES += [
+ "mozISandboxReporter.idl",
+]
diff --git a/security/sandbox/linux/interfaces/mozISandboxReporter.idl b/security/sandbox/linux/interfaces/mozISandboxReporter.idl
new file mode 100644
index 0000000000..82f3ab7a72
--- /dev/null
+++ b/security/sandbox/linux/interfaces/mozISandboxReporter.idl
@@ -0,0 +1,65 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+
+// A wrapper for the C++ class SandboxReport, representing one system
+// call that was rejected by policy.
+[scriptable, builtinclass, uuid(ed1e84d3-3346-42e1-b28c-e76a77f549f0)]
+interface mozISandboxReport : nsISupports
+{
+ // The timestamp relative to the time when this property is read.
+ // This is mainly meant for distinguishing recent events that might
+ // be related to an observable failure from older ones that may be
+ // unrelated, not for exact timing.
+ readonly attribute uint64_t msecAgo;
+ readonly attribute int32_t pid;
+ readonly attribute int32_t tid;
+ readonly attribute ACString procType;
+ readonly attribute uint32_t syscall;
+ // Currently numArgs is effectively a constant and indicates the
+ // maximum number of arguments possible on the platform; the actual
+ // system call may use fewer.
+ readonly attribute uint32_t numArgs;
+ // The argument values are presented as strings because JS doesn't
+ // have 64-bit integers and data would be lost on 64-bit platforms
+ // if the XPIDL type uint64_t were used. The string may be decimal
+ // or hex (with leading "0x").
+ ACString getArg(in uint32_t aIndex);
+};
+
+// A wrapper for SandboxReporter::Snapshot, representing the most
+// recent SandboxReport events. Index 0 is the first report in the
+// session, and so on; exposing the indices like this lets us see how
+// many reports have been received even though only a limited number
+// of them are stored.
+[scriptable, builtinclass, uuid(6e8ff6e5-05c9-42d3-853d-40523fd86a50)]
+interface mozISandboxReportArray : nsISupports
+{
+ readonly attribute uint64_t begin;
+ readonly attribute uint64_t end;
+ // (aIndex >= begin && aIndex < end) must be true.
+ mozISandboxReport getElement(in uint64_t aIndex);
+};
+
+// A wrapper for the SandboxReporter; use the component/contract IDs
+// below to access the SandboxReporter singleton. The component
+// constructor will fail if called in a child process.
+[scriptable, builtinclass, uuid(8535bdf7-6d9e-4853-acf9-a146449c4a3b)]
+interface mozISandboxReporter : nsISupports
+{
+ mozISandboxReportArray snapshot();
+};
+
+%{ C++
+
+#define MOZ_SANDBOX_REPORTER_CID \
+{0x5118a6f9, 0x2493, 0x4f97, {0x95, 0x52, 0x62, 0x06, 0x63, 0xe0, 0x3c, 0xb3}}
+
+#define MOZ_SANDBOX_REPORTER_CONTRACTID \
+ "@mozilla.org/sandbox/syscall-reporter;1"
+
+%}
diff --git a/security/sandbox/linux/launch/LinuxCapabilities.cpp b/security/sandbox/linux/launch/LinuxCapabilities.cpp
new file mode 100644
index 0000000000..6a1c73ed37
--- /dev/null
+++ b/security/sandbox/linux/launch/LinuxCapabilities.cpp
@@ -0,0 +1,26 @@
+/* -*- 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/launch/LinuxCapabilities.h b/security/sandbox/linux/launch/LinuxCapabilities.h
new file mode 100644
index 0000000000..c8db719811
--- /dev/null
+++ b/security/sandbox/linux/launch/LinuxCapabilities.h
@@ -0,0 +1,122 @@
+/* -*- 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) = default;
+
+ 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/launch/SandboxLaunch.cpp b/security/sandbox/linux/launch/SandboxLaunch.cpp
new file mode 100644
index 0000000000..bec94f3c4c
--- /dev/null
+++ b/security/sandbox/linux/launch/SandboxLaunch.cpp
@@ -0,0 +1,675 @@
+/* -*- 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 "SandboxLaunch.h"
+
+#include <fcntl.h>
+#include <sched.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <utility>
+
+#include "LinuxCapabilities.h"
+#include "LinuxSched.h"
+#include "SandboxChrootProto.h"
+#include "SandboxInfo.h"
+#include "SandboxLogging.h"
+#include "base/eintr_wrapper.h"
+#include "base/strings/safe_sprintf.h"
+#include "mozilla/Array.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SandboxReporter.h"
+#include "mozilla/SandboxSettings.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsIGfxInfo.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "prenv.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+#ifdef MOZ_X11
+# ifndef MOZ_WIDGET_GTK
+# error "Unknown toolkit"
+# endif
+# include "mozilla/WidgetUtilsGtk.h"
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+# include "X11UndefineNone.h"
+# include "gfxPlatform.h"
+#endif
+
+namespace mozilla {
+
+// Returns true if graphics will work from a content process
+// started in a new network namespace. Specifically, named
+// Unix-domain sockets will work, but TCP/IP will not, even if it's a
+// connection to localhost: the child process has its own private
+// loopback interface.
+//
+// (Longer-term we intend to either proxy or remove X11 access from
+// content processes, at which point this will stop being an issue.)
+static bool IsGraphicsOkWithoutNetwork() {
+ // For X11, check whether the parent's connection is a Unix-domain
+ // socket. This is done instead of trying to parse the display name
+ // because an empty hostname (e.g., ":0") will fall back to TCP in
+ // case of failure to connect using Unix-domain sockets.
+#ifdef MOZ_X11
+ // First, ensure that the parent process's graphics are initialized.
+ DebugOnly<gfxPlatform*> gfxPlatform = gfxPlatform::GetPlatform();
+
+ const auto display = gdk_display_get_default();
+ if (!display) {
+ // In this case, the browser is headless, but WebGL could still
+ // try to use X11. However, WebGL isn't supported with remote
+ // X11, and in any case these connections are made after sandbox
+ // startup (lazily when WebGL is used), so they aren't being done
+ // directly by the process anyway. (For local X11, they're
+ // brokered.)
+ MOZ_ASSERT(gfxPlatform->IsHeadless());
+ return true;
+ }
+ if (mozilla::widget::GdkIsX11Display(display)) {
+ const int xSocketFd = ConnectionNumber(GDK_DISPLAY_XDISPLAY(display));
+ if (NS_WARN_IF(xSocketFd < 0)) {
+ return false;
+ }
+
+ int domain;
+ socklen_t optlen = static_cast<socklen_t>(sizeof(domain));
+ int rv = getsockopt(xSocketFd, SOL_SOCKET, SO_DOMAIN, &domain, &optlen);
+ if (NS_WARN_IF(rv != 0)) {
+ return false;
+ }
+ MOZ_RELEASE_ASSERT(static_cast<size_t>(optlen) == sizeof(domain));
+ if (domain != AF_LOCAL) {
+ return false;
+ }
+ // There's one more complication: Xorg listens on named sockets
+ // (actual filesystem nodes) as well as abstract addresses (opaque
+ // octet strings scoped to the network namespace; this is a Linux
+ // extension).
+ //
+ // Inside a container environment (e.g., when running as a Snap
+ // package), it's possible that only the abstract addresses are
+ // accessible. In that case, the display must be considered
+ // remote. See also bug 1450740.
+ //
+ // Unfortunately, the Xorg client libraries prefer the abstract
+ // addresses, so this isn't directly detectable by inspecting the
+ // parent process's socket. Instead, parse the DISPLAY env var
+ // (which was updated if necessary in nsAppRunner.cpp) to get the
+ // display number and construct the socket path, falling back to
+ // testing the directory in case that doesn't work. (See bug
+ // 1565972 and bug 1559368 for cases where we need to test the
+ // specific socket.)
+ const char* const displayStr = PR_GetEnv("DISPLAY");
+ nsAutoCString socketPath("/tmp/.X11-unix");
+ int accessFlags = X_OK;
+ int displayNum;
+ // sscanf ignores trailing text, so display names with a screen
+ // number (e.g., ":0.2") will parse correctly.
+ if (displayStr && (sscanf(displayStr, ":%d", &displayNum) == 1 ||
+ sscanf(displayStr, "unix:%d", &displayNum) == 1)) {
+ socketPath.AppendPrintf("/X%d", displayNum);
+ accessFlags = R_OK | W_OK;
+ }
+ if (access(socketPath.get(), accessFlags) != 0) {
+ SANDBOX_LOG_ERRNO(
+ "%s is inaccessible; can't isolate network namespace in"
+ " content processes",
+ socketPath.get());
+ return false;
+ }
+ }
+#endif
+
+ // Assume that other backends (e.g., Wayland) will not use the
+ // network namespace.
+ return true;
+}
+
+bool HasAtiDrivers() {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ nsAutoString vendorID;
+ static const Array<nsresult (nsIGfxInfo::*)(nsAString&), 2> kMethods = {
+ &nsIGfxInfo::GetAdapterVendorID,
+ &nsIGfxInfo::GetAdapterVendorID2,
+ };
+ for (const auto method : kMethods) {
+ if (NS_SUCCEEDED((gfxInfo->*method)(vendorID))) {
+ // This test is based on telemetry data. The proprietary ATI
+ // drivers seem to use this vendor string, including for some
+ // newer devices that have AMD branding in the device name, such
+ // as those using AMDGPU-PRO drivers.
+ // The open-source drivers integrated into Mesa appear to use
+ // the vendor ID "X.Org" instead.
+ if (vendorID.EqualsLiteral("ATI Technologies Inc.")) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// Content processes may need direct access to SysV IPC in certain
+// uncommon use cases.
+static bool ContentNeedsSysVIPC() {
+ // The ALSA dmix plugin uses SysV semaphores and shared memory to
+ // coordinate software mixing.
+#ifdef MOZ_ALSA
+ if (!StaticPrefs::media_cubeb_sandbox()) {
+ return true;
+ }
+#endif
+
+ if (!StaticPrefs::security_sandbox_content_headless_AtStartup()) {
+ // Bug 1438391: VirtualGL uses SysV shm for images and configuration.
+ if (PR_GetEnv("VGL_ISACTIVE") != nullptr) {
+ return true;
+ }
+
+ // The fglrx (ATI Catalyst) GPU drivers use SysV IPC.
+ if (HasAtiDrivers()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void PreloadSandboxLib(base::environment_map* aEnv) {
+ // Preload libmozsandbox.so so that sandbox-related interpositions
+ // can be defined there instead of in the executable.
+ // (This could be made conditional on intent to use sandboxing, but
+ // it's harmless for non-sandboxed processes.)
+ nsAutoCString preload;
+ // Prepend this, because people can and do preload libpthread.
+ // (See bug 1222500.)
+ preload.AssignLiteral("libmozsandbox.so");
+ if (const char* oldPreload = PR_GetEnv("LD_PRELOAD")) {
+ // Doesn't matter if oldPreload is ""; extra separators are ignored.
+ preload.Append(' ');
+ preload.Append(oldPreload);
+ (*aEnv)["MOZ_ORIG_LD_PRELOAD"] = oldPreload;
+ }
+ MOZ_ASSERT(aEnv->count("LD_PRELOAD") == 0);
+ (*aEnv)["LD_PRELOAD"] = preload.get();
+}
+
+static void AttachSandboxReporter(base::file_handle_mapping_vector* aFdMap) {
+ int srcFd, dstFd;
+ SandboxReporter::Singleton()->GetClientFileDescriptorMapping(&srcFd, &dstFd);
+ aFdMap->push_back({srcFd, dstFd});
+}
+
+static int GetEffectiveSandboxLevel(GeckoProcessType aType,
+ ipc::SandboxingKind aKind) {
+ auto info = SandboxInfo::Get();
+ switch (aType) {
+#ifdef MOZ_ENABLE_FORKSERVER
+ // With this env MOZ_SANDBOXED will be set, and mozsandbox will
+ // be preloaded for the fork server. Sandboxed child processes
+ // rely on wrappers defined by mozsandbox to work properly.
+ case GeckoProcessType_ForkServer:
+ return 1;
+ break;
+#endif
+ case GeckoProcessType_Content:
+ // GetEffectiveContentSandboxLevel is main-thread-only due to prefs.
+ MOZ_ASSERT(NS_IsMainThread());
+ if (info.Test(SandboxInfo::kEnabledForContent)) {
+ return GetEffectiveContentSandboxLevel();
+ }
+ return 0;
+ case GeckoProcessType_GMPlugin:
+ if (info.Test(SandboxInfo::kEnabledForMedia)) {
+ return 1;
+ }
+ return 0;
+ case GeckoProcessType_RDD:
+ return PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX") == nullptr ? 1 : 0;
+ case GeckoProcessType_Socket:
+ // GetEffectiveSocketProcessSandboxLevel is main-thread-only due to prefs.
+ MOZ_ASSERT(NS_IsMainThread());
+ return GetEffectiveSocketProcessSandboxLevel();
+ case GeckoProcessType_Utility:
+ return IsUtilitySandboxEnabled(aKind);
+ default:
+ return 0;
+ }
+}
+
+// static
+void SandboxLaunch::Configure(GeckoProcessType aType, SandboxingKind aKind,
+ LaunchOptions* aOptions) {
+ MOZ_ASSERT(aOptions->fork_flags == 0 && !aOptions->sandbox_chroot);
+ auto info = SandboxInfo::Get();
+
+ // We won't try any kind of sandboxing without seccomp-bpf.
+ if (!info.Test(SandboxInfo::kHasSeccompBPF)) {
+ return;
+ }
+
+ // Check prefs (and env vars) controlling sandbox use.
+ int level = GetEffectiveSandboxLevel(aType, aKind);
+ if (level == 0) {
+ return;
+ }
+
+ // At this point, we know we'll be using sandboxing; generic
+ // sandboxing support goes here. The MOZ_SANDBOXED env var tells
+ // the child process whether this is the case.
+ aOptions->env_map["MOZ_SANDBOXED"] = "1";
+ PreloadSandboxLib(&aOptions->env_map);
+ AttachSandboxReporter(&aOptions->fds_to_remap);
+
+ bool canChroot = false;
+ int flags = 0;
+
+ if (aType == GeckoProcessType_Content && level >= 1) {
+ static const bool needSysV = ContentNeedsSysVIPC();
+ if (needSysV) {
+ // Tell the child process so it can adjust its seccomp-bpf
+ // policy.
+ aOptions->env_map["MOZ_SANDBOX_ALLOW_SYSV"] = "1";
+ } else {
+ flags |= CLONE_NEWIPC;
+ }
+
+ if (StaticPrefs::security_sandbox_content_headless_AtStartup()) {
+ aOptions->env_map["MOZ_HEADLESS"] = "1";
+ }
+ }
+
+ // Anything below this requires unprivileged user namespaces.
+ if (!info.Test(SandboxInfo::kHasUserNamespaces)) {
+ return;
+ }
+
+ switch (aType) {
+ case GeckoProcessType_Socket:
+ if (level >= 1) {
+ canChroot = true;
+ flags |= CLONE_NEWIPC;
+ }
+ break;
+ case GeckoProcessType_GMPlugin:
+ case GeckoProcessType_RDD:
+ if (level >= 1) {
+ canChroot = true;
+ // Can't use CLONE_NEWIPC because of intel-media-driver.
+ flags |= CLONE_NEWNET;
+ }
+ break;
+ case GeckoProcessType_Content:
+ if (level >= 4) {
+ canChroot = true;
+
+ // Unshare network namespace if allowed by graphics; see
+ // function definition above for details. (The display
+ // local-ness is cached because it won't change.)
+ static const bool canCloneNet =
+ StaticPrefs::security_sandbox_content_headless_AtStartup() ||
+ (IsGraphicsOkWithoutNetwork() &&
+ !PR_GetEnv("RENDERDOC_CAPTUREOPTS"));
+
+ if (canCloneNet) {
+ flags |= CLONE_NEWNET;
+ }
+ }
+ // Hidden pref to allow testing user namespaces separately, even
+ // if there's nothing that would require them.
+ if (Preferences::GetBool("security.sandbox.content.force-namespace",
+ false)) {
+ flags |= CLONE_NEWUSER;
+ }
+ break;
+ default:
+ // Nothing yet.
+ break;
+ }
+
+ if (canChroot || flags != 0) {
+ flags |= CLONE_NEWUSER;
+ }
+
+ aOptions->env_map[kSandboxChrootEnvFlag] = std::to_string(canChroot ? 1 : 0);
+
+ aOptions->sandbox_chroot = canChroot;
+ aOptions->fork_flags = flags;
+}
+
+SandboxLaunch::SandboxLaunch()
+ : mFlags(0), mChrootServer(-1), mChrootClient(-1) {}
+
+SandboxLaunch::~SandboxLaunch() {
+ if (mChrootClient >= 0) {
+ close(mChrootClient);
+ }
+ if (mChrootServer >= 0) {
+ close(mChrootServer);
+ }
+}
+
+bool SandboxLaunch::Prepare(LaunchOptions* aOptions) {
+ MOZ_ASSERT(mChrootClient < 0 && mChrootServer < 0);
+
+ mFlags = aOptions->fork_flags;
+
+ // Create the socket for communication between the child process and
+ // the chroot helper process. The client end is passed to the child
+ // via `fds_to_remap` and the server end is inherited and used in
+ // `StartChrootServer`.
+ if (aOptions->sandbox_chroot) {
+ int fds[2];
+ int rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
+ if (rv != 0) {
+ SANDBOX_LOG_ERRNO("socketpair");
+ return false;
+ }
+ mChrootClient = fds[0];
+ mChrootServer = fds[1];
+
+ aOptions->fds_to_remap.push_back({mChrootClient, kSandboxChrootClientFd});
+ }
+
+ return true;
+}
+
+static void BlockAllSignals(sigset_t* aOldSigs) {
+ sigset_t allSigs;
+ int rv = sigfillset(&allSigs);
+ MOZ_RELEASE_ASSERT(rv == 0);
+ rv = pthread_sigmask(SIG_BLOCK, &allSigs, aOldSigs);
+ if (rv != 0) {
+ SANDBOX_LOG_WITH_ERROR(rv, "pthread_sigmask (block all)");
+ MOZ_CRASH("pthread_sigmask");
+ }
+}
+
+static void RestoreSignals(const sigset_t* aOldSigs) {
+ // Assuming that pthread_sigmask is a thin layer over rt_sigprocmask
+ // and doesn't try to touch TLS, which may be in an "interesting"
+ // state right now:
+ int rv = pthread_sigmask(SIG_SETMASK, aOldSigs, nullptr);
+ if (rv != 0) {
+ SANDBOX_LOG_WITH_ERROR(rv, "pthread_sigmask (restore)");
+ MOZ_CRASH("pthread_sigmask");
+ }
+}
+
+static bool IsSignalIgnored(int aSig) {
+ struct sigaction sa {};
+
+ if (sigaction(aSig, nullptr, &sa) != 0) {
+ if (errno != EINVAL) {
+ SANDBOX_LOG_ERRNO("sigaction(%d)", aSig);
+ }
+ return false;
+ }
+ return sa.sa_handler == SIG_IGN;
+}
+
+static void ResetSignalHandlers() {
+ for (int signum = 1; signum <= SIGRTMAX; ++signum) {
+ if (IsSignalIgnored(signum)) {
+ continue;
+ }
+ if (signal(signum, SIG_DFL) == SIG_ERR) {
+ MOZ_DIAGNOSTIC_ASSERT(errno == EINVAL);
+ }
+ }
+}
+
+namespace {
+
+// The libc clone() routine insists on calling a provided function on
+// a new stack, even if the address space isn't shared and it would be
+// safe to expose the underlying system call's fork()-like behavior.
+// So, we work around this by longjmp()ing back onto the original stack;
+// this technique is also used by Chromium.
+//
+// In theory, the clone syscall could be used directly if we ensure
+// that functions like raise() are never used in the child, including
+// by inherited signal handlers, but the longjmp approach isn't much
+// extra code and avoids a class of potential bugs.
+static int CloneCallee(void* aPtr) {
+ auto ctxPtr = reinterpret_cast<jmp_buf*>(aPtr);
+ longjmp(*ctxPtr, 1);
+ MOZ_CRASH("unreachable");
+ return 1;
+}
+
+// According to the Chromium developers, builds with FORTIFY_SOURCE
+// require that longjump move the stack pointer towards the root
+// function of the call stack. Therefore, we must ensure that the
+// clone callee stack is leafward of the stack pointer captured in
+// setjmp() below by using this no-inline helper function.
+//
+// ASan apparently also causes problems, by the combination of
+// allocating the large stack-allocated buffer outside of the actual
+// stack and then assuming that longjmp is used only to unwind a
+// stack, not switch stacks.
+//
+// Valgrind would disapprove of using clone() without CLONE_VM;
+// Chromium uses the raw syscall as a workaround in that case, but
+// we don't currently support sandboxing under valgrind.
+MOZ_NEVER_INLINE MOZ_ASAN_IGNORE static pid_t DoClone(int aFlags,
+ jmp_buf* aCtx) {
+ static constexpr size_t kStackAlignment = 16;
+ uint8_t miniStack[4096] __attribute__((aligned(kStackAlignment)));
+#ifdef __hppa__
+ void* stackPtr = miniStack;
+#else
+ void* stackPtr = ArrayEnd(miniStack);
+#endif
+ return clone(CloneCallee, stackPtr, aFlags, aCtx);
+}
+
+} // namespace
+
+// Similar to fork(), but allows passing flags to clone() and does not
+// run pthread_atfork hooks.
+static pid_t ForkWithFlags(int aFlags) {
+ // Don't allow flags that would share the address space, or
+ // require clone() arguments we're not passing:
+ static const int kBadFlags = CLONE_VM | CLONE_VFORK | CLONE_SETTLS |
+ CLONE_PARENT_SETTID | CLONE_CHILD_SETTID |
+ CLONE_CHILD_CLEARTID;
+ MOZ_RELEASE_ASSERT((aFlags & kBadFlags) == 0);
+
+ // Block signals due to small stack in DoClone.
+ sigset_t oldSigs;
+ BlockAllSignals(&oldSigs);
+
+ int ret = 0;
+ jmp_buf ctx;
+ if (setjmp(ctx) == 0) {
+ // In the parent and just called setjmp:
+ ret = DoClone(aFlags | SIGCHLD, &ctx);
+ }
+ RestoreSignals(&oldSigs);
+ // In the child and have longjmp'ed:
+ return ret;
+}
+
+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;
+}
+
+// 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 currently being
+// used, but could be useful), and leaving the ids unchanged is
+// likely to minimize unexpected side-effects.
+static void ConfigureUserNamespace(uid_t uid, gid_t gid) {
+ using base::strings::SafeSPrintf;
+ char buf[sizeof("18446744073709551615 18446744073709551615 1")];
+ size_t len;
+
+ len = static_cast<size_t>(SafeSPrintf(buf, "%d %d 1", uid, uid));
+ MOZ_RELEASE_ASSERT(len < sizeof(buf));
+ if (!WriteStringToFile("/proc/self/uid_map", buf, len)) {
+ MOZ_CRASH("Failed to write /proc/self/uid_map");
+ }
+
+ // 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.
+ Unused << WriteStringToFile("/proc/self/setgroups", "deny", 4);
+
+ len = static_cast<size_t>(SafeSPrintf(buf, "%d %d 1", gid, gid));
+ MOZ_RELEASE_ASSERT(len < sizeof(buf));
+ if (!WriteStringToFile("/proc/self/gid_map", buf, len)) {
+ MOZ_CRASH("Failed to write /proc/self/gid_map");
+ }
+}
+
+static void DropAllCaps() {
+ if (!LinuxCapabilities().SetCurrent()) {
+ SANDBOX_LOG_ERRNO("capset (drop all)");
+ }
+}
+
+pid_t SandboxLaunch::Fork() {
+ if (mFlags == 0) {
+ MOZ_ASSERT(mChrootServer < 0);
+ return fork();
+ }
+
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+
+ // Block signals so that the handlers can be safely reset in the
+ // child process without races, and so that repeated SIGPROF from
+ // the profiler won't prevent clone() from making progress. (The
+ // profiler uses pthread_atfork to do that, but ForkWithFlags
+ // can't run atfork hooks.)
+ sigset_t oldSigs;
+ BlockAllSignals(&oldSigs);
+ pid_t pid = ForkWithFlags(mFlags);
+ if (pid != 0) {
+ RestoreSignals(&oldSigs);
+ return pid;
+ }
+
+ // WARNING: all code from this point on (and in StartChrootServer)
+ // must be async signal safe. In particular, it cannot do anything
+ // that could allocate heap memory or use mutexes.
+ prctl(PR_SET_NAME, "Sandbox Forked");
+
+ // Clear signal handlers in the child, under the assumption that any
+ // actions they would take (running the crash reporter, manipulating
+ // the Gecko profile, etc.) wouldn't work correctly in the child.
+ ResetSignalHandlers();
+ RestoreSignals(&oldSigs);
+ ConfigureUserNamespace(uid, gid);
+
+ if (mChrootServer >= 0) {
+ StartChrootServer();
+ // Don't close the client fd when this object is destroyed. At
+ // this point we're in the child process proper, so it's "owned"
+ // by the FileDescriptorShuffle / CloseSuperfluous code (i.e.,
+ // that's what will consume it and close it).
+ mChrootClient = -1;
+ }
+
+ // execve() will drop capabilities, but the fork server case doesn't
+ // exec so we need to do this directly. (Also, it's a good idea to
+ // follow the principle of least privilege even when not strictly
+ // necessary.)
+ //
+ // Note that, while capabilities within an unprivileged user
+ // namespace are constrained in theory, in practice they expose a
+ // lot of attack surface and there have been exploitable kernel bugs
+ // related to that in the past, so we really want to drop them
+ // before doing anything that needs sandboxing.
+ DropAllCaps();
+ return 0;
+}
+
+void SandboxLaunch::StartChrootServer() {
+ // Run the rest of this function in a separate process that can
+ // chroot() on behalf of this process after it's sandboxed.
+ pid_t pid = ForkWithFlags(CLONE_FS);
+ if (pid < 0) {
+ MOZ_CRASH("failed to clone chroot helper process");
+ }
+ if (pid > 0) {
+ return;
+ }
+ prctl(PR_SET_NAME, "Chroot Helper");
+
+ LinuxCapabilities caps;
+ caps.Effective(CAP_SYS_CHROOT) = true;
+ if (!caps.SetCurrent()) {
+ SANDBOX_LOG_ERRNO("capset (chroot helper)");
+ MOZ_DIAGNOSTIC_ASSERT(false);
+ }
+
+ base::CloseSuperfluousFds(this, [](void* aCtx, int aFd) {
+ return aFd == static_cast<decltype(this)>(aCtx)->mChrootServer;
+ });
+
+ char msg;
+ ssize_t msgLen = HANDLE_EINTR(read(mChrootServer, &msg, 1));
+ if (msgLen == 0) {
+ // Process exited before chrooting (or chose not to chroot?).
+ _exit(0);
+ }
+ MOZ_RELEASE_ASSERT(msgLen == 1);
+ MOZ_RELEASE_ASSERT(msg == kSandboxChrootRequest);
+
+ // This chroots both processes to this process's procfs fdinfo
+ // directory, which becomes empty and unlinked when this process
+ // exits at the end of this function, and which is always
+ // unwriteable.
+ int rv = chroot("/proc/self/fdinfo");
+ MOZ_RELEASE_ASSERT(rv == 0);
+
+ // Drop CAP_SYS_CHROOT ASAP. This must happen before responding;
+ // the main child won't be able to waitpid(), so it could start
+ // handling hostile content before this process finishes exiting.
+ DropAllCaps();
+
+ // The working directory still grant access to the real filesystem;
+ // remove that. (Note: if the process can obtain directory fds, for
+ // example via SandboxBroker, it must be blocked from using fchdir.)
+ rv = chdir("/");
+ MOZ_RELEASE_ASSERT(rv == 0);
+
+ msg = kSandboxChrootResponse;
+ msgLen = HANDLE_EINTR(write(mChrootServer, &msg, 1));
+ MOZ_RELEASE_ASSERT(msgLen == 1);
+ _exit(0);
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/launch/SandboxLaunch.h b/security/sandbox/linux/launch/SandboxLaunch.h
new file mode 100644
index 0000000000..988709dcdb
--- /dev/null
+++ b/security/sandbox/linux/launch/SandboxLaunch.h
@@ -0,0 +1,71 @@
+/* -*- 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_SandboxLaunch_h
+#define mozilla_SandboxLaunch_h
+
+#include "base/process_util.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "nsXULAppAPI.h"
+#include <vector>
+
+namespace mozilla {
+
+class SandboxLaunch final {
+ public:
+ SandboxLaunch();
+ ~SandboxLaunch();
+
+ SandboxLaunch(const SandboxLaunch&) = delete;
+ SandboxLaunch& operator=(const SandboxLaunch&) = delete;
+
+ using LaunchOptions = base::LaunchOptions;
+ using SandboxingKind = ipc::SandboxingKind;
+
+ // Decide what sandboxing features will be used for a process, and
+ // modify `*aOptions` accordingly. This does not allocate fds or
+ // other OS resources (other than memory for strings).
+ //
+ // This is meant to be called in the parent process (even if the
+ // fork server will be used), and if `aType` is Content then it must
+ // be called on the main thread in order to access prefs.
+ static void Configure(GeckoProcessType aType, SandboxingKind aKind,
+ LaunchOptions* aOptions);
+
+ // Finish setting up for process launch, based on the information
+ // from `Configure(...)`. Called in the process that will do the
+ // launch (fork server if applicable, otherwise parent), and before
+ // calling `FileDescriptorShuffle::Init`.
+ //
+ // This can allocate fds (owned by `*this`) and modify
+ // `aOptions->fds_to_remap`, but does not access the
+ // environment-related fields of `*aOptions`.
+ bool Prepare(LaunchOptions* aOptions);
+
+ // Launch the child process, similarly to `::fork()`; called after
+ // `Configure` and `Prepare`.
+ //
+ // If launch-time sandboxing features are used, `pthread_atfork`
+ // hooks are not currently supported in that case, and signal
+ // handlers are reset in the child process. If sandboxing is not
+ // used, this is equivalent to `::fork()`.
+ pid_t Fork();
+
+ private:
+ int mFlags;
+ int mChrootServer;
+ int mChrootClient;
+
+ void StartChrootServer();
+};
+
+// This doesn't really belong in this header but it's used in both
+// SandboxLaunch and SandboxBrokerPolicyFactory.
+bool HasAtiDrivers();
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxLaunch_h
diff --git a/security/sandbox/linux/launch/moz.build b/security/sandbox/linux/launch/moz.build
new file mode 100644
index 0000000000..09f483f519
--- /dev/null
+++ b/security/sandbox/linux/launch/moz.build
@@ -0,0 +1,33 @@
+# -*- 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 += [
+ "SandboxLaunch.h",
+]
+
+UNIFIED_SOURCES += [
+ "LinuxCapabilities.cpp",
+ "SandboxLaunch.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ # Need this for safe_sprintf.h used by SandboxLogging.h,
+ # but it has to be after ipc/chromium/src.
+ "/security/sandbox/chromium",
+ "/security/sandbox/linux",
+]
+
+USE_LIBS += [
+ "mozsandbox",
+]
+
+# For the X11 socket domain inspection in SandboxLaunch:
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+FINAL_LIBRARY = "xul"
diff --git a/security/sandbox/linux/moz.build b/security/sandbox/linux/moz.build
new file mode 100644
index 0000000000..cbb99e514c
--- /dev/null
+++ b/security/sandbox/linux/moz.build
@@ -0,0 +1,139 @@
+# -*- 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",
+ ]
+
+USE_LIBS += [
+ # For PR_GetEnv
+ "nspr",
+]
+
+EXPORTS.mozilla += [
+ "Sandbox.h",
+ "SandboxInfo.h",
+]
+
+UNIFIED_SOURCES += [
+ "../chromium-shim/base/logging.cpp",
+ "../chromium-shim/base/threading/platform_thread_linux.cpp",
+ "../chromium/base/at_exit.cc",
+ "../chromium/base/callback_internal.cc",
+ "../chromium/base/lazy_instance_helpers.cc",
+ "../chromium/base/location.cc",
+ "../chromium/base/memory/ref_counted.cc",
+ "../chromium/base/posix/can_lower_nice_to.cc",
+ "../chromium/base/posix/safe_strerror.cc",
+ "../chromium/base/strings/string16.cc",
+ "../chromium/base/strings/string_number_conversions.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/threading/platform_thread.cc",
+ "../chromium/base/threading/platform_thread_internal_posix.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_storage.cc",
+ "../chromium/base/threading/thread_local_storage_posix.cc",
+ "../chromium/base/threading/thread_restrictions.cc",
+ "../chromium/base/time/time.cc",
+ "../chromium/base/time/time_exploded_posix.cc",
+ "../chromium/base/time/time_now_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",
+ "/ipc/glue/UtilityProcessSandboxing.cpp",
+ "broker/SandboxBrokerCommon.cpp",
+ "Sandbox.cpp",
+ "SandboxBrokerClient.cpp",
+ "SandboxFilter.cpp",
+ "SandboxFilterUtil.cpp",
+ "SandboxHooks.cpp",
+ "SandboxInfo.cpp",
+ "SandboxLogging.cpp",
+ "SandboxOpenedFiles.cpp",
+ "SandboxReporterClient.cpp",
+]
+
+SOURCES += [
+ "../chromium/base/strings/safe_sprintf.cc",
+ "../chromium/base/third_party/icu/icu_utf.cc",
+ "../chromium/sandbox/linux/seccomp-bpf/trap.cc",
+ "../chromium/sandbox/linux/services/syscall_wrappers.cc",
+]
+
+# 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"]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ # Keep clang from warning about intentional 'switch' fallthrough in icu_utf.cc:
+ SOURCES["../chromium/base/third_party/icu/icu_utf.cc"].flags += [
+ "-Wno-implicit-fallthrough"
+ ]
+ SOURCES["../chromium/sandbox/linux/seccomp-bpf/trap.cc"].flags += [
+ "-Wno-unreachable-code-return"
+ ]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=stack-protector"]
+ 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.
+for f in CONFIG["OS_CXXFLAGS"]:
+ if f.startswith("-flto") and CONFIG["CC_TYPE"] != "clang":
+ LDFLAGS += ["--param lto-partitions=1"]
+
+DEFINES["NS_NO_XPCOM"] = True
+DisableStlWrapping()
+
+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",
+ "interfaces",
+ "launch",
+ "reporter",
+]
+
+TEST_DIRS += [
+ "gtest",
+]
diff --git a/security/sandbox/linux/reporter/SandboxReporter.cpp b/security/sandbox/linux/reporter/SandboxReporter.cpp
new file mode 100644
index 0000000000..a7c71cd5c9
--- /dev/null
+++ b/security/sandbox/linux/reporter/SandboxReporter.cpp
@@ -0,0 +1,299 @@
+/* -*- 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 "SandboxReporter.h"
+#include "SandboxLogging.h"
+
+#include <algorithm>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <time.h> // for clockid_t
+
+#include "GeckoProfiler.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/PodOperations.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Telemetry.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+// Distinguish architectures for the telemetry key.
+#if defined(__i386__)
+# define SANDBOX_ARCH_NAME "x86"
+#elif defined(__x86_64__)
+# define SANDBOX_ARCH_NAME "amd64"
+#elif defined(__arm__)
+# define SANDBOX_ARCH_NAME "arm"
+#elif defined(__aarch64__)
+# define SANDBOX_ARCH_NAME "arm64"
+#else
+# error "unrecognized architecture"
+#endif
+
+namespace mozilla {
+
+StaticAutoPtr<SandboxReporter> SandboxReporter::sSingleton;
+
+SandboxReporter::SandboxReporter()
+ : mClientFd(-1),
+ mServerFd(-1),
+ mMutex("SandboxReporter"),
+ mBuffer(MakeUnique<SandboxReport[]>(kSandboxReporterBufferSize)),
+ mCount(0) {}
+
+bool SandboxReporter::Init() {
+ int fds[2];
+
+ if (0 != socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, fds)) {
+ SANDBOX_LOG_ERRNO("SandboxReporter: socketpair failed");
+ return false;
+ }
+ mClientFd = fds[0];
+ mServerFd = fds[1];
+
+ if (!PlatformThread::Create(0, this, &mThread)) {
+ SANDBOX_LOG_ERRNO("SandboxReporter: thread creation failed");
+ close(mClientFd);
+ close(mServerFd);
+ mClientFd = mServerFd = -1;
+ return false;
+ }
+
+ return true;
+}
+
+SandboxReporter::~SandboxReporter() {
+ if (mServerFd < 0) {
+ return;
+ }
+ shutdown(mServerFd, SHUT_RD);
+ PlatformThread::Join(mThread);
+ close(mServerFd);
+ close(mClientFd);
+}
+
+/* static */
+SandboxReporter* SandboxReporter::Singleton() {
+ static StaticMutex sMutex MOZ_UNANNOTATED;
+ StaticMutexAutoLock lock(sMutex);
+
+ if (sSingleton == nullptr) {
+ sSingleton = new SandboxReporter();
+ if (!sSingleton->Init()) {
+ // If socketpair or thread creation failed, trying to continue
+ // with child process creation is unlikely to succeed; crash
+ // instead of trying to handle that case.
+ MOZ_CRASH("SandboxRepoter::Singleton: initialization failed");
+ }
+ // ClearOnShutdown must be called on the main thread and will
+ // destroy the object on the main thread. That *should* be safe;
+ // the destructor will shut down the reporter's socket reader
+ // thread before freeing anything, IPC should already be shut down
+ // by that point (so it won't race by calling Singleton()), all
+ // non-main XPCOM threads will also be shut down, and currently
+ // the only other user is the main-thread-only Troubleshoot.sys.mjs.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "SandboxReporter::Singleton", [] { ClearOnShutdown(&sSingleton); }));
+ }
+ return sSingleton.get();
+}
+
+void SandboxReporter::GetClientFileDescriptorMapping(int* aSrcFd,
+ int* aDstFd) const {
+ MOZ_ASSERT(mClientFd >= 0);
+ *aSrcFd = mClientFd;
+ *aDstFd = kSandboxReporterFileDesc;
+}
+
+// This function is mentioned in Histograms.json; keep that in mind if
+// it's renamed or moved to a different file.
+static void SubmitToTelemetry(const SandboxReport& aReport) {
+ nsAutoCString key;
+ // The key contains the process type, something that uniquely
+ // identifies the syscall, and in some cases arguments (see below
+ // for details). Arbitrary formatting choice: fields in the key are
+ // separated by ':', except that (arch, syscall#) pairs are
+ // separated by '/'.
+ //
+ // Examples:
+ // * "content:x86/64" (bug 1285768)
+ // * "content:x86_64/110" (bug 1285768)
+ // * "gmp:madvise:8" (bug 1303813)
+ // * "content:clock_gettime:4" (bug 1334687)
+
+ switch (aReport.mProcType) {
+ case SandboxReport::ProcType::CONTENT:
+ key.AppendLiteral("content");
+ break;
+ case SandboxReport::ProcType::FILE:
+ key.AppendLiteral("file");
+ break;
+ case SandboxReport::ProcType::MEDIA_PLUGIN:
+ key.AppendLiteral("gmp");
+ break;
+ case SandboxReport::ProcType::RDD:
+ key.AppendLiteral("rdd");
+ break;
+ case SandboxReport::ProcType::SOCKET_PROCESS:
+ key.AppendLiteral("socket");
+ break;
+ case SandboxReport::ProcType::UTILITY:
+ key.AppendLiteral("utility");
+ break;
+ default:
+ MOZ_ASSERT(false);
+ }
+ key.Append(':');
+
+ switch (aReport.mSyscall) {
+ // Syscalls that are filtered by arguments in one or more of the
+ // policies in SandboxFilter.cpp should generally have those
+ // arguments included here, but don't include irrelevant
+ // information that would cause large numbers of distinct keys for
+ // the same issue -- for example, pids or pointers. When in
+ // doubt, include arguments only if they would typically be
+ // constants (or asm immediates) in the code making the syscall.
+ //
+ // Also, keep in mind that this is opt-out data collection and
+ // privacy is critical. While it's unlikely that information in
+ // the register values alone could personally identify a user
+ // (see also crash reports, where register contents are public),
+ // and the guidelines in the previous paragraph should rule out
+ // any value that's capable of holding PII, please be careful.
+ //
+ // When making changes here, please consult with a data steward
+ // (https://wiki.mozilla.org/Firefox/Data_Collection) and ask for
+ // a review if you are unsure about anything.
+
+ // This macro includes one argument as a decimal number; it should
+ // be enough for most cases.
+#define ARG_DECIMAL(name, idx) \
+ case __NR_##name: \
+ key.AppendLiteral(#name ":"); \
+ key.AppendInt(aReport.mArgs[idx]); \
+ break
+
+ // This may be more convenient if the argument is a set of bit flags.
+#define ARG_HEX(name, idx) \
+ case __NR_##name: \
+ key.AppendLiteral(#name ":0x"); \
+ key.AppendInt(aReport.mArgs[idx], 16); \
+ break
+
+ // clockid_t is annoying: there are a small set of fixed timers,
+ // but it can also encode a pid/tid (or a fd for a hardware clock
+ // device); in this case the value is negative.
+#define ARG_CLOCKID(name, idx) \
+ case __NR_##name: \
+ key.AppendLiteral(#name ":"); \
+ if (static_cast<clockid_t>(aReport.mArgs[idx]) < 0) { \
+ key.AppendLiteral("dynamic"); \
+ } else { \
+ key.AppendInt(aReport.mArgs[idx]); \
+ } \
+ break
+
+ // The syscalls handled specially:
+
+ ARG_HEX(clone, 0); // flags
+ ARG_DECIMAL(prctl, 0); // option
+ ARG_HEX(ioctl, 1); // request
+ ARG_DECIMAL(fcntl, 1); // cmd
+ ARG_DECIMAL(madvise, 2); // advice
+ ARG_CLOCKID(clock_gettime, 0); // clk_id
+
+#ifdef __NR_socketcall
+ ARG_DECIMAL(socketcall, 0); // call
+#endif
+#ifdef __NR_ipc
+ ARG_DECIMAL(ipc, 0); // call
+#endif
+
+#undef ARG_DECIMAL
+#undef ARG_HEX
+#undef ARG_CLOCKID
+
+ default:
+ // Otherwise just use the number, with the arch name to disambiguate.
+ key.Append(SANDBOX_ARCH_NAME "/");
+ key.AppendInt(aReport.mSyscall);
+ }
+
+ Telemetry::Accumulate(Telemetry::SANDBOX_REJECTED_SYSCALLS, key);
+}
+
+void SandboxReporter::AddOne(const SandboxReport& aReport) {
+ SubmitToTelemetry(aReport);
+
+ MutexAutoLock lock(mMutex);
+ mBuffer[mCount % kSandboxReporterBufferSize] = aReport;
+ ++mCount;
+}
+
+void SandboxReporter::ThreadMain(void) {
+ // Create a nsThread wrapper for the current platform thread, and register it
+ // with the thread manager.
+ (void)NS_GetCurrentThread();
+
+ PlatformThread::SetName("SandboxReporter");
+ AUTO_PROFILER_REGISTER_THREAD("SandboxReporter");
+
+ for (;;) {
+ SandboxReport rep;
+ struct iovec iov;
+ struct msghdr msg;
+
+ iov.iov_base = &rep;
+ iov.iov_len = sizeof(rep);
+ PodZero(&msg);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ const auto recvd = recvmsg(mServerFd, &msg, 0);
+ if (recvd < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ SANDBOX_LOG_ERRNO("SandboxReporter: recvmsg");
+ }
+ if (recvd <= 0) {
+ break;
+ }
+
+ if (static_cast<size_t>(recvd) < sizeof(rep)) {
+ SANDBOX_LOG("SandboxReporter: packet too short (%d < %d)", recvd,
+ sizeof(rep));
+ continue;
+ }
+ if (msg.msg_flags & MSG_TRUNC) {
+ SANDBOX_LOG("SandboxReporter: packet too long");
+ continue;
+ }
+
+ AddOne(rep);
+ }
+}
+
+SandboxReporter::Snapshot SandboxReporter::GetSnapshot() {
+ Snapshot snapshot;
+ MutexAutoLock lock(mMutex);
+
+ const uint64_t bufSize = static_cast<uint64_t>(kSandboxReporterBufferSize);
+ const uint64_t start = std::max(mCount, bufSize) - bufSize;
+ snapshot.mOffset = start;
+ snapshot.mReports.Clear();
+ snapshot.mReports.SetCapacity(mCount - start);
+ for (size_t i = start; i < mCount; ++i) {
+ const SandboxReport* rep = &mBuffer[i % kSandboxReporterBufferSize];
+ MOZ_ASSERT(rep->IsValid());
+ snapshot.mReports.AppendElement(*rep);
+ }
+ return snapshot;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/reporter/SandboxReporter.h b/security/sandbox/linux/reporter/SandboxReporter.h
new file mode 100644
index 0000000000..0969111c9c
--- /dev/null
+++ b/security/sandbox/linux/reporter/SandboxReporter.h
@@ -0,0 +1,86 @@
+/* -*- 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_SandboxReporter_h
+#define mozilla_SandboxReporter_h
+
+#include "SandboxReporterCommon.h"
+
+#include "base/platform_thread.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+// This object collects the SandboxReport messages from all of the
+// child processes, submits them to Telemetry, and maintains a ring
+// buffer of the last kSandboxReporterBufferSize reports.
+class SandboxReporter final : public PlatformThread::Delegate {
+ public:
+ // For normal use, don't construct this directly; use the
+ // Singleton() method.
+ //
+ // For unit testing, use this constructor followed by the Init
+ // method; the object isn't usable unless Init returns true.
+ explicit SandboxReporter();
+ ~SandboxReporter();
+
+ // See above; this method is not thread-safe.
+ bool Init();
+
+ // Used in GeckoChildProcessHost to connect the child process's
+ // client to this report collector.
+ void GetClientFileDescriptorMapping(int* aSrcFd, int* aDstFd) const;
+
+ // A snapshot of the report ring buffer; element 0 of `mReports` is
+ // the `mOffset`th report to be received, and so on.
+ struct Snapshot {
+ // The buffer has to fit in memory, but the total number of
+ // reports received in the session can increase without bound and
+ // could potentially overflow a uint32_t, so this is 64-bit.
+ // (It's exposed to JS as a 53-bit int, effectively, but that
+ // should also be large enough.)
+ uint64_t mOffset;
+ nsTArray<SandboxReport> mReports;
+ };
+
+ // Read the ring buffer contents; this method is thread-safe.
+ Snapshot GetSnapshot();
+
+ // Gets or creates the singleton report collector. Crashes if
+ // initialization fails (if a socketpair and/or thread can't be
+ // created, there was almost certainly about to be a crash anyway).
+ // Thread-safe as long as the pointer isn't used during/after XPCOM
+ // shutdown.
+ static SandboxReporter* Singleton();
+
+ private:
+ // These are constant over the life of the object:
+ int mClientFd;
+ int mServerFd;
+ PlatformThreadHandle mThread;
+
+ Mutex mMutex MOZ_UNANNOTATED;
+ // These are protected by mMutex:
+ UniquePtr<SandboxReport[]> mBuffer;
+ uint64_t mCount;
+
+ static StaticAutoPtr<SandboxReporter> sSingleton;
+
+ void ThreadMain(void) override;
+ void AddOne(const SandboxReport& aReport);
+};
+
+// This is a constant so the % operations can be optimized. This is
+// exposed in the header so that unit tests can see it.
+static const size_t kSandboxReporterBufferSize = 32;
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxReporter_h
diff --git a/security/sandbox/linux/reporter/SandboxReporterCommon.h b/security/sandbox/linux/reporter/SandboxReporterCommon.h
new file mode 100644
index 0000000000..9bfb40bb98
--- /dev/null
+++ b/security/sandbox/linux/reporter/SandboxReporterCommon.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_SandboxReporterCommon_h
+#define mozilla_SandboxReporterCommon_h
+
+#include "mozilla/IntegerTypeTraits.h"
+#include "mozilla/Types.h"
+
+#include <sys/types.h>
+
+// Note: this is also used in libmozsandbox, so dependencies on
+// symbols from libxul probably won't work.
+
+namespace mozilla {
+static const size_t kSandboxSyscallArguments = 6;
+// fds 0-2: stdio; fd 3: IPC; fd 4: crash reporter. (The IPC child
+// process launching code will check that we don't try to use the same
+// fd twice.)
+static const int kSandboxReporterFileDesc = 5;
+
+// This struct represents a system call that was rejected by a
+// seccomp-bpf policy.
+struct SandboxReport {
+ // In the future this may include finer distinctions than
+ // GeckoProcessType -- e.g., whether a content process can load
+ // file:/// URLs, or if it's reserved for content with certain
+ // user-granted permissions.
+ enum class ProcType : uint8_t {
+ CONTENT,
+ FILE,
+ MEDIA_PLUGIN,
+ RDD,
+ SOCKET_PROCESS,
+ UTILITY,
+ };
+
+ // The syscall number and arguments are usually `unsigned long`, but
+ // that causes ambiguous overload errors with nsACString::AppendInt.
+ using ULong = UnsignedStdintTypeForSize<sizeof(unsigned long)>::Type;
+
+ // This time uses CLOCK_MONOTONIC_COARSE. Displaying or reporting
+ // it should usually be done relative to the current value of that
+ // clock (or the time at some other event of interest, like a
+ // subsequent crash).
+ struct timespec mTime;
+
+ // The pid/tid values, like every other field in this struct, aren't
+ // authenticated and a compromised process could send anything, so
+ // use the values with caution.
+ pid_t mPid;
+ pid_t mTid;
+ ProcType mProcType;
+ ULong mSyscall;
+ ULong mArgs[kSandboxSyscallArguments];
+
+ SandboxReport() : mPid(0) {}
+ bool IsValid() const { return mPid > 0; }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxReporterCommon_h
diff --git a/security/sandbox/linux/reporter/SandboxReporterWrappers.cpp b/security/sandbox/linux/reporter/SandboxReporterWrappers.cpp
new file mode 100644
index 0000000000..755e8e858d
--- /dev/null
+++ b/security/sandbox/linux/reporter/SandboxReporterWrappers.cpp
@@ -0,0 +1,199 @@
+/* -*- 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 "mozISandboxReporter.h"
+#include "SandboxReporter.h"
+
+#include <time.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Components.h"
+#include "nsCOMPtr.h"
+#include "nsPrintfCString.h"
+#include "nsTArray.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+
+class SandboxReportWrapper final : public mozISandboxReport {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISANDBOXREPORT
+
+ explicit SandboxReportWrapper(const SandboxReport& aReport)
+ : mReport(aReport) {}
+
+ private:
+ ~SandboxReportWrapper() = default;
+ SandboxReport mReport;
+};
+
+NS_IMPL_ISUPPORTS(SandboxReportWrapper, mozISandboxReport)
+
+/* readonly attribute uint64_t msecAgo; */
+NS_IMETHODIMP SandboxReportWrapper::GetMsecAgo(uint64_t* aMsec) {
+ struct timespec then = mReport.mTime, now = {0, 0};
+ clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
+
+ const uint64_t now_msec = uint64_t(now.tv_sec) * 1000 + now.tv_nsec / 1000000;
+ const uint64_t then_msec =
+ uint64_t(then.tv_sec) * 1000 + then.tv_nsec / 1000000;
+ MOZ_DIAGNOSTIC_ASSERT(now_msec >= then_msec);
+ if (now_msec >= then_msec) {
+ *aMsec = now_msec - then_msec;
+ } else {
+ *aMsec = 0;
+ }
+ return NS_OK;
+}
+
+/* readonly attribute int32_t pid; */
+NS_IMETHODIMP SandboxReportWrapper::GetPid(int32_t* aPid) {
+ *aPid = mReport.mPid;
+ return NS_OK;
+}
+
+/* readonly attribute int32_t tid; */
+NS_IMETHODIMP SandboxReportWrapper::GetTid(int32_t* aTid) {
+ *aTid = mReport.mTid;
+ return NS_OK;
+}
+
+/* readonly attribute ACString procType; */
+NS_IMETHODIMP SandboxReportWrapper::GetProcType(nsACString& aProcType) {
+ switch (mReport.mProcType) {
+ case SandboxReport::ProcType::CONTENT:
+ aProcType.AssignLiteral("content");
+ return NS_OK;
+ case SandboxReport::ProcType::FILE:
+ aProcType.AssignLiteral("file");
+ return NS_OK;
+ case SandboxReport::ProcType::MEDIA_PLUGIN:
+ aProcType.AssignLiteral("mediaPlugin");
+ return NS_OK;
+ case SandboxReport::ProcType::RDD:
+ aProcType.AssignLiteral("dataDecoder");
+ return NS_OK;
+ case SandboxReport::ProcType::SOCKET_PROCESS:
+ aProcType.AssignLiteral("socketProcess");
+ return NS_OK;
+ case SandboxReport::ProcType::UTILITY:
+ aProcType.AssignLiteral("utility");
+ return NS_OK;
+ default:
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+ }
+}
+
+/* readonly attribute uint32_t syscall; */
+NS_IMETHODIMP SandboxReportWrapper::GetSyscall(uint32_t* aSyscall) {
+ *aSyscall = static_cast<uint32_t>(mReport.mSyscall);
+ MOZ_ASSERT(static_cast<SandboxReport::ULong>(*aSyscall) == mReport.mSyscall);
+ return NS_OK;
+}
+
+/* readonly attribute uint32_t numArgs; */
+NS_IMETHODIMP SandboxReportWrapper::GetNumArgs(uint32_t* aNumArgs) {
+ *aNumArgs = static_cast<uint32_t>(kSandboxSyscallArguments);
+ return NS_OK;
+}
+
+/* ACString getArg (in uint32_t aIndex); */
+NS_IMETHODIMP SandboxReportWrapper::GetArg(uint32_t aIndex,
+ nsACString& aRetval) {
+ if (aIndex >= kSandboxSyscallArguments) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ const auto arg = mReport.mArgs[aIndex];
+ nsAutoCString str;
+ // Use decimal for smaller numbers (more likely ints) and hex for
+ // larger (more likely pointers). This cutoff is arbitrary.
+ if (arg >= 1000000) {
+ str.AppendLiteral("0x");
+ str.AppendInt(arg, 16);
+ } else {
+ str.AppendInt(arg, 10);
+ }
+ aRetval = str;
+ return NS_OK;
+}
+
+class SandboxReportArray final : public mozISandboxReportArray {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISANDBOXREPORTARRAY
+
+ explicit SandboxReportArray(SandboxReporter::Snapshot&& aSnap)
+ : mOffset(aSnap.mOffset), mArray(std::move(aSnap.mReports)) {}
+
+ private:
+ ~SandboxReportArray() = default;
+ uint64_t mOffset;
+ nsTArray<SandboxReport> mArray;
+};
+
+NS_IMPL_ISUPPORTS(SandboxReportArray, mozISandboxReportArray)
+
+/* readonly attribute uint64_t begin; */
+NS_IMETHODIMP SandboxReportArray::GetBegin(uint64_t* aBegin) {
+ *aBegin = mOffset;
+ return NS_OK;
+}
+
+/* readonly attribute uint64_t end; */
+NS_IMETHODIMP SandboxReportArray::GetEnd(uint64_t* aEnd) {
+ *aEnd = mOffset + mArray.Length();
+ return NS_OK;
+}
+
+/* mozISandboxReport getElement (in uint64_t aIndex); */
+NS_IMETHODIMP SandboxReportArray::GetElement(uint64_t aIndex,
+ mozISandboxReport** aRetval) {
+ uint64_t relIndex = aIndex - mOffset;
+ if (relIndex >= mArray.Length()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<mozISandboxReport> wrapper =
+ new SandboxReportWrapper(mArray[relIndex]);
+ wrapper.forget(aRetval);
+ return NS_OK;
+}
+
+class SandboxReporterWrapper final : public mozISandboxReporter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISANDBOXREPORTER
+
+ SandboxReporterWrapper() = default;
+
+ private:
+ ~SandboxReporterWrapper() = default;
+};
+
+NS_IMPL_ISUPPORTS(SandboxReporterWrapper, mozISandboxReporter)
+
+/* mozISandboxReportArray snapshot(); */
+NS_IMETHODIMP SandboxReporterWrapper::Snapshot(
+ mozISandboxReportArray** aRetval) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<mozISandboxReportArray> wrapper =
+ new SandboxReportArray(SandboxReporter::Singleton()->GetSnapshot());
+ wrapper.forget(aRetval);
+ return NS_OK;
+}
+
+} // namespace mozilla
+
+NS_IMPL_COMPONENT_FACTORY(mozISandboxReporter) {
+ return MakeAndAddRef<SandboxReporterWrapper>().downcast<nsISupports>();
+}
diff --git a/security/sandbox/linux/reporter/components.conf b/security/sandbox/linux/reporter/components.conf
new file mode 100644
index 0000000000..7bd278f349
--- /dev/null
+++ b/security/sandbox/linux/reporter/components.conf
@@ -0,0 +1,13 @@
+# -*- 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/.
+
+Classes = [
+ {
+ 'cid': '{5118a6f9-2493-4f97-9552-620663e03cb3}',
+ 'contract_ids': ['@mozilla.org/sandbox/syscall-reporter;1'],
+ 'type': 'mozISandboxReporter',
+ },
+]
diff --git a/security/sandbox/linux/reporter/moz.build b/security/sandbox/linux/reporter/moz.build
new file mode 100644
index 0000000000..d5b037fd09
--- /dev/null
+++ b/security/sandbox/linux/reporter/moz.build
@@ -0,0 +1,34 @@
+# -*- 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 += [
+ "SandboxReporter.h",
+ "SandboxReporterCommon.h",
+]
+
+UNIFIED_SOURCES += [
+ "SandboxReporter.cpp",
+ "SandboxReporterWrappers.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+LOCAL_INCLUDES += [
+ "/security/sandbox/linux", # SandboxLogging.h
+]
+
+# Need this for base::PlatformThread
+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"