--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1003,17 +1003,17 @@ pref("security.sandbox.content.level", 1
// to whitelist more system calls.
//
// So the purpose of this setting is to allow nightly users to disable the
// sandbox while we fix their problems. This way, they won't have to wait for
// another nightly release which disables seccomp-bpf again.
//
// This setting may not be required anymore once we decide to permanently
// enable the content sandbox.
-pref("security.sandbox.content.level", 1);
+pref("security.sandbox.content.level", 2);
#endif
#if defined(XP_MACOSX) || defined(XP_WIN)
#if defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
// ID (a UUID when set by gecko) that is used to form the name of a
// sandbox-writable temporary directory to be used by content processes
// when a temporary writable file is required in a level 1 sandbox.
pref("security.sandbox.content.tempDirSuffix", "");
--- a/security/sandbox/linux/SandboxBrokerClient.cpp
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -15,38 +15,41 @@
#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"
+#include "sandbox/linux/system_headers/linux_syscalls.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)
+ const char* aPath2, void* aResponseBuff,
+ 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.)
+ // We only remap the first path.
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 =
@@ -57,125 +60,205 @@ SandboxBrokerClient::DoCall(const Reques
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];
+ struct iovec ios[3];
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);
+ ios[1].iov_len = strlen(path) + 1;
+ if (aPath2 != nullptr) {
+ ios[2].iov_base = const_cast<char*>(aPath2);
+ ios[2].iov_len = strlen(aPath2) + 1;
+ } else {
+ ios[2].iov_base = 0;
+ ios[2].iov_len = 0;
+ }
if (ios[1].iov_len > kMaxPathLen) {
return -ENAMETOOLONG;
}
+ if (ios[2].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 ssize_t sent = SendWithFd(mFileDesc, ios, 3, respFds[1]);
const int sendErrno = errno;
MOZ_ASSERT(sent < 0 ||
- static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len);
+ static_cast<size_t>(sent) == ios[0].iov_len
+ + ios[1].iov_len
+ + ios[2].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);
+ if (aResponseBuff) {
+ ios[1].iov_base = aResponseBuff;
+ ios[1].iov_len = aReq->mBufSize;
} 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,
+ const ssize_t recvd = RecvWithFd(respFds[0], ios, aResponseBuff ? 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) {
+ MOZ_ASSERT(static_cast<size_t>(recvd) <= ios[0].iov_len + ios[1].iov_len);
+ // Some calls such as readlink return a size if successful
+ if (resp.mError >= 0) {
// Success!
if (expectFd) {
MOZ_ASSERT(openedFd >= 0);
return openedFd;
}
- return 0;
+ return resp.mError;
}
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;
+ return resp.mError;
}
int
SandboxBrokerClient::Open(const char* aPath, int aFlags)
{
- Request req = { SANDBOX_FILE_OPEN, aFlags };
- int maybeFd = DoCall(&req, aPath, nullptr, true);
+ Request req = { SANDBOX_FILE_OPEN, aFlags, 0 };
+ int maybeFd = DoCall(&req, aPath, nullptr, 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);
+ Request req = { SANDBOX_FILE_ACCESS, aMode, 0 };
+ return DoCall(&req, aPath, nullptr, nullptr, false);
}
int
SandboxBrokerClient::Stat(const char* aPath, struct stat* aStat)
{
- Request req = { SANDBOX_FILE_STAT, 0 };
- return DoCall(&req, aPath, aStat, false);
+ // This is actually stat64 on 32-bit Linux, so adjust the
+ // struct to have the right expected size.
+#if defined(__NR_stat64)
+ Request req = { SANDBOX_FILE_STAT, 0, sizeof(struct stat64) };
+#elif defined(__NR_stat)
+ Request req = { SANDBOX_FILE_STAT, 0, sizeof(struct stat) };
+#else
+#error Missing include.
+#endif
+ return DoCall(&req, aPath, nullptr, (void*)aStat, false);
}
int
SandboxBrokerClient::LStat(const char* aPath, struct stat* aStat)
{
- Request req = { SANDBOX_FILE_STAT, O_NOFOLLOW };
- return DoCall(&req, aPath, aStat, false);
+ // This is actually stat64 on 32-bit Linux, so adjust the
+ // struct to have the right expected size.
+#if defined(__NR_stat64)
+ Request req = { SANDBOX_FILE_STAT, O_NOFOLLOW, sizeof(struct stat64) };
+#elif defined(__NR_stat)
+ Request req = { SANDBOX_FILE_STAT, O_NOFOLLOW, sizeof(struct stat) };
+#else
+#error Missing include.
+#endif
+ return DoCall(&req, aPath, nullptr, (void*)aStat, false);
+}
+
+int
+SandboxBrokerClient::Chmod(const char* aPath, int aMode)
+{
+ Request req = {SANDBOX_FILE_CHMOD, aMode, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Link(const char* aOldPath, const char* aNewPath)
+{
+ Request req = {SANDBOX_FILE_LINK, 0, 0};
+ return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Symlink(const char* aOldPath, const char* aNewPath)
+{
+ Request req = {SANDBOX_FILE_SYMLINK, 0, 0};
+ return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Rename(const char* aOldPath, const char* aNewPath)
+{
+ Request req = {SANDBOX_FILE_RENAME, 0, 0};
+ return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Mkdir(const char* aPath, int aMode)
+{
+ Request req = {SANDBOX_FILE_MKDIR, aMode, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Unlink(const char* aPath)
+{
+ Request req = {SANDBOX_FILE_UNLINK, 0, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Rmdir(const char* aPath)
+{
+ Request req = {SANDBOX_FILE_RMDIR, 0, 0};
+ return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Readlink(const char* aPath, void* aBuff, size_t aSize)
+{
+ Request req = {SANDBOX_FILE_READLINK, 0, aSize};
+ return DoCall(&req, aPath, nullptr, aBuff, false);
}
} // namespace mozilla
--- a/security/sandbox/linux/SandboxBrokerClient.h
+++ b/security/sandbox/linux/SandboxBrokerClient.h
@@ -28,19 +28,30 @@ class SandboxBrokerClient final : privat
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);
+ int Chmod(const char* aPath, int aMode);
+ int Link(const char* aPath, const char* aPath2);
+ int Mkdir(const char* aPath, int aMode);
+ int Symlink(const char* aOldPath, const char* aNewPath);
+ int Rename(const char* aOldPath, const char* aNewPath);
+ int Unlink(const char* aPath);
+ int Rmdir(const char* aPath);
+ int Readlink(const char* aPath, void* aBuf, size_t aBufSize);
private:
int mFileDesc;
- int DoCall(const Request* aReq, const char* aPath, struct stat* aStat,
+ int DoCall(const Request* aReq,
+ const char* aPath,
+ const char* aPath2,
+ void *aReponseBuff,
bool expectFd);
};
} // namespace mozilla
#endif // mozilla_SandboxBrokerClient_h
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -410,16 +410,71 @@ class ContentSandboxPolicy : public Sand
(flags & ~AT_SYMLINK_NOFOLLOW), fd, path, buf, flags);
return BlockedSyscallTrap(aArgs, nullptr);
}
return (flags & AT_SYMLINK_NOFOLLOW) == 0
? broker->Stat(path, buf)
: broker->LStat(path, buf);
}
+ static intptr_t ChmodTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto mode = static_cast<mode_t>(aArgs.args[1]);
+ return broker->Chmod(path, mode);
+ }
+
+ static intptr_t LinkTrap(ArgsRef aArgs, void *aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
+ return broker->Link(path, path2);
+ }
+
+ static intptr_t SymlinkTrap(ArgsRef aArgs, void *aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
+ return broker->Symlink(path, path2);
+ }
+
+ static intptr_t RenameTrap(ArgsRef aArgs, void *aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
+ return broker->Rename(path, path2);
+ }
+
+ static intptr_t MkdirTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto mode = static_cast<mode_t>(aArgs.args[1]);
+ return broker->Mkdir(path, mode);
+ }
+
+ static intptr_t RmdirTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ return broker->Rmdir(path);
+ }
+
+ static intptr_t UnlinkTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ return broker->Unlink(path);
+ }
+
+ static intptr_t ReadlinkTrap(ArgsRef aArgs, void* aux) {
+ auto broker = static_cast<SandboxBrokerClient*>(aux);
+ auto path = reinterpret_cast<const char*>(aArgs.args[0]);
+ auto buf = reinterpret_cast<char*>(aArgs.args[1]);
+ auto size = static_cast<size_t>(aArgs.args[2]);
+ return broker->Readlink(path, buf, size);
+ }
+
static intptr_t GetPPidTrap(ArgsRef aArgs, void* aux) {
// In a pid namespace, getppid() will return 0. We will return 0 instead
// of the real parent pid to see what breaks when we introduce the
// pid namespace (Bug 1151624).
return 0;
}
public:
@@ -508,16 +563,32 @@ public:
case __NR_faccessat:
return Trap(AccessAtTrap, mBroker);
CASES_FOR_stat:
return Trap(StatTrap, mBroker);
CASES_FOR_lstat:
return Trap(LStatTrap, mBroker);
CASES_FOR_fstatat:
return Trap(StatAtTrap, mBroker);
+ case __NR_chmod:
+ return Trap(ChmodTrap, mBroker);
+ case __NR_link:
+ return Trap(LinkTrap, mBroker);
+ case __NR_mkdir:
+ return Trap(MkdirTrap, mBroker);
+ case __NR_symlink:
+ return Trap(SymlinkTrap, mBroker);
+ case __NR_rename:
+ return Trap(RenameTrap, mBroker);
+ case __NR_rmdir:
+ return Trap(RmdirTrap, mBroker);
+ case __NR_unlink:
+ return Trap(UnlinkTrap, mBroker);
+ case __NR_readlink:
+ return Trap(ReadlinkTrap, mBroker);
}
} else {
// No broker; allow the syscalls directly. )-:
switch(sysno) {
case __NR_open:
case __NR_openat:
case __NR_access:
case __NR_faccessat:
@@ -530,34 +601,26 @@ public:
switch (sysno) {
#ifdef DESKTOP
case __NR_getppid:
return Trap(GetPPidTrap, nullptr);
// Filesystem syscalls that need more work to determine who's
// using them, if they need to be, and what we intend to about it.
- case __NR_mkdir:
- case __NR_rmdir:
case __NR_getcwd:
CASES_FOR_statfs:
CASES_FOR_fstatfs:
- case __NR_chmod:
- case __NR_rename:
- case __NR_symlink:
case __NR_quotactl:
- case __NR_link:
- case __NR_unlink:
CASES_FOR_fchown:
case __NR_fchmod:
case __NR_flock:
#endif
return Allow();
- case __NR_readlink:
case __NR_readlinkat:
#ifdef DESKTOP
// Bug 1290896
return Allow();
#else
// Workaround for bug 964455:
return Error(EINVAL);
#endif
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -26,16 +26,17 @@
#endif
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Move.h"
#include "mozilla/NullPtr.h"
#include "mozilla/Sprintf.h"
#include "mozilla/ipc/FileDescriptor.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
namespace mozilla {
// This constructor signals failure by setting mFileDesc and aClientFd to -1.
SandboxBroker::SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
int& aClientFd)
: mChildPid(aChildPid), mPolicy(Move(aPolicy))
{
@@ -278,16 +279,35 @@ SandboxBroker::Policy::Lookup(const nsAC
}
// Strip away the RECURSIVE flag as it doesn't
// necessarily apply to aPath.
return allPerms & ~RECURSIVE;
}
static bool
+AllowOperation(int aReqFlags, int aPerms)
+{
+ int needed = 0;
+ if (aReqFlags & R_OK) {
+ needed |= SandboxBroker::MAY_READ;
+ }
+ if (aReqFlags & W_OK) {
+ needed |= SandboxBroker::MAY_WRITE;
+ }
+ // We don't really allow executing anything,
+ // so in true unix tradition we hijack this
+ // for directories.
+ if (aReqFlags & X_OK) {
+ needed |= SandboxBroker::MAY_CREATE;
+ }
+ return (aPerms & needed) == needed;
+}
+
+static bool
AllowAccess(int aReqFlags, int aPerms)
{
if (aReqFlags & ~(R_OK|W_OK|F_OK)) {
return false;
}
int needed = 0;
if (aReqFlags & R_OK) {
needed |= SandboxBroker::MAY_READ;
@@ -338,22 +358,58 @@ AllowOpen(int aReqFlags, int aPerms)
}
if (aReqFlags & O_CREAT) {
needed |= SandboxBroker::MAY_CREATE;
}
return (aPerms & needed) == needed;
}
static int
-DoStat(const char* aPath, struct stat* aStat, int aFlags)
+DoStat(const char* aPath, void* aBuff, int aFlags)
{
+#if defined(__NR_stat64)
+ if (aFlags & O_NOFOLLOW) {
+ return lstat64(aPath, (struct stat64*)aBuff);
+ }
+ return stat64(aPath, (struct stat64*)aBuff);
+#else
if (aFlags & O_NOFOLLOW) {
- return lstat(aPath, aStat);
+ return lstat(aPath, (struct stat*)aBuff);
+ }
+ return stat(aPath, (struct stat*)aBuff);
+#endif
+}
+
+static int
+DoLink(const char* aPath, const char* aPath2,
+ SandboxBrokerCommon::Operation aOper)
+{
+ if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) {
+ return link(aPath, aPath2);
+ } else if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
+ return symlink(aPath, aPath2);
}
- return stat(aPath, aStat);
+ MOZ_CRASH("SandboxBroker: Unknown link operation");
+}
+
+size_t
+SandboxBroker::ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen)
+{
+ if (strstr(aPath, "..") != NULL) {
+ char* result = realpath(aPath, NULL);
+ if (result != NULL) {
+ strncpy(aPath, result, aBufSize);
+ aPath[aBufSize - 1] = '\0';
+ free(result);
+ // Size changed, but guaranteed to be 0 terminated
+ aPathLen = strlen(aPath);
+ }
+ // ValidatePath will handle failure to translate
+ }
+ return aPathLen;
}
void
SandboxBroker::ThreadMain(void)
{
char threadName[16];
SprintfLiteral(threadName, "FS Broker %d", mChildPid);
PlatformThread::SetName(threadName);
@@ -374,27 +430,37 @@ SandboxBroker::ThreadMain(void)
if (syscall(nr_setregid, getgid(), AID_APP + mChildPid) != 0 ||
syscall(nr_setreuid, getuid(), AID_APP + mChildPid) != 0) {
MOZ_CRASH("SandboxBroker: failed to drop privileges");
}
#endif
while (true) {
struct iovec ios[2];
+ // We will receive the path strings in 1 buffer and split them back up.
+ char recvBuf[2 * (kMaxPathLen + 1)];
char pathBuf[kMaxPathLen + 1];
+ char pathBuf2[kMaxPathLen + 1];
size_t pathLen;
- struct stat statBuf;
+ size_t pathLen2;
+ char respBuf[kMaxPathLen + 1]; // Also serves as struct stat
Request req;
Response resp;
int respfd;
+ // Make sure stat responses fit in the response buffer
+ MOZ_ASSERT((kMaxPathLen + 1) > sizeof(struct stat));
+
+ // This makes our string handling below a bit less error prone.
+ memset(recvBuf, 0, sizeof(recvBuf));
+
ios[0].iov_base = &req;
ios[0].iov_len = sizeof(req);
- ios[1].iov_base = pathBuf;
- ios[1].iov_len = kMaxPathLen;
+ ios[1].iov_base = recvBuf;
+ ios[1].iov_len = sizeof(recvBuf);
const ssize_t recvd = RecvWithFd(mFileDesc, ios, 2, &respfd);
if (recvd == 0) {
if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
SANDBOX_LOG_ERROR("EOF from pid %d", mChildPid);
}
break;
}
@@ -416,117 +482,256 @@ SandboxBroker::ThreadMain(void)
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;
+ memset(&respBuf, 0, sizeof(respBuf));
+ resp.mError = -EACCES;
ios[0].iov_base = &resp;
ios[0].iov_len = sizeof(resp);
ios[1].iov_base = nullptr;
ios[1].iov_len = 0;
int openedFd = -1;
- // 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)) {
+ // Clear permissions
+ int perms;
+
+ // Find end of first string, make sure the buffer is still
+ // 0 terminated.
+ size_t recvBufLen = static_cast<size_t>(recvd) - sizeof(req);
+ if (recvBufLen > 0 && recvBuf[recvBufLen - 1] != 0) {
+ SANDBOX_LOG_ERROR("corrupted path buffer from pid %d", mChildPid);
+ shutdown(mFileDesc, SHUT_RD);
+ break;
+ }
+
+ // First path should fit in maximum path length buffer.
+ size_t first_len = strlen(recvBuf);
+ if (first_len <= kMaxPathLen) {
+ strcpy(pathBuf, recvBuf);
+ // Skip right over the terminating 0, and try to copy in the
+ // second path, if any. If there's no path, this will hit a
+ // 0 immediately (we nulled the buffer before receiving).
+ // We do not assume the second path is 0-terminated, this is
+ // enforced below.
+ strncpy(pathBuf2, recvBuf + first_len + 1, kMaxPathLen + 1);
+
+ // First string is guaranteed to be 0-terminated.
+ pathLen = first_len;
+
+ // Look up the first pathname but first translate relative paths.
+ pathLen = ConvertToRealPath(pathBuf, sizeof(pathBuf), pathLen);
perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
+
+ // Same for the second path.
+ pathLen2 = strnlen(pathBuf2, kMaxPathLen);
+ if (pathLen2 > 0) {
+ // Force 0 termination.
+ pathBuf[pathLen2] = '\0';
+ pathLen2 = ConvertToRealPath(pathBuf2, sizeof(pathBuf2), pathLen2);
+ int perms2 = mPolicy->Lookup(nsDependentCString(pathBuf2, pathLen2));
+
+ // Take the intersection of the permissions for both paths.
+ perms &= perms2;
+ }
+ } else {
+ // Failed to receive intelligible paths.
+ perms = 0;
}
// And now perform the operation if allowed.
if (perms & CRASH_INSTEAD) {
// This is somewhat nonmodular, but it works.
- resp.mError = ENOSYS;
+ resp.mError = -ENOSYS;
} else if (permissive || perms & MAY_ACCESS) {
// If the operation was only allowed because of permissive mode, log it.
if (permissive && !(perms & MAY_ACCESS)) {
- AuditDenial(req.mOp, req.mFlags, pathBuf);
+ AuditPermissive(req.mOp, req.mFlags, perms, pathBuf);
}
switch(req.mOp) {
case SANDBOX_FILE_OPEN:
if (permissive || AllowOpen(req.mFlags, perms)) {
// Permissions for O_CREAT hardwired to 0600; if that's
// ever a problem we can change the protocol (but really we
// should be trying to remove uses of MAY_CREATE, not add
// new ones).
openedFd = open(pathBuf, req.mFlags | kRequiredOpenFlags, 0600);
if (openedFd >= 0) {
resp.mError = 0;
} else {
- resp.mError = errno;
+ resp.mError = -errno;
}
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
}
break;
case SANDBOX_FILE_ACCESS:
if (permissive || AllowAccess(req.mFlags, perms)) {
// This can't use access() itself because that uses the ruid
// and not the euid. In theory faccessat() with AT_EACCESS
// would work, but Linux doesn't actually implement the
// flags != 0 case; glibc has a hack which doesn't even work
// in this case so it'll ignore the flag, and Bionic just
// passes through the syscall and always ignores the flags.
//
// Instead, because we've already checked the requested
// r/w/x bits against the policy, just return success if the
// file exists and hope that's close enough.
- if (stat(pathBuf, &statBuf) == 0) {
+ if (stat(pathBuf, (struct stat*)&respBuf) == 0) {
resp.mError = 0;
} else {
- resp.mError = errno;
+ resp.mError = -errno;
}
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
}
break;
case SANDBOX_FILE_STAT:
- if (DoStat(pathBuf, &statBuf, req.mFlags) == 0) {
+ if (DoStat(pathBuf, (struct stat*)&respBuf, req.mFlags) == 0) {
resp.mError = 0;
- ios[1].iov_base = &statBuf;
- ios[1].iov_len = sizeof(statBuf);
+ ios[1].iov_base = &respBuf;
+ ios[1].iov_len = req.mBufSize;
+ } else {
+ resp.mError = -errno;
+ }
+ break;
+
+ case SANDBOX_FILE_CHMOD:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (chmod(pathBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_LINK:
+ case SANDBOX_FILE_SYMLINK:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_RENAME:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (rename(pathBuf, pathBuf2) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
} else {
- resp.mError = errno;
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_MKDIR:
+ if (permissive || AllowOperation(W_OK | X_OK, perms)) {
+ if (mkdir(pathBuf, req.mFlags) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_UNLINK:
+ if (permissive || AllowOperation(W_OK, perms)) {
+ if (unlink(pathBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_RMDIR:
+ if (permissive || AllowOperation(W_OK | X_OK, perms)) {
+ if (rmdir(pathBuf) == 0) {
+ resp.mError = 0;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+ }
+ break;
+
+ case SANDBOX_FILE_READLINK:
+ if (permissive || AllowOperation(R_OK, perms)) {
+ ssize_t respSize = readlink(pathBuf, (char*)&respBuf, sizeof(respBuf));
+ if (respSize >= 0) {
+ resp.mError = respSize;
+ ios[1].iov_base = &respBuf;
+ ios[1].iov_len = respSize;
+ } else {
+ resp.mError = -errno;
+ }
+ } else {
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
}
break;
}
} else {
MOZ_ASSERT(perms == 0);
+ AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
}
const size_t numIO = ios[1].iov_len > 0 ? 2 : 1;
DebugOnly<const ssize_t> sent = SendWithFd(respfd, ios, numIO, openedFd);
close(respfd);
MOZ_ASSERT(sent < 0 ||
static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len);
if (openedFd >= 0) {
close(openedFd);
}
}
}
void
-SandboxBroker::AuditDenial(int aOp, int aFlags, const char* aPath)
+SandboxBroker::AuditPermissive(int aOp, int aFlags, int aPerms, const char* aPath)
{
MOZ_RELEASE_ASSERT(SandboxInfo::Get().Test(SandboxInfo::kPermissive));
struct stat statBuf;
if (lstat(aPath, &statBuf) == 0) {
// Path exists, set errno to 0 to indicate "success".
errno = 0;
}
- SANDBOX_LOG_ERROR("SandboxBroker: denied op=%d rflags=%o path=%s for pid=%d" \
- " permissive=1 error=\"%s\"", aOp, aFlags, aPath, mChildPid,
- strerror(errno));
+ SANDBOX_LOG_ERROR("SandboxBroker: would have denied op=%d rflags=%o perms=%d path=%s for pid=%d" \
+ " permissive=1 error=\"%s\"", aOp, aFlags, aPerms,
+ aPath, mChildPid, strerror(errno));
}
+void
+SandboxBroker::AuditDenial(int aOp, int aFlags, int aPerms, const char* aPath)
+{
+#ifdef DEBUG
+ SANDBOX_LOG_ERROR("SandboxBroker: denied op=%d rflags=%o perms=%d path=%s for pid=%d" \
+ " error=\"%s\"", aOp, aFlags, aPerms, aPath, mChildPid,
+ strerror(errno));
+#endif
+}
+
+
} // namespace mozilla
--- a/security/sandbox/linux/broker/SandboxBroker.h
+++ b/security/sandbox/linux/broker/SandboxBroker.h
@@ -112,17 +112,20 @@ class SandboxBroker final
PlatformThreadHandle mThread;
int mFileDesc;
const int mChildPid;
const UniquePtr<const Policy> mPolicy;
SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
int& aClientFd);
void ThreadMain(void) override;
- void AuditDenial(int aOp, int aFlags, const char* aPath);
+ void AuditPermissive(int aOp, int aFlags, int aPerms, const char* aPath);
+ void AuditDenial(int aOp, int aFlags, int aPerms, const char* aPath);
+ // Remap relative paths to absolute paths.
+ size_t ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen);
// Holding a UniquePtr should disallow copying, but to make that explicit:
SandboxBroker(const SandboxBroker&) = delete;
void operator=(const SandboxBroker&) = delete;
};
} // namespace mozilla
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.h
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -24,28 +24,39 @@ struct iovec;
namespace mozilla {
class SandboxBrokerCommon {
public:
enum Operation {
SANDBOX_FILE_OPEN,
SANDBOX_FILE_ACCESS,
SANDBOX_FILE_STAT,
+ SANDBOX_FILE_CHMOD,
+ SANDBOX_FILE_LINK,
+ SANDBOX_FILE_SYMLINK,
+ SANDBOX_FILE_MKDIR,
+ SANDBOX_FILE_RENAME,
+ SANDBOX_FILE_RMDIR,
+ SANDBOX_FILE_UNLINK,
+ SANDBOX_FILE_READLINK,
};
struct Request {
Operation mOp;
// For open, flags; for access, "mode"; for stat, O_NOFOLLOW for lstat.
int mFlags;
+ // Size of return value buffer, if any
+ size_t mBufSize;
// The rest of the packet is the pathname.
// SCM_RIGHTS for response socket attached.
};
struct Response {
- int mError; // errno, or 0 for no error
+ // Syscall result, -errno if failure, or 0 for no error
+ int mError;
// Followed by struct stat for stat/lstat.
// SCM_RIGHTS attached for successful open.
};
// This doesn't need to be the system's maximum path length, just
// the largest path that would be allowed by any policy. (It's used
// to size a stack-allocated buffer.)
static const size_t kMaxPathLen = 4096;
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -35,22 +35,25 @@ SandboxBrokerPolicyFactory::IsSystemSupp
// automatically regardless of the device.
if (SandboxInfo::Get().Test(SandboxInfo::kPermissive)) {
return true;
}
#endif
return false;
}
-#if defined(MOZ_CONTENT_SANDBOX) && defined(MOZ_WIDGET_GONK)
+#if defined(MOZ_CONTENT_SANDBOX)
namespace {
static const int rdonly = SandboxBroker::MAY_READ;
static const int wronly = SandboxBroker::MAY_WRITE;
static const int rdwr = rdonly | wronly;
+static const int rdwrcr = rdwr | SandboxBroker::MAY_CREATE;
+#if defined(MOZ_WIDGET_GONK)
static const int wrlog = wronly | SandboxBroker::MAY_CREATE;
+#endif
}
#endif
SandboxBrokerPolicyFactory::SandboxBrokerPolicyFactory()
{
// Policy entries that are the same in every process go here, and
// are cached over the lifetime of the factory.
#if defined(MOZ_CONTENT_SANDBOX) && defined(MOZ_WIDGET_GONK)
@@ -106,50 +109,62 @@ SandboxBrokerPolicyFactory::SandboxBroke
// Bug 1198401: timezones. Yes, we need both of these; see bug.
policy->AddTree(rdonly, "/system/usr/share/zoneinfo");
policy->AddTree(rdonly, "/system//usr/share/zoneinfo");
policy->AddPath(rdonly, "/data/local/tmp/profiler.options",
SandboxBroker::Policy::AddAlways); // bug 1029337
mCommonContentPolicy.reset(policy);
+#elif defined(MOZ_CONTENT_SANDBOX)
+ SandboxBroker::Policy* policy = new SandboxBroker::Policy;
+ policy->AddDir(rdonly, "/");
+ policy->AddDir(rdwrcr, "/dev/shm");
+ policy->AddDir(rdwrcr, "/tmp");
+ mCommonContentPolicy.reset(policy);
#endif
}
#ifdef MOZ_CONTENT_SANDBOX
UniquePtr<SandboxBroker::Policy>
SandboxBrokerPolicyFactory::GetContentPolicy(int aPid)
{
- // Allow overriding "unsupported"ness with a pref, for testing.
- if (!IsSystemSupported() &&
- Preferences::GetInt("security.sandbox.content.level") <= 1) {
+ // Policy entries that vary per-process (currently the only reason
+ // that can happen is because they contain the pid) are added here.
+
+ MOZ_ASSERT(NS_IsMainThread());
+ // File broker usage is controlled through a pref.
+ if (Preferences::GetInt("security.sandbox.content.level") <= 1) {
return nullptr;
}
- // Policy entries that vary per-process (currently the only reason
- // that can happen is because they contain the pid) are added here.
+ MOZ_ASSERT(mCommonContentPolicy);
#if defined(MOZ_WIDGET_GONK)
- MOZ_ASSERT(NS_IsMainThread());
- MOZ_ASSERT(mCommonContentPolicy);
+ // Allow overriding "unsupported"ness with a pref, for testing.
+ if (!IsSystemSupported()) {
+ return nullptr;
+ }
UniquePtr<SandboxBroker::Policy>
policy(new SandboxBroker::Policy(*mCommonContentPolicy));
// Bug 1029337: where the profiler writes the data.
nsPrintfCString profilerLogPath("/data/local/tmp/profile_%d_%d.txt",
GeckoProcessType_Content, aPid);
policy->AddPath(wrlog, profilerLogPath.get());
// Bug 1198550: the profiler's replacement for dl_iterate_phdr
policy->AddPath(rdonly, nsPrintfCString("/proc/%d/maps", aPid).get());
// Bug 1198552: memory reporting.
policy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get());
policy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get());
return policy;
-#else // MOZ_WIDGET_GONK
- // Not implemented for desktop yet.
- return nullptr;
+#else
+ UniquePtr<SandboxBroker::Policy>
+ policy(new SandboxBroker::Policy(*mCommonContentPolicy));
+ // Return the common policy.
+ return policy;
#endif
}
#endif // MOZ_CONTENT_SANDBOX
} // namespace mozilla
--- a/security/sandbox/linux/gtest/TestBroker.cpp
+++ b/security/sandbox/linux/gtest/TestBroker.cpp
@@ -26,17 +26,17 @@
#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 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;
@@ -55,16 +55,40 @@ protected:
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);
}
+ int Chmod(const char* aPath, int aMode) {
+ return mClient->Chmod(aPath, aMode);
+ }
+ int Link(const char* aPath, const char* bPath) {
+ return mClient->Link(aPath, bPath);
+ }
+ int Mkdir(const char* aPath, int aMode) {
+ return mClient->Mkdir(aPath, aMode);
+ }
+ int Symlink(const char* aPath, const char* bPath) {
+ return mClient->Symlink(aPath, bPath);
+ }
+ int Rename(const char* aPath, const char* bPath) {
+ return mClient->Rename(aPath, bPath);
+ }
+ int Rmdir(const char* aPath) {
+ return mClient->Rmdir(aPath);
+ }
+ int Unlink(const char* aPath) {
+ return mClient->Unlink(aPath);
+ }
+ ssize_t Readlink(const char* aPath, char* aBuff, size_t aSize) {
+ return mClient->Readlink(aPath, aBuff, aSize);
+ }
virtual void SetUp() {
ipc::FileDescriptor fd;
mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd);
ASSERT_NE(mServer, nullptr);
ASSERT_TRUE(fd.IsValid());
auto rawFD = fd.ClonePlatformHandle();
@@ -100,16 +124,19 @@ 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.
+ policy->AddPath(MAY_READ | MAY_WRITE, "/tmp", AddAlways);
+ policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublu", AddAlways);
+ policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublublu", AddAlways);
return Move(policy);
}
TEST_F(SandboxBrokerTest, OpenForRead)
{
int fd;
@@ -205,16 +232,173 @@ TEST_F(SandboxBrokerTest, LStat)
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));
}
+static void PrePostTestCleanup(void)
+{
+ unlink("/tmp/blublu");
+ rmdir("/tmp/blublu");
+ unlink("/tmp/nope");
+ rmdir("/tmp/nope");
+ unlink("/tmp/blublublu");
+ rmdir("/tmp/blublublu");
+}
+
+TEST_F(SandboxBrokerTest, Chmod)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ // Set read only. SandboxBroker enforces 0600 mode flags.
+ ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR));
+ // SandboxBroker doesn't use real access(), it just checks against
+ // the policy. So it can't see the change in permisions here.
+ // This won't work:
+ // EXPECT_EQ(-EACCES, Access("/tmp/blublu", W_OK));
+ struct stat realStat;
+ EXPECT_EQ(0, stat("/tmp/blublu", &realStat));
+ EXPECT_EQ((mode_t)S_IRUSR, realStat.st_mode & 0777);
+
+ ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR | S_IWUSR));
+ EXPECT_EQ(0, stat("/tmp/blublu", &realStat));
+ EXPECT_EQ((mode_t)(S_IRUSR | S_IWUSR), realStat.st_mode & 0777);
+ EXPECT_EQ(0, unlink("/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Link)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ ASSERT_EQ(0, Link("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Link("/tmp/blublu", "/tmp/nope"));
+ EXPECT_EQ(0, unlink("/tmp/blublublu"));
+ EXPECT_EQ(0, unlink("/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Symlink)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ struct stat aStat;
+ ASSERT_EQ(0, lstat("/tmp/blublublu", &aStat));
+ EXPECT_EQ((mode_t)S_IFLNK, aStat.st_mode & S_IFMT);
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Symlink("/tmp/blublu", "/tmp/nope"));
+ EXPECT_EQ(0, unlink("/tmp/blublublu"));
+ EXPECT_EQ(0, unlink("/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Mkdir)
+{
+ PrePostTestCleanup();
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
+ << "Creating dir /tmp/blublu failed.";
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Mkdir("/tmp/nope", 0600))
+ << "Creating dir without MAY_CREATE succeed.";
+ EXPECT_EQ(0, rmdir("/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Rename)
+{
+ PrePostTestCleanup();
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
+ << "Creating dir /tmp/blublu failed.";
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ ASSERT_EQ(0, Rename("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ EXPECT_EQ(-ENOENT , Access("/tmp/blublu", F_OK));
+ // Not whitelisted target path
+ EXPECT_EQ(-EACCES, Rename("/tmp/blublublu", "/tmp/nope"))
+ << "Renaming dir without write access succeed.";
+ EXPECT_EQ(0, rmdir("/tmp/blublublu"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Rmdir)
+{
+ PrePostTestCleanup();
+
+ ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
+ << "Creating dir /tmp/blublu failed.";
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ ASSERT_EQ(0, Rmdir("/tmp/blublu"));
+ EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
+ // Bypass sandbox to create a non-deletable dir
+ ASSERT_EQ(0, mkdir("/tmp/nope", 0600));
+ EXPECT_EQ(-EACCES, Rmdir("/tmp/nope"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Unlink)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
+ EXPECT_EQ(0, Unlink("/tmp/blublu"));
+ EXPECT_EQ(-ENOENT , Access("/tmp/blublu", F_OK));
+ // Bypass sandbox to write a non-deletable file
+ fd = open("/tmp/nope", O_WRONLY | O_CREAT, 0600);
+ ASSERT_GE(fd, 0) << "Opening /tmp/nope for writing failed.";
+ close(fd);
+ EXPECT_EQ(-EACCES, Unlink("/tmp/nope"));
+
+ PrePostTestCleanup();
+}
+
+TEST_F(SandboxBrokerTest, Readlink)
+{
+ PrePostTestCleanup();
+
+ int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
+ ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
+ close(fd);
+ ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
+ EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
+ char linkBuff[256];
+ EXPECT_EQ(11, Readlink("/tmp/blublublu", linkBuff, sizeof(linkBuff)));
+ linkBuff[11] = '\0';
+ EXPECT_EQ(0, strcmp(linkBuff, "/tmp/blublu"));
+
+ PrePostTestCleanup();
+}
+
TEST_F(SandboxBrokerTest, MultiThreadOpen) {
RunOnManyThreads<SandboxBrokerTest,
&SandboxBrokerTest::MultiThreadOpenWorker>();
}
void SandboxBrokerTest::MultiThreadOpenWorker() {
static const int kNumLoops = 10000;
for (int i = 1; i <= kNumLoops; ++i) {