diff --git a/security/sandbox/linux/SandboxBrokerClient.cpp b/security/sandbox/linux/SandboxBrokerClient.cpp new file mode 100644 index 00000000000..c6d4ebf08f6 --- /dev/null +++ b/security/sandbox/linux/SandboxBrokerClient.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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(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(aReq); + ios[0].iov_len = sizeof(*aReq); + ios[1].iov_base = const_cast(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(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(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 + diff --git a/security/sandbox/linux/SandboxBrokerClient.h b/security/sandbox/linux/SandboxBrokerClient.h new file mode 100644 index 00000000000..9a1ff9e6d71 --- /dev/null +++ b/security/sandbox/linux/SandboxBrokerClient.h @@ -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 diff --git a/security/sandbox/linux/broker/SandboxBroker.cpp b/security/sandbox/linux/broker/SandboxBroker.cpp new file mode 100644 index 00000000000..a465e33ff50 --- /dev/null +++ b/security/sandbox/linux/broker/SandboxBroker.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +#ifdef XP_LINUX +#include +#endif + +#ifdef MOZ_WIDGET_GONK +#include +#include +#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 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::Create(UniquePtr aPolicy, int aChildPid, + ipc::FileDescriptor& aClientFdOut) +{ + int clientFd; + // Can't use MakeUnique here because the constructor is private. + UniquePtr 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(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 sent = SendWithFd(respfd, ios, numIO, openedFd); + close(respfd); + MOZ_ASSERT(sent < 0 || + static_cast(sent) == ios[0].iov_len + ios[1].iov_len); + + if (openedFd >= 0) { + close(openedFd); + } + } +} + +} // namespace mozilla diff --git a/security/sandbox/linux/broker/SandboxBroker.h b/security/sandbox/linux/broker/SandboxBroker.h new file mode 100644 index 00000000000..0e78d43557d --- /dev/null +++ b/security/sandbox/linux/broker/SandboxBroker.h @@ -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 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 + Create(UniquePtr aPolicy, int aChildPid, + ipc::FileDescriptor& aClientFdOut); + virtual ~SandboxBroker(); + + private: + PlatformThreadHandle mThread; + int mFileDesc; + const int mChildPid; + const UniquePtr mPolicy; + + SandboxBroker(UniquePtr 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 diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.cpp b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp new file mode 100644 index 00000000000..fe7bc8c45b0 --- /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 +#include +#include +#include + +#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(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(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(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(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 00000000000..e52b8b45f45 --- /dev/null +++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h @@ -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 + +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 diff --git a/security/sandbox/linux/broker/moz.build b/security/sandbox/linux/broker/moz.build new file mode 100644 index 00000000000..620d0011dfa --- /dev/null +++ b/security/sandbox/linux/broker/moz.build @@ -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' diff --git a/security/sandbox/linux/gtest/TestBroker.cpp b/security/sandbox/linux/gtest/TestBroker.cpp new file mode 100644 index 00000000000..0fbe8fb4d1d --- /dev/null +++ b/security/sandbox/linux/gtest/TestBroker.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 mServer; + UniquePtr mClient; + + UniquePtr GetPolicy() const; + + template + static void* ThreadMain(void* arg) { + (static_cast(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 + void StartThread(pthread_t *aThread) { + ASSERT_EQ(0, pthread_create(aThread, nullptr, ThreadMain, + static_cast(this))); + } + + template + void RunOnManyThreads() { + static const int kNumThreads = 5; + pthread_t threads[kNumThreads]; + for (int i = 0; i < kNumThreads; ++i) { + StartThread(&threads[i]); + } + for (int i = 0; i < kNumThreads; ++i) { + void* retval; + ASSERT_EQ(pthread_join(threads[i], &retval), 0); + ASSERT_EQ(retval, static_cast(nullptr)); + } + } + +public: + void MultiThreadOpenWorker(); + void MultiThreadStatWorker(); +}; + +UniquePtr +SandboxBrokerTest::GetPolicy() const +{ + UniquePtr 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(); +} +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(); +} +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 mVoidPtr; + + static void SigHandler(int aSigNum, siginfo_t* aSigInfo, void *aCtx) { + ASSERT_EQ(SI_QUEUE, aSigInfo->si_code); + SandboxBrokerSigStress* that = + static_cast(aSigInfo->si_value.sival_ptr); + ASSERT_EQ(that->mSigNum, aSigNum); + that->DoSomething(); + } + +protected: + Atomic 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(&threads[0]); + StartThread(&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(mTestIter)); + switch (mTestIter % 5) { + case 0: + fd = Open("/dev/null", O_RDWR); + ASSERT_GE(fd, 0); + ASSERT_EQ(0, read(fd, &c, 1)); + close(fd); + break; + case 1: + fd = Open("/dev/zero", O_RDONLY); + ASSERT_GE(fd, 0); + ASSERT_EQ(1, read(fd, &c, 1)); + ASSERT_EQ('\0', c); + close(fd); + break; + case 2: + ASSERT_EQ(0, Access("/dev/null", W_OK)); + break; + case 3: + ASSERT_EQ(0, Stat("/proc/self", &st)); + ASSERT_TRUE(S_ISDIR(st.st_mode)); + break; + case 4: + ASSERT_EQ(0, LStat("/proc/self", &st)); + ASSERT_TRUE(S_ISLNK(st.st_mode)); + break; + } + mTestIter--; + sem_post(&mSemaphore); +} +#endif + +} // namespace mozilla diff --git a/security/sandbox/linux/gtest/TestBrokerPolicy.cpp b/security/sandbox/linux/gtest/TestBrokerPolicy.cpp new file mode 100644 index 00000000000..4eb66f1888a --- /dev/null +++ b/security/sandbox/linux/gtest/TestBrokerPolicy.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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 + diff --git a/security/sandbox/linux/gtest/moz.build b/security/sandbox/linux/gtest/moz.build index 9c2eab28c85..a0ac3ff5946 100644 --- a/security/sandbox/linux/gtest/moz.build +++ b/security/sandbox/linux/gtest/moz.build @@ -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', ] diff --git a/security/sandbox/linux/moz.build b/security/sandbox/linux/moz.build index d8acd030682..712269b4b6c 100644 --- a/security/sandbox/linux/moz.build +++ b/security/sandbox/linux/moz.build @@ -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', ]