import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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();
}
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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));
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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']

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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',
]