summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux/broker
diff options
context:
space:
mode:
Diffstat (limited to 'security/sandbox/linux/broker')
-rw-r--r--security/sandbox/linux/broker/SandboxBroker.cpp731
-rw-r--r--security/sandbox/linux/broker/SandboxBroker.h132
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerCommon.cpp120
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerCommon.h72
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp194
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h32
-rw-r--r--security/sandbox/linux/broker/SandboxBrokerUtils.h30
-rw-r--r--security/sandbox/linux/broker/moz.build37
8 files changed, 1348 insertions, 0 deletions
diff --git a/security/sandbox/linux/broker/SandboxBroker.cpp b/security/sandbox/linux/broker/SandboxBroker.cpp
new file mode 100644
index 000000000..a31d1fc66
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -0,0 +1,731 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxBroker.h"
+#include "SandboxInfo.h"
+#include "SandboxLogging.h"
+#include "SandboxBrokerUtils.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifdef XP_LINUX
+#include <sys/prctl.h>
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+#include <private/android_filesystem_config.h>
+#include <sys/syscall.h>
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Move.h"
+#include "mozilla/NullPtr.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+namespace mozilla {
+
+// This constructor signals failure by setting mFileDesc and aClientFd to -1.
+SandboxBroker::SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
+ int& aClientFd)
+ : mChildPid(aChildPid), mPolicy(Move(aPolicy))
+{
+ int fds[2];
+ if (0 != socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)) {
+ SANDBOX_LOG_ERROR("SandboxBroker: socketpair failed: %s", strerror(errno));
+ mFileDesc = -1;
+ aClientFd = -1;
+ return;
+ }
+ mFileDesc = fds[0];
+ aClientFd = fds[1];
+
+ if (!PlatformThread::Create(0, this, &mThread)) {
+ SANDBOX_LOG_ERROR("SandboxBroker: thread creation failed: %s",
+ strerror(errno));
+ close(mFileDesc);
+ close(aClientFd);
+ mFileDesc = -1;
+ aClientFd = -1;
+ }
+}
+
+UniquePtr<SandboxBroker>
+SandboxBroker::Create(UniquePtr<const Policy> aPolicy, int aChildPid,
+ ipc::FileDescriptor& aClientFdOut)
+{
+ int clientFd;
+ // Can't use MakeUnique here because the constructor is private.
+ UniquePtr<SandboxBroker> rv(new SandboxBroker(Move(aPolicy), aChildPid,
+ clientFd));
+ if (clientFd < 0) {
+ rv = nullptr;
+ } else {
+ aClientFdOut = ipc::FileDescriptor(clientFd);
+ }
+ return Move(rv);
+}
+
+SandboxBroker::~SandboxBroker() {
+ // If the constructor failed, there's nothing to be done here.
+ if (mFileDesc < 0) {
+ return;
+ }
+
+ shutdown(mFileDesc, SHUT_RD);
+ // The thread will now get EOF even if the client hasn't exited.
+ PlatformThread::Join(mThread);
+ // Now that the thread has exited, the fd will no longer be accessed.
+ close(mFileDesc);
+ // Having ensured that this object outlives the thread, this
+ // destructor can now return.
+}
+
+SandboxBroker::Policy::Policy() { }
+SandboxBroker::Policy::~Policy() { }
+
+SandboxBroker::Policy::Policy(const Policy& aOther) {
+ for (auto iter = aOther.mMap.ConstIter(); !iter.Done(); iter.Next()) {
+ mMap.Put(iter.Key(), iter.Data());
+ }
+}
+
+// Chromium
+// sandbox/linux/syscall_broker/broker_file_permission.cc
+// Async signal safe
+bool
+SandboxBroker::Policy::ValidatePath(const char* path) const {
+ if (!path)
+ return false;
+
+ const size_t len = strlen(path);
+ // No empty paths
+ if (len == 0)
+ return false;
+ // Paths must be absolute and not relative
+ if (path[0] != '/')
+ return false;
+ // No trailing / (but "/" is valid)
+ if (len > 1 && path[len - 1] == '/')
+ return false;
+ // No trailing /.
+ if (len >= 2 && path[len - 2] == '/' && path[len - 1] == '.')
+ return false;
+ // No trailing /..
+ if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' &&
+ path[len - 1] == '.')
+ return false;
+ // No /../ anywhere
+ for (size_t i = 0; i < len; i++) {
+ if (path[i] == '/' && (len - i) > 3) {
+ if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void
+SandboxBroker::Policy::AddPath(int aPerms, const char* aPath,
+ AddCondition aCond)
+{
+ nsDependentCString path(aPath);
+ MOZ_ASSERT(path.Length() <= kMaxPathLen);
+ int perms;
+ if (aCond == AddIfExistsNow) {
+ struct stat statBuf;
+ if (lstat(aPath, &statBuf) != 0) {
+ return;
+ }
+ }
+ if (!mMap.Get(path, &perms)) {
+ perms = MAY_ACCESS;
+ } else {
+ MOZ_ASSERT(perms & MAY_ACCESS);
+ }
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("policy for %s: %d -> %d", aPath, perms, perms | aPerms);
+ }
+ perms |= aPerms;
+ mMap.Put(path, perms);
+}
+
+void
+SandboxBroker::Policy::AddTree(int aPerms, const char* aPath)
+{
+ struct stat statBuf;
+
+ if (stat(aPath, &statBuf) != 0) {
+ return;
+ }
+ if (!S_ISDIR(statBuf.st_mode)) {
+ AddPath(aPerms, aPath, AddAlways);
+ } else {
+ DIR* dirp = opendir(aPath);
+ if (!dirp) {
+ return;
+ }
+ while (struct dirent* de = readdir(dirp)) {
+ if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
+ continue;
+ }
+ // Note: could optimize the string handling.
+ nsAutoCString subPath;
+ subPath.Assign(aPath);
+ subPath.Append('/');
+ subPath.Append(de->d_name);
+ AddTree(aPerms, subPath.get());
+ }
+ closedir(dirp);
+ }
+}
+
+void
+SandboxBroker::Policy::AddDir(int aPerms, const char* aPath)
+{
+ struct stat statBuf;
+
+ if (stat(aPath, &statBuf) != 0) {
+ return;
+ }
+
+ if (!S_ISDIR(statBuf.st_mode)) {
+ return;
+ }
+
+ nsDependentCString path(aPath);
+ MOZ_ASSERT(path.Length() <= kMaxPathLen - 1);
+ // Enforce trailing / on aPath
+ if (path[path.Length() - 1] != '/') {
+ path.Append('/');
+ }
+ int origPerms;
+ if (!mMap.Get(path, &origPerms)) {
+ origPerms = MAY_ACCESS;
+ } else {
+ MOZ_ASSERT(origPerms & MAY_ACCESS);
+ }
+ int newPerms = origPerms | aPerms | RECURSIVE;
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("policy for %s: %d -> %d", aPath, origPerms, newPerms);
+ }
+ mMap.Put(path, newPerms);
+}
+
+void
+SandboxBroker::Policy::AddPrefix(int aPerms, const char* aDir,
+ const char* aPrefix)
+{
+ size_t prefixLen = strlen(aPrefix);
+ DIR* dirp = opendir(aDir);
+ struct dirent* de;
+ if (!dirp) {
+ return;
+ }
+ while ((de = readdir(dirp))) {
+ if (strncmp(de->d_name, aPrefix, prefixLen) == 0) {
+ nsAutoCString subPath;
+ subPath.Assign(aDir);
+ subPath.Append('/');
+ subPath.Append(de->d_name);
+ AddPath(aPerms, subPath.get(), AddAlways);
+ }
+ }
+ closedir(dirp);
+}
+
+int
+SandboxBroker::Policy::Lookup(const nsACString& aPath) const
+{
+ // Early exit for paths explicitly found in the
+ // whitelist.
+ // This means they will not gain extra permissions
+ // from recursive paths.
+ int perms = mMap.Get(aPath);
+ if (perms) {
+ return perms;
+ }
+
+ // Not a legally constructed path
+ if (!ValidatePath(PromiseFlatCString(aPath).get()))
+ return 0;
+
+ // Now it's either an illegal access, or a recursive
+ // directory permission. We'll have to check the entire
+ // whitelist for the best match (slower).
+ int allPerms = 0;
+ for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
+ const nsACString& whiteListPath = iter.Key();
+ const int& perms = iter.Data();
+
+ if (!(perms & RECURSIVE))
+ continue;
+
+ // passed part starts with something on the whitelist
+ if (StringBeginsWith(aPath, whiteListPath)) {
+ allPerms |= perms;
+ }
+ }
+
+ // Strip away the RECURSIVE flag as it doesn't
+ // necessarily apply to aPath.
+ return allPerms & ~RECURSIVE;
+}
+
+static bool
+AllowOperation(int aReqFlags, int aPerms)
+{
+ int needed = 0;
+ if (aReqFlags & R_OK) {
+ needed |= SandboxBroker::MAY_READ;
+ }
+ if (aReqFlags & W_OK) {
+ needed |= SandboxBroker::MAY_WRITE;
+ }
+ // We don't really allow executing anything,
+ // so in true unix tradition we hijack this
+ // for directories.
+ if (aReqFlags & X_OK) {
+ needed |= SandboxBroker::MAY_CREATE;
+ }
+ return (aPerms & needed) == needed;
+}
+
+static bool
+AllowAccess(int aReqFlags, int aPerms)
+{
+ if (aReqFlags & ~(R_OK|W_OK|F_OK)) {
+ return false;
+ }
+ int needed = 0;
+ if (aReqFlags & R_OK) {
+ needed |= SandboxBroker::MAY_READ;
+ }
+ if (aReqFlags & W_OK) {
+ needed |= SandboxBroker::MAY_WRITE;
+ }
+ return (aPerms & needed) == needed;
+}
+
+// These flags are added to all opens to prevent possible side-effects
+// on this process. These shouldn't be relevant to the child process
+// in any case due to the sandboxing restrictions on it. (See also
+// the use of MSG_CMSG_CLOEXEC in SandboxBrokerCommon.cpp).
+static const int kRequiredOpenFlags = O_CLOEXEC | O_NOCTTY;
+
+// Linux originally assigned a flag bit to O_SYNC but implemented the
+// semantics standardized as O_DSYNC; later, that bit was renamed and
+// a new bit was assigned to the full O_SYNC, and O_SYNC was redefined
+// to be both bits. As a result, this #define is needed to compensate
+// for outdated kernel headers like Android's.
+#define O_SYNC_NEW 04010000
+static const int kAllowedOpenFlags =
+ O_APPEND | O_ASYNC | O_DIRECT | O_DIRECTORY | O_EXCL | O_LARGEFILE
+ | O_NOATIME | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY | O_SYNC_NEW
+ | O_TRUNC | O_CLOEXEC | O_CREAT;
+#undef O_SYNC_NEW
+
+static bool
+AllowOpen(int aReqFlags, int aPerms)
+{
+ if (aReqFlags & ~O_ACCMODE & ~kAllowedOpenFlags) {
+ return false;
+ }
+ int needed;
+ switch(aReqFlags & O_ACCMODE) {
+ case O_RDONLY:
+ needed = SandboxBroker::MAY_READ;
+ break;
+ case O_WRONLY:
+ needed = SandboxBroker::MAY_WRITE;
+ break;
+ case O_RDWR:
+ needed = SandboxBroker::MAY_READ | SandboxBroker::MAY_WRITE;
+ break;
+ default:
+ return false;
+ }
+ if (aReqFlags & O_CREAT) {
+ needed |= SandboxBroker::MAY_CREATE;
+ }
+ return (aPerms & needed) == needed;
+}
+
+static int
+DoStat(const char* aPath, void* aBuff, int aFlags)
+{
+ if (aFlags & O_NOFOLLOW) {
+ return lstatsyscall(aPath, (statstruct*)aBuff);
+ }
+ return statsyscall(aPath, (statstruct*)aBuff);
+}
+
+static int
+DoLink(const char* aPath, const char* aPath2,
+ SandboxBrokerCommon::Operation aOper)
+{
+ if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) {
+ return link(aPath, aPath2);
+ } else if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
+ return symlink(aPath, aPath2);
+ }
+ MOZ_CRASH("SandboxBroker: Unknown link operation");
+}
+
+size_t
+SandboxBroker::ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen)
+{
+ if (strstr(aPath, "..") != NULL) {
+ char* result = realpath(aPath, NULL);
+ if (result != NULL) {
+ strncpy(aPath, result, aBufSize);
+ aPath[aBufSize - 1] = '\0';
+ free(result);
+ // Size changed, but guaranteed to be 0 terminated
+ aPathLen = strlen(aPath);
+ }
+ // ValidatePath will handle failure to translate
+ }
+ return aPathLen;
+}
+
+void
+SandboxBroker::ThreadMain(void)
+{
+ char threadName[16];
+ SprintfLiteral(threadName, "FS Broker %d", mChildPid);
+ PlatformThread::SetName(threadName);
+
+ // Permissive mode can only be enabled through an environment variable,
+ // therefore it is sufficient to fetch the value once
+ // before the main thread loop starts
+ bool permissive = SandboxInfo::Get().Test(SandboxInfo::kPermissive);
+
+#ifdef MOZ_WIDGET_GONK
+#ifdef __NR_setreuid32
+ static const long nr_setreuid = __NR_setreuid32;
+ static const long nr_setregid = __NR_setregid32;
+#else
+ static const long nr_setreuid = __NR_setreuid;
+ static const long nr_setregid = __NR_setregid;
+#endif
+ if (syscall(nr_setregid, getgid(), AID_APP + mChildPid) != 0 ||
+ syscall(nr_setreuid, getuid(), AID_APP + mChildPid) != 0) {
+ MOZ_CRASH("SandboxBroker: failed to drop privileges");
+ }
+#endif
+
+ while (true) {
+ struct iovec ios[2];
+ // We will receive the path strings in 1 buffer and split them back up.
+ char recvBuf[2 * (kMaxPathLen + 1)];
+ char pathBuf[kMaxPathLen + 1];
+ char pathBuf2[kMaxPathLen + 1];
+ size_t pathLen;
+ size_t pathLen2;
+ char respBuf[kMaxPathLen + 1]; // Also serves as struct stat
+ Request req;
+ Response resp;
+ int respfd;
+
+ // Make sure stat responses fit in the response buffer
+ MOZ_ASSERT((kMaxPathLen + 1) > sizeof(struct stat));
+
+ // This makes our string handling below a bit less error prone.
+ memset(recvBuf, 0, sizeof(recvBuf));
+
+ ios[0].iov_base = &req;
+ ios[0].iov_len = sizeof(req);
+ ios[1].iov_base = recvBuf;
+ ios[1].iov_len = sizeof(recvBuf);
+
+ const ssize_t recvd = RecvWithFd(mFileDesc, ios, 2, &respfd);
+ if (recvd == 0) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("EOF from pid %d", mChildPid);
+ }
+ break;
+ }
+ // It could be possible to continue after errors and short reads,
+ // at least in some cases, but protocol violation indicates a
+ // hostile client, so terminate the broker instead.
+ if (recvd < 0) {
+ SANDBOX_LOG_ERROR("bad read from pid %d: %s",
+ mChildPid, strerror(errno));
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+ if (recvd < static_cast<ssize_t>(sizeof(req))) {
+ SANDBOX_LOG_ERROR("bad read from pid %d (%d < %d)",
+ mChildPid, recvd, sizeof(req));
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+ if (respfd == -1) {
+ SANDBOX_LOG_ERROR("no response fd from pid %d", mChildPid);
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+
+ // Initialize the response with the default failure.
+ memset(&resp, 0, sizeof(resp));
+ memset(&respBuf, 0, sizeof(respBuf));
+ resp.mError = -EACCES;
+ ios[0].iov_base = &resp;
+ ios[0].iov_len = sizeof(resp);
+ ios[1].iov_base = nullptr;
+ ios[1].iov_len = 0;
+ int openedFd = -1;
+
+ // Clear permissions
+ int perms;
+
+ // Find end of first string, make sure the buffer is still
+ // 0 terminated.
+ size_t recvBufLen = static_cast<size_t>(recvd) - sizeof(req);
+ if (recvBufLen > 0 && recvBuf[recvBufLen - 1] != 0) {
+ SANDBOX_LOG_ERROR("corrupted path buffer from pid %d", mChildPid);
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+
+ // First path should fit in maximum path length buffer.
+ size_t first_len = strlen(recvBuf);
+ if (first_len <= kMaxPathLen) {
+ strcpy(pathBuf, recvBuf);
+ // Skip right over the terminating 0, and try to copy in the
+ // second path, if any. If there's no path, this will hit a
+ // 0 immediately (we nulled the buffer before receiving).
+ // We do not assume the second path is 0-terminated, this is
+ // enforced below.
+ strncpy(pathBuf2, recvBuf + first_len + 1, kMaxPathLen + 1);
+
+ // First string is guaranteed to be 0-terminated.
+ pathLen = first_len;
+
+ // Look up the first pathname but first translate relative paths.
+ pathLen = ConvertToRealPath(pathBuf, sizeof(pathBuf), pathLen);
+ perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
+
+ // Same for the second path.
+ pathLen2 = strnlen(pathBuf2, kMaxPathLen);
+ if (pathLen2 > 0) {
+ // Force 0 termination.
+ pathBuf2[pathLen2] = '\0';
+ pathLen2 = ConvertToRealPath(pathBuf2, sizeof(pathBuf2), pathLen2);
+ int perms2 = mPolicy->Lookup(nsDependentCString(pathBuf2, pathLen2));
+
+ // Take the intersection of the permissions for both paths.
+ perms &= perms2;
+ }
+ } else {
+ // Failed to receive intelligible paths.
+ perms = 0;
+ }
+
+ // And now perform the operation if allowed.
+ if (perms & CRASH_INSTEAD) {
+ // This is somewhat nonmodular, but it works.
+ resp.mError = -ENOSYS;
+ } else if (permissive || perms & MAY_ACCESS) {
+ // If the operation was only allowed because of permissive mode, log it.
+ if (permissive && !(perms & MAY_ACCESS)) {
+ AuditPermissive(req.mOp, req.mFlags, perms, pathBuf);
+ }
+
+ switch(req.mOp) {
+ case SANDBOX_FILE_OPEN:
+ if (permissive || AllowOpen(req.mFlags, perms)) {
+ // Permissions for O_CREAT hardwired to 0600; if that's
+ // ever a problem we can change the protocol (but really we
+ // should be trying to remove uses of MAY_CREATE, not add
+ // new ones).
+ openedFd = open(pathBuf, req.mFlags | kRequiredOpenFlags, 0600);
+ if (openedFd >= 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_ACCESS:
+ if (permissive || AllowAccess(req.mFlags, perms)) {
+ // This can't use access() itself because that uses the ruid
+ // and not the euid. In theory faccessat() with AT_EACCESS
+ // would work, but Linux doesn't actually implement the
+ // flags != 0 case; glibc has a hack which doesn't even work
+ // in this case so it'll ignore the flag, and Bionic just
+ // passes through the syscall and always ignores the flags.
+ //
+ // Instead, because we've already checked the requested
+ // r/w/x bits against the policy, just return success if the
+ // file exists and hope that's close enough.
+ if (stat(pathBuf, (struct stat*)&respBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_STAT:
+ if (DoStat(pathBuf, (struct stat*)&respBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ ios[1].iov_base = &respBuf;
+ ios[1].iov_len = req.mBufSize;
+ } else {
+ resp.mError = -errno;
+ }
+ break;
+
+ case SANDBOX_FILE_CHMOD:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (chmod(pathBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_LINK:
+ case SANDBOX_FILE_SYMLINK:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_RENAME:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (rename(pathBuf, pathBuf2) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_MKDIR:
+ if (permissive || AllowOperation(W_OK | X_OK, perms)) {
+ if (mkdir(pathBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_UNLINK:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (unlink(pathBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_RMDIR:
+ if (permissive || AllowOperation(W_OK | X_OK, perms)) {
+ if (rmdir(pathBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_READLINK:
+ if (permissive || AllowOperation(R_OK, perms)) {
+ ssize_t respSize = readlink(pathBuf, (char*)&respBuf, sizeof(respBuf));
+ if (respSize >= 0) {
+ resp.mError = respSize;
+ ios[1].iov_base = &respBuf;
+ ios[1].iov_len = respSize;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+ }
+ } else {
+ MOZ_ASSERT(perms == 0);
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+
+ const size_t numIO = ios[1].iov_len > 0 ? 2 : 1;
+ DebugOnly<const ssize_t> sent = SendWithFd(respfd, ios, numIO, openedFd);
+ close(respfd);
+ MOZ_ASSERT(sent < 0 ||
+ static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len);
+
+ if (openedFd >= 0) {
+ close(openedFd);
+ }
+ }
+}
+
+void
+SandboxBroker::AuditPermissive(int aOp, int aFlags, int aPerms, const char* aPath)
+{
+ MOZ_RELEASE_ASSERT(SandboxInfo::Get().Test(SandboxInfo::kPermissive));
+
+ struct stat statBuf;
+
+ if (lstat(aPath, &statBuf) == 0) {
+ // Path exists, set errno to 0 to indicate "success".
+ errno = 0;
+ }
+
+ SANDBOX_LOG_ERROR("SandboxBroker: would have denied op=%d rflags=%o perms=%d path=%s for pid=%d" \
+ " permissive=1 error=\"%s\"", aOp, aFlags, aPerms,
+ aPath, mChildPid, strerror(errno));
+}
+
+void
+SandboxBroker::AuditDenial(int aOp, int aFlags, int aPerms, const char* aPath)
+{
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("SandboxBroker: denied op=%d rflags=%o perms=%d path=%s for pid=%d" \
+ " error=\"%s\"", aOp, aFlags, aPerms, aPath, mChildPid,
+ strerror(errno));
+ }
+}
+
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBroker.h b/security/sandbox/linux/broker/SandboxBroker.h
new file mode 100644
index 000000000..bb4570a64
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBroker.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxBroker_h
+#define mozilla_SandboxBroker_h
+
+#include "mozilla/SandboxBrokerCommon.h"
+
+#include "base/platform_thread.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+namespace ipc {
+class FileDescriptor;
+}
+
+// This class implements a broker for filesystem operations requested
+// by a sandboxed child process -- opening files and accessing their
+// metadata. (This is necessary in order to restrict access by path;
+// seccomp-bpf can filter only on argument register values, not
+// parameters passed in memory like pathnames.)
+//
+// The broker currently runs on a thread in the parent process (with
+// effective uid changed on B2G), which is for memory efficiency
+// (compared to forking a process) and simplicity (compared to having
+// a separate executable and serializing/deserializing the policy).
+//
+// See also ../SandboxBrokerClient.h for the corresponding client.
+
+class SandboxBroker final
+ : private SandboxBrokerCommon
+ , public PlatformThread::Delegate
+{
+ public:
+ enum Perms {
+ MAY_ACCESS = 1 << 0,
+ MAY_READ = 1 << 1,
+ MAY_WRITE = 1 << 2,
+ MAY_CREATE = 1 << 3,
+ // This flag is for testing policy changes -- when the client is
+ // used with the seccomp-bpf integration, an access to this file
+ // will invoke a crash dump with the context of the syscall.
+ // (This overrides all other flags.)
+ CRASH_INSTEAD = 1 << 4,
+ // Applies to everything below this path, including subdirs created
+ // at runtime
+ RECURSIVE = 1 << 5,
+ };
+ // Bitwise operations on enum values return ints, so just use int in
+ // the hash table type (and below) to avoid cluttering code with casts.
+ typedef nsDataHashtable<nsCStringHashKey, int> PathMap;
+
+ class Policy {
+ PathMap mMap;
+ public:
+ Policy();
+ Policy(const Policy& aOther);
+ ~Policy();
+
+ enum AddCondition {
+ AddIfExistsNow,
+ AddAlways,
+ };
+ // Typically, files that don't exist at policy creation time don't
+ // need to be whitelisted, but this allows adding entries for
+ // them if they'll exist later. See also the overload below.
+ void AddPath(int aPerms, const char* aPath, AddCondition aCond);
+ // This adds all regular files (not directories) in the tree
+ // rooted at the given path.
+ void AddTree(int aPerms, const char* aPath);
+ // A directory, and all files and directories under it, even those
+ // added after creation (the dir itself must exist).
+ void AddDir(int aPerms, const char* aPath);
+ // All files in a directory with a given prefix; useful for devices.
+ void AddPrefix(int aPerms, const char* aDir, const char* aPrefix);
+ // Default: add file if it exists when creating policy or if we're
+ // conferring permission to create it (log files, etc.).
+ void AddPath(int aPerms, const char* aPath) {
+ AddPath(aPerms, aPath,
+ (aPerms & MAY_CREATE) ? AddAlways : AddIfExistsNow);
+ }
+ int Lookup(const nsACString& aPath) const;
+ int Lookup(const char* aPath) const {
+ return Lookup(nsDependentCString(aPath));
+ }
+ private:
+ // ValidatePath checks |path| and returns true if these conditions are met
+ // * Greater than 0 length
+ // * Is an absolute path
+ // * No trailing slash
+ // * No /../ path traversal
+ bool ValidatePath(const char* path) const;
+ };
+
+ // Constructing a broker involves creating a socketpair and a
+ // background thread to handle requests, so it can fail. If this
+ // returns nullptr, do not use the value of aClientFdOut.
+ static UniquePtr<SandboxBroker>
+ Create(UniquePtr<const Policy> aPolicy, int aChildPid,
+ ipc::FileDescriptor& aClientFdOut);
+ virtual ~SandboxBroker();
+
+ private:
+ PlatformThreadHandle mThread;
+ int mFileDesc;
+ const int mChildPid;
+ const UniquePtr<const Policy> mPolicy;
+
+ SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
+ int& aClientFd);
+ void ThreadMain(void) override;
+ void AuditPermissive(int aOp, int aFlags, int aPerms, const char* aPath);
+ void AuditDenial(int aOp, int aFlags, int aPerms, const char* aPath);
+ // Remap relative paths to absolute paths.
+ size_t ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen);
+
+ // Holding a UniquePtr should disallow copying, but to make that explicit:
+ SandboxBroker(const SandboxBroker&) = delete;
+ void operator=(const SandboxBroker&) = delete;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxBroker_h
diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.cpp b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
new file mode 100644
index 000000000..fe7bc8c45
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxBrokerCommon.h"
+
+#include "mozilla/Assertions.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef MSG_CMSG_CLOEXEC
+#ifdef XP_LINUX
+// As always, Android's kernel headers are somewhat old.
+#define MSG_CMSG_CLOEXEC 0x40000000
+#else
+// Most of this code can support other POSIX OSes, but being able to
+// receive fds and atomically make them close-on-exec is important,
+// because this is running in a multithreaded process that can fork.
+// In the future, if the broker becomes a dedicated executable, this
+// can change.
+#error "No MSG_CMSG_CLOEXEC?"
+#endif // XP_LINUX
+#endif // MSG_CMSG_CLOEXEC
+
+namespace mozilla {
+
+/* static */ ssize_t
+SandboxBrokerCommon::RecvWithFd(int aFd, const iovec* aIO, size_t aNumIO,
+ int* aPassedFdPtr)
+{
+ struct msghdr msg = {};
+ msg.msg_iov = const_cast<iovec*>(aIO);
+ msg.msg_iovlen = aNumIO;
+
+ char cmsg_buf[CMSG_SPACE(sizeof(int))];
+ if (aPassedFdPtr) {
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+ *aPassedFdPtr = -1;
+ }
+
+ ssize_t rv;
+ do {
+ // MSG_CMSG_CLOEXEC is needed to prevent the parent process from
+ // accidentally leaking a copy of the child's response socket to a
+ // new child process. (The child won't be able to exec, so this
+ // doesn't matter as much for that direction.)
+ rv = recvmsg(aFd, &msg, MSG_CMSG_CLOEXEC);
+ } while (rv < 0 && errno == EINTR);
+
+ if (rv <= 0) {
+ return rv;
+ }
+ if (msg.msg_controllen > 0) {
+ MOZ_ASSERT(aPassedFdPtr);
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ int* fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
+ // A client could, for example, send an extra 32-bit int if
+ // CMSG_SPACE pads to 64-bit size_t alignment. If so, treat
+ // it as an error, but also don't leak the fds.
+ for (size_t i = 0; CMSG_LEN(sizeof(int) * i) < cmsg->cmsg_len; ++i) {
+ close(fds[i]);
+ }
+ errno = EMSGSIZE;
+ return -1;
+ }
+ *aPassedFdPtr = fds[0];
+ } else {
+ errno = EPROTO;
+ return -1;
+ }
+ }
+ if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
+ if (aPassedFdPtr && *aPassedFdPtr >= 0) {
+ close(*aPassedFdPtr);
+ *aPassedFdPtr = -1;
+ }
+ errno = EMSGSIZE;
+ return -1;
+ }
+
+ return rv;
+}
+
+/* static */ ssize_t
+SandboxBrokerCommon::SendWithFd(int aFd, const iovec* aIO, size_t aNumIO,
+ int aPassedFd)
+{
+ struct msghdr msg = {};
+ msg.msg_iov = const_cast<iovec*>(aIO);
+ msg.msg_iovlen = aNumIO;
+
+ char cmsg_buf[CMSG_SPACE(sizeof(int))];
+ if (aPassedFd != -1) {
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ *reinterpret_cast<int*>(CMSG_DATA(cmsg)) = aPassedFd;
+ }
+
+ ssize_t rv;
+ do {
+ rv = sendmsg(aFd, &msg, MSG_NOSIGNAL);
+ } while (rv < 0 && errno == EINTR);
+
+ return rv;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.h b/security/sandbox/linux/broker/SandboxBrokerCommon.h
new file mode 100644
index 000000000..dbd17e0b9
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxBrokerCommon_h
+#define mozilla_SandboxBrokerCommon_h
+
+#include <sys/types.h>
+
+struct iovec;
+
+// This file defines the protocol between the filesystem broker,
+// described in SandboxBroker.h, and its client, described in
+// ../SandboxBrokerClient.h; and it defines some utility functions
+// used by both.
+//
+// In order to keep the client simple while allowing it to be thread
+// safe and async signal safe, the main broker socket is used only for
+// requests; responses arrive on a per-request socketpair sent with
+// the request. (This technique is also used by Chromium and Breakpad.)
+
+namespace mozilla {
+
+class SandboxBrokerCommon {
+public:
+ enum Operation {
+ SANDBOX_FILE_OPEN,
+ SANDBOX_FILE_ACCESS,
+ SANDBOX_FILE_STAT,
+ SANDBOX_FILE_CHMOD,
+ SANDBOX_FILE_LINK,
+ SANDBOX_FILE_SYMLINK,
+ SANDBOX_FILE_MKDIR,
+ SANDBOX_FILE_RENAME,
+ SANDBOX_FILE_RMDIR,
+ SANDBOX_FILE_UNLINK,
+ SANDBOX_FILE_READLINK,
+ };
+
+ struct Request {
+ Operation mOp;
+ // For open, flags; for access, "mode"; for stat, O_NOFOLLOW for lstat.
+ int mFlags;
+ // Size of return value buffer, if any
+ size_t mBufSize;
+ // The rest of the packet is the pathname.
+ // SCM_RIGHTS for response socket attached.
+ };
+
+ struct Response {
+ // Syscall result, -errno if failure, or 0 for no error
+ int mError;
+ // Followed by struct stat for stat/lstat.
+ // SCM_RIGHTS attached for successful open.
+ };
+
+ // This doesn't need to be the system's maximum path length, just
+ // the largest path that would be allowed by any policy. (It's used
+ // to size a stack-allocated buffer.)
+ static const size_t kMaxPathLen = 4096;
+
+ static ssize_t RecvWithFd(int aFd, const iovec* aIO, size_t aNumIO,
+ int* aPassedFdPtr);
+ static ssize_t SendWithFd(int aFd, const iovec* aIO, size_t aNumIO,
+ int aPassedFd);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxBrokerCommon_h
diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
new file mode 100644
index 000000000..8e698606e
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SandboxBrokerPolicyFactory.h"
+#include "SandboxInfo.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "SpecialSystemDirectory.h"
+
+#ifdef ANDROID
+#include "cutils/properties.h"
+#endif
+
+namespace mozilla {
+
+/* static */ bool
+SandboxBrokerPolicyFactory::IsSystemSupported() {
+#ifdef ANDROID
+ char hardware[PROPERTY_VALUE_MAX];
+ int length = property_get("ro.hardware", hardware, nullptr);
+ // "goldfish" -> emulator. Other devices can be added when we're
+ // reasonably sure they work. Eventually this won't be needed....
+ if (length > 0 && strcmp(hardware, "goldfish") == 0) {
+ return true;
+ }
+
+ // When broker is running in permissive mode, we enable it
+ // automatically regardless of the device.
+ if (SandboxInfo::Get().Test(SandboxInfo::kPermissive)) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+#if defined(MOZ_CONTENT_SANDBOX)
+namespace {
+static const int rdonly = SandboxBroker::MAY_READ;
+static const int wronly = SandboxBroker::MAY_WRITE;
+static const int rdwr = rdonly | wronly;
+static const int rdwrcr = rdwr | SandboxBroker::MAY_CREATE;
+#if defined(MOZ_WIDGET_GONK)
+static const int wrlog = wronly | SandboxBroker::MAY_CREATE;
+#endif
+}
+#endif
+
+SandboxBrokerPolicyFactory::SandboxBrokerPolicyFactory()
+{
+ // Policy entries that are the same in every process go here, and
+ // are cached over the lifetime of the factory.
+#if defined(MOZ_CONTENT_SANDBOX) && defined(MOZ_WIDGET_GONK)
+ SandboxBroker::Policy* policy = new SandboxBroker::Policy;
+
+ // Devices that need write access:
+ policy->AddPath(rdwr, "/dev/genlock"); // bug 980924
+ policy->AddPath(rdwr, "/dev/ashmem"); // bug 980947
+ policy->AddTree(wronly, "/dev/log"); // bug 1199857
+ // Graphics devices are a significant source of attack surface, but
+ // there's not much we can do about it without proxying (which is
+ // very difficult and a perforamnce hit).
+ policy->AddPrefix(rdwr, "/dev", "kgsl"); // bug 995072
+ policy->AddPath(rdwr, "/dev/qemu_pipe"); // but 1198410: goldfish gralloc.
+
+ // Bug 1198475: mochitest logs. (This is actually passed in via URL
+ // query param to the mochitest page, and is configurable, so this
+ // isn't enough in general, but hopefully it's good enough for B2G.)
+ // Conditional on tests being run, using the same check seen in
+ // DirectoryProvider.js to set ProfD.
+ if (access("/data/local/tests/profile", R_OK) == 0) {
+ policy->AddPath(wrlog, "/data/local/tests/log/mochitest.log");
+ }
+
+ // Read-only items below this line.
+
+ policy->AddPath(rdonly, "/dev/urandom"); // bug 964500, bug 995069
+ policy->AddPath(rdonly, "/dev/ion"); // bug 980937
+ policy->AddPath(rdonly, "/proc/cpuinfo"); // bug 995067
+ policy->AddPath(rdonly, "/proc/meminfo"); // bug 1025333
+ policy->AddPath(rdonly, "/sys/devices/system/cpu/present"); // bug 1025329
+ policy->AddPath(rdonly, "/sys/devices/system/soc/soc0/id"); // bug 1025339
+ policy->AddPath(rdonly, "/etc/media_profiles.xml"); // bug 1198419
+ policy->AddPath(rdonly, "/etc/media_codecs.xml"); // bug 1198460
+ policy->AddTree(rdonly, "/system/fonts"); // bug 1026063
+
+ // Bug 1199051 (crossplatformly, this is NS_GRE_DIR).
+ policy->AddTree(rdonly, "/system/b2g");
+
+ // Bug 1026356: dynamic library loading from assorted frameworks we
+ // don't control (media codecs, maybe others).
+ //
+ // Bug 1198515: Also, the profiler calls breakpad code to get info
+ // on all loaded ELF objects, which opens those files.
+ policy->AddTree(rdonly, "/system/lib");
+ policy->AddTree(rdonly, "/vendor/lib");
+ policy->AddPath(rdonly, "/system/bin/linker"); // (profiler only)
+
+ // Bug 1199866: EGL/WebGL.
+ policy->AddPath(rdonly, "/system/lib/egl");
+ policy->AddPath(rdonly, "/vendor/lib/egl");
+
+ // Bug 1198401: timezones. Yes, we need both of these; see bug.
+ policy->AddTree(rdonly, "/system/usr/share/zoneinfo");
+ policy->AddTree(rdonly, "/system//usr/share/zoneinfo");
+
+ policy->AddPath(rdonly, "/data/local/tmp/profiler.options",
+ SandboxBroker::Policy::AddAlways); // bug 1029337
+
+ mCommonContentPolicy.reset(policy);
+#elif defined(MOZ_CONTENT_SANDBOX)
+ SandboxBroker::Policy* policy = new SandboxBroker::Policy;
+ policy->AddDir(rdonly, "/");
+ policy->AddDir(rdwrcr, "/dev/shm");
+ // Add write permissions on the temporary directory. This can come
+ // from various environment variables (TMPDIR,TMP,TEMP,...) so
+ // make sure to use the full logic.
+ nsCOMPtr<nsIFile> tmpDir;
+ nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
+ getter_AddRefs(tmpDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = tmpDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdwrcr, tmpPath.get());
+ }
+ }
+ // If the above fails at any point, fall back to a very good guess.
+ if (NS_FAILED(rv)) {
+ policy->AddDir(rdwrcr, "/tmp");
+ }
+
+ // Bug 1308851: NVIDIA proprietary driver when using WebGL
+ policy->AddPrefix(rdwr, "/dev", "nvidia");
+
+ // Bug 1312678: radeonsi/Intel with DRI when using WebGL
+ policy->AddDir(rdwr, "/dev/dri");
+
+ mCommonContentPolicy.reset(policy);
+#endif
+}
+
+#ifdef MOZ_CONTENT_SANDBOX
+UniquePtr<SandboxBroker::Policy>
+SandboxBrokerPolicyFactory::GetContentPolicy(int aPid)
+{
+ // Policy entries that vary per-process (currently the only reason
+ // that can happen is because they contain the pid) are added here.
+
+ MOZ_ASSERT(NS_IsMainThread());
+ // File broker usage is controlled through a pref.
+ if (Preferences::GetInt("security.sandbox.content.level") <= 1) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mCommonContentPolicy);
+#if defined(MOZ_WIDGET_GONK)
+ // Allow overriding "unsupported"ness with a pref, for testing.
+ if (!IsSystemSupported()) {
+ return nullptr;
+ }
+ UniquePtr<SandboxBroker::Policy>
+ policy(new SandboxBroker::Policy(*mCommonContentPolicy));
+
+ // Bug 1029337: where the profiler writes the data.
+ nsPrintfCString profilerLogPath("/data/local/tmp/profile_%d_%d.txt",
+ GeckoProcessType_Content, aPid);
+ policy->AddPath(wrlog, profilerLogPath.get());
+
+ // Bug 1198550: the profiler's replacement for dl_iterate_phdr
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/maps", aPid).get());
+
+ // Bug 1198552: memory reporting.
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get());
+ policy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get());
+
+ return policy;
+#else
+ UniquePtr<SandboxBroker::Policy>
+ policy(new SandboxBroker::Policy(*mCommonContentPolicy));
+ // Return the common policy.
+ return policy;
+#endif
+}
+
+#endif // MOZ_CONTENT_SANDBOX
+} // namespace mozilla
diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h
new file mode 100644
index 000000000..c66a09189
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxBrokerPolicyFactory_h
+#define mozilla_SandboxBrokerPolicyFactory_h
+
+#include "mozilla/SandboxBroker.h"
+
+namespace mozilla {
+
+class SandboxBrokerPolicyFactory {
+public:
+ SandboxBrokerPolicyFactory();
+
+#ifdef MOZ_CONTENT_SANDBOX
+ UniquePtr<SandboxBroker::Policy> GetContentPolicy(int aPid);
+#endif
+
+private:
+ UniquePtr<const SandboxBroker::Policy> mCommonContentPolicy;
+ // B2G devices tend to have hardware-specific paths used by device
+ // drivers, so rollout of filesystem isolation will need per-device
+ // testing. This predicate allows that to happen gradually.
+ static bool IsSystemSupported();
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxBrokerPolicyFactory_h
diff --git a/security/sandbox/linux/broker/SandboxBrokerUtils.h b/security/sandbox/linux/broker/SandboxBrokerUtils.h
new file mode 100644
index 000000000..1db4f4411
--- /dev/null
+++ b/security/sandbox/linux/broker/SandboxBrokerUtils.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_SandboxBrokerUtils_h
+#define mozilla_SandboxBrokerUtils_h
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+// On 32-bit Linux, stat calls are translated by libc into stat64
+// calls. We'll intercept those and handle them in the stat functions
+// but must be sure to use the right structure layout.
+
+#if defined(__NR_stat64)
+typedef struct stat64 statstruct;
+#define statsyscall stat64
+#define lstatsyscall lstat64
+#elif defined(__NR_stat)
+typedef struct stat statstruct;
+#define statsyscall stat
+#define lstatsyscall lstat
+#else
+#error Missing stat syscall include.
+#endif
+
+#endif // mozilla_SandboxBrokerUtils_h
diff --git a/security/sandbox/linux/broker/moz.build b/security/sandbox/linux/broker/moz.build
new file mode 100644
index 000000000..343a5cfad
--- /dev/null
+++ b/security/sandbox/linux/broker/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; python-indent: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla += [
+ 'SandboxBroker.h',
+ 'SandboxBrokerCommon.h',
+ 'SandboxBrokerPolicyFactory.h',
+]
+
+SOURCES += [
+ 'SandboxBroker.cpp',
+ 'SandboxBrokerCommon.cpp',
+ 'SandboxBrokerPolicyFactory.cpp',
+]
+
+if CONFIG['OS_TARGET'] == 'Android':
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ DEFINES['HAVE_ANDROID_OS'] = True
+
+LOCAL_INCLUDES += [
+ '/security/sandbox/linux', # SandboxLogging.h, SandboxInfo.h
+]
+
+# Need this for mozilla::ipc::FileDescriptor etc.
+include('/ipc/chromium/chromium-config.mozbuild')
+
+# Need this for safe_sprintf.h used by SandboxLogging.h,
+# but it has to be after ipc/chromium/src.
+LOCAL_INCLUDES += [
+ '/security/sandbox/chromium',
+]
+
+
+FINAL_LIBRARY = 'xul'