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