Bug 1289718 - Extend sandbox file broker to handle paths, support more syscalls. r=tedd r=jld draft
authorGian-Carlo Pascutto <gcp@mozilla.com>
Tue, 27 Sep 2016 17:25:06 +0200
changeset 421528 ad740950d3c2df009c13fab53a91358d4d86ed44
parent 421527 c24253346eac4e0c6701840f0797c6ff742b1eb2
child 421529 6380b554313a1ff5bcc80fa95be6d9b2891ed167
push id31540
push userbmo:gpascutto@mozilla.com
push dateThu, 06 Oct 2016 11:25:45 +0000
reviewerstedd, jld
bugs1289718
milestone52.0a1
Bug 1289718 - Extend sandbox file broker to handle paths, support more syscalls. r=tedd r=jld MozReview-Commit-ID: DW415ABoaeN MozReview-Commit-ID: cXrlXNlEwh MozReview-Commit-ID: CIX6d7td6Bc MozReview-Commit-ID: DCQ9DGBBjm4 MozReview-Commit-ID: BOYSSof3t7 MozReview-Commit-ID: LmsTqv0GzC2 MozReview-Commit-ID: HOWR0HEHmMg MozReview-Commit-ID: 6IzqdrUP7lD MozReview-Commit-ID: 6r6sqOVekVu MozReview-Commit-ID: 5FL2WkhIxFx MozReview-Commit-ID: CcTyHn76p46 MozReview-Commit-ID: F8erB4Tvn2V MozReview-Commit-ID: D9m10t0Rodc MozReview-Commit-ID: I1llpEBgDP6
browser/app/profile/firefox.js
security/sandbox/linux/SandboxBrokerClient.cpp
security/sandbox/linux/SandboxBrokerClient.h
security/sandbox/linux/SandboxFilter.cpp
security/sandbox/linux/broker/SandboxBroker.cpp
security/sandbox/linux/broker/SandboxBroker.h
security/sandbox/linux/broker/SandboxBrokerCommon.h
security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
security/sandbox/linux/gtest/TestBroker.cpp
--- 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) {