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')
|
Library('sandboxtest')
|
||||||
|
|
||||||
SOURCES = [
|
SOURCES = [
|
||||||
|
'../SandboxBrokerClient.cpp',
|
||||||
'../SandboxUtil.cpp',
|
'../SandboxUtil.cpp',
|
||||||
|
'TestBroker.cpp',
|
||||||
|
'TestBrokerPolicy.cpp',
|
||||||
'TestSandboxUtil.cpp',
|
'TestSandboxUtil.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -15,6 +18,9 @@ LOCAL_INCLUDES += [
|
|||||||
'/security/sandbox/linux',
|
'/security/sandbox/linux',
|
||||||
'/security/sandbox/linux/common',
|
'/security/sandbox/linux/common',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
include('/ipc/chromium/chromium-config.mozbuild')
|
||||||
|
|
||||||
LOCAL_INCLUDES += [
|
LOCAL_INCLUDES += [
|
||||||
'/security/sandbox/chromium',
|
'/security/sandbox/chromium',
|
||||||
]
|
]
|
||||||
|
@ -55,8 +55,10 @@ SOURCES += [
|
|||||||
'../chromium/sandbox/linux/seccomp-bpf/syscall.cc',
|
'../chromium/sandbox/linux/seccomp-bpf/syscall.cc',
|
||||||
'../chromium/sandbox/linux/seccomp-bpf/syscall_iterator.cc',
|
'../chromium/sandbox/linux/seccomp-bpf/syscall_iterator.cc',
|
||||||
'../chromium/sandbox/linux/seccomp-bpf/trap.cc',
|
'../chromium/sandbox/linux/seccomp-bpf/trap.cc',
|
||||||
|
'broker/SandboxBrokerCommon.cpp',
|
||||||
'LinuxCapabilities.cpp',
|
'LinuxCapabilities.cpp',
|
||||||
'Sandbox.cpp',
|
'Sandbox.cpp',
|
||||||
|
'SandboxBrokerClient.cpp',
|
||||||
'SandboxChroot.cpp',
|
'SandboxChroot.cpp',
|
||||||
'SandboxFilter.cpp',
|
'SandboxFilter.cpp',
|
||||||
'SandboxFilterUtil.cpp',
|
'SandboxFilterUtil.cpp',
|
||||||
@ -90,7 +92,12 @@ if CONFIG['OS_TARGET'] != 'Android':
|
|||||||
'rt',
|
'rt',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
USE_LIBS += [
|
||||||
|
'mfbt',
|
||||||
|
]
|
||||||
|
|
||||||
DIRS += [
|
DIRS += [
|
||||||
|
'broker',
|
||||||
'common',
|
'common',
|
||||||
'glue',
|
'glue',
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user