mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 930258 - Part 1: The file broker, and unit tests for it. r=kang f=froydnj
This commit is contained in:
parent
a8f27d9556
commit
6a682f52f9
181
security/sandbox/linux/SandboxBrokerClient.cpp
Normal file
181
security/sandbox/linux/SandboxBrokerClient.cpp
Normal 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
|
||||
|
46
security/sandbox/linux/SandboxBrokerClient.h
Normal file
46
security/sandbox/linux/SandboxBrokerClient.h
Normal 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
|
404
security/sandbox/linux/broker/SandboxBroker.cpp
Normal file
404
security/sandbox/linux/broker/SandboxBroker.cpp
Normal 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
|
123
security/sandbox/linux/broker/SandboxBroker.h
Normal file
123
security/sandbox/linux/broker/SandboxBroker.h
Normal 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
|
120
security/sandbox/linux/broker/SandboxBrokerCommon.cpp
Normal file
120
security/sandbox/linux/broker/SandboxBrokerCommon.cpp
Normal 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
|
61
security/sandbox/linux/broker/SandboxBrokerCommon.h
Normal file
61
security/sandbox/linux/broker/SandboxBrokerCommon.h
Normal 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
|
32
security/sandbox/linux/broker/moz.build
Normal file
32
security/sandbox/linux/broker/moz.build
Normal 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'
|
440
security/sandbox/linux/gtest/TestBroker.cpp
Normal file
440
security/sandbox/linux/gtest/TestBroker.cpp
Normal 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
|
60
security/sandbox/linux/gtest/TestBrokerPolicy.cpp
Normal file
60
security/sandbox/linux/gtest/TestBrokerPolicy.cpp
Normal 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
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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',
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user