summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux/Sandbox.cpp
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/Sandbox.cpp
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/Sandbox.cpp')
-rw-r--r--security/sandbox/linux/Sandbox.cpp813
1 files changed, 813 insertions, 0 deletions
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