Bug 930258 - Part 1: The file broker, and unit tests for it. r=kang f=froydnj

This commit is contained in:
Jed Davis 2015-10-07 22:13:08 -07:00
parent a8f27d9556
commit 6a682f52f9
11 changed files with 1480 additions and 0 deletions

View File

@ -0,0 +1,181 @@
/* -*- 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,
struct stat* aStat, 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.)
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[2];
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);
if (ios[1].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, 2, respFds[1]);
const int sendErrno = errno;
MOZ_ASSERT(sent < 0 ||
static_cast<size_t>(sent) == ios[0].iov_len + ios[1].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 (aStat) {
ios[1].iov_base = aStat;
ios[1].iov_len = sizeof(*aStat);
} 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, aStat ? 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;
}
if (resp.mError != 0) {
// If the operation fails, the return payload will be empty;
// adjust the iov_len for the following assertion.
ios[1].iov_len = 0;
}
MOZ_ASSERT(static_cast<size_t>(recvd) == ios[0].iov_len + ios[1].iov_len);
if (resp.mError == 0) {
// Success!
if (expectFd) {
MOZ_ASSERT(openedFd >= 0);
return openedFd;
}
return 0;
}
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 };
int maybeFd = DoCall(&req, aPath, 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 };
return DoCall(&req, aPath, nullptr, false);
}
int
SandboxBrokerClient::Stat(const char* aPath, struct stat* aStat)
{
Request req = { SANDBOX_FILE_STAT, 0 };
return DoCall(&req, aPath, aStat, false);
}
int
SandboxBrokerClient::LStat(const char* aPath, struct stat* aStat)
{
Request req = { SANDBOX_FILE_STAT, O_NOFOLLOW };
return DoCall(&req, aPath, aStat, false);
}
} // namespace mozilla

View File

@ -0,0 +1,46 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_SandboxBrokerClient_h
#define mozilla_SandboxBrokerClient_h
#include "broker/SandboxBrokerCommon.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, struct stat* aStat);
int LStat(const char* aPath, struct stat* aStat);
private:
int mFileDesc;
int DoCall(const Request* aReq, const char* aPath, struct stat* aStat,
bool expectFd);
};
} // namespace mozilla
#endif // mozilla_SandboxBrokerClient_h

View File

@ -0,0 +1,404 @@
/* -*- 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 <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/ipc/FileDescriptor.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());
}
}
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::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
{
return mMap.Get(aPath);
}
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, struct stat* aStat, int aFlags)
{
if (aFlags & O_NOFOLLOW) {
return lstat(aPath, aStat);
}
return stat(aPath, aStat);
}
void
SandboxBroker::ThreadMain(void)
{
char threadName[16];
snprintf(threadName, sizeof(threadName), "FS Broker %d", mChildPid);
PlatformThread::SetName(threadName);
#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];
char pathBuf[kMaxPathLen + 1];
size_t pathLen;
struct stat statBuf;
Request req;
Response resp;
int respfd;
ios[0].iov_base = &req;
ios[0].iov_len = sizeof(req);
ios[1].iov_base = pathBuf;
ios[1].iov_len = kMaxPathLen;
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(&statBuf, 0, sizeof(statBuf));
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;
// Look up the pathname.
pathLen = recvd - sizeof(req);
// It shouldn't be possible for recvmsg to violate this assertion,
// but one more predictable branch shouldn't have much perf impact:
MOZ_RELEASE_ASSERT(pathLen <= kMaxPathLen);
pathBuf[pathLen] = '\0';
int perms = 0;
if (!memchr(pathBuf, '\0', pathLen)) {
perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
}
// And now perform the operation if allowed.
if (perms & CRASH_INSTEAD) {
// This is somewhat nonmodular, but it works.
resp.mError = ENOSYS;
} else if (perms & MAY_ACCESS) {
switch(req.mOp) {
case SANDBOX_FILE_OPEN:
if (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;
}
}
break;
case SANDBOX_FILE_ACCESS:
if (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, &statBuf) == 0) {
resp.mError = 0;
} else {
resp.mError = errno;
}
}
break;
case SANDBOX_FILE_STAT:
if (DoStat(pathBuf, &statBuf, req.mFlags) == 0) {
resp.mError = 0;
ios[1].iov_base = &statBuf;
ios[1].iov_len = sizeof(statBuf);
} else {
resp.mError = errno;
}
break;
}
} else {
MOZ_ASSERT(perms == 0);
}
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);
}
}
}
} // namespace mozilla

View File

@ -0,0 +1,123 @@
/* -*- 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 policy is currently just a map from strings to sets of
// permissions; the broker doesn't attempt to interpret or
// canonicalize pathnames. This makes the broker simpler, and thus
// less likely to contain vulnerabilities a compromised client could
// exploit. (This might need to change in the future if we need to
// whitelist a set of files that could change after policy
// construction, like hotpluggable devices.)
//
// 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,
};
// 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);
// 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));
}
};
// 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;
// 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,61 @@
/* -*- 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_SandboxBrokerTypes_h
#define mozilla_SandboxBrokerTypes_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,
};
struct Request {
Operation mOp;
// For open, flags; for access, "mode"; for stat, O_NOFOLLOW for lstat.
int mFlags;
// The rest of the packet is the pathname.
// SCM_RIGHTS for response socket attached.
};
struct Response {
int mError; // errno, or 0 for no error
// 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_SandboxBrokerTypes_h

View File

@ -0,0 +1,32 @@
# -*- 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',
]
SOURCES += [
'SandboxBroker.cpp',
'SandboxBrokerCommon.cpp',
]
LOCAL_INCLUDES += [
'/security/sandbox/linux', # SandboxLogging.h
'/security/sandbox/linux/common', # 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,440 @@
/* -*- 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 "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, struct stat* aStat) {
return mClient->Stat(aPath, aStat);
}
int LStat(const char* aPath, struct stat* aStat) {
return mClient->LStat(aPath, aStat);
}
virtual void SetUp() {
ipc::FileDescriptor fd;
mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd);
ASSERT_NE(mServer, nullptr);
ASSERT_TRUE(fd.IsValid());
mClient.reset(new SandboxBrokerClient(dup(fd.PlatformHandle())));
}
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.
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)
{
struct stat brokeredStat, realStat;
ASSERT_EQ(0, stat("/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)
{
struct stat brokeredStat, realStat;
ASSERT_EQ(0, lstat("/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));
}
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;
struct stat nullStat, zeroStat, selfStat;
dev_t realNullDev, realZeroDev;
ino_t realSelfInode;
ASSERT_EQ(0, stat("/dev/null", &nullStat)) << "Shouldn't ever fail!";
ASSERT_EQ(0, stat("/dev/zero", &zeroStat)) << "Shouldn't ever fail!";
ASSERT_EQ(0, lstat("/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,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 "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 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.";
}
} // namespace mozilla

View File

@ -7,7 +7,10 @@
Library('sandboxtest')
SOURCES = [
'../SandboxBrokerClient.cpp',
'../SandboxUtil.cpp',
'TestBroker.cpp',
'TestBrokerPolicy.cpp',
'TestSandboxUtil.cpp',
]
@ -15,6 +18,9 @@ LOCAL_INCLUDES += [
'/security/sandbox/linux',
'/security/sandbox/linux/common',
]
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'/security/sandbox/chromium',
]

View File

@ -55,8 +55,10 @@ SOURCES += [
'../chromium/sandbox/linux/seccomp-bpf/syscall.cc',
'../chromium/sandbox/linux/seccomp-bpf/syscall_iterator.cc',
'../chromium/sandbox/linux/seccomp-bpf/trap.cc',
'broker/SandboxBrokerCommon.cpp',
'LinuxCapabilities.cpp',
'Sandbox.cpp',
'SandboxBrokerClient.cpp',
'SandboxChroot.cpp',
'SandboxFilter.cpp',
'SandboxFilterUtil.cpp',
@ -90,7 +92,12 @@ if CONFIG['OS_TARGET'] != 'Android':
'rt',
]
USE_LIBS += [
'mfbt',
]
DIRS += [
'broker',
'common',
'glue',
]