Bug 1289718 - Implement symlink, rename, unlink, rmdir. draft
authorGian-Carlo Pascutto <gcp@mozilla.com>
Thu, 01 Sep 2016 20:36:34 +0200
changeset 409937 0694a1b511323346af25c9c743f34436386d024f
parent 409936 c6db11730ee842db0167eabea4d2c132100ed3de
child 409938 ce977f89f04ba481c64ab6b3ffeaaf2591f5a8fa
push id28613
push usergpascutto@mozilla.com
push dateMon, 05 Sep 2016 18:00:21 +0000
bugs1289718
milestone51.0a1
Bug 1289718 - Implement symlink, rename, unlink, rmdir. MozReview-Commit-ID: 5FL2WkhIxFx
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/SandboxBrokerCommon.h
security/sandbox/linux/gtest/TestBroker.cpp
--- a/security/sandbox/linux/SandboxBrokerClient.cpp
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -201,16 +201,46 @@ SandboxBrokerClient::Chmod(const char* a
 int
 SandboxBrokerClient::Link(const char* aOldPath, const char* aNewPath)
 {
   Request req = {SANDBOX_FILE_LINK, 0};
   return DoCall(&req, aOldPath, aNewPath, nullptr, false);
 }
 
 int
+SandboxBrokerClient::Symlink(const char* aOldPath, const char* aNewPath)
+{
+  Request req = {SANDBOX_FILE_SYMLINK, 0};
+  return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+
+}
+
+int
+SandboxBrokerClient::Rename(const char* aOldPath, const char* aNewPath)
+{
+  Request req = {SANDBOX_FILE_RENAME, 0};
+  return DoCall(&req, aOldPath, aNewPath, nullptr, false);
+
+}
+
+int
 SandboxBrokerClient::Mkdir(const char* aPath, int aMode)
 {
   Request req = {SANDBOX_FILE_MKDIR, aMode};
   return DoCall(&req, aPath, nullptr, nullptr, false);
 }
 
+int
+SandboxBrokerClient::Unlink(const char* aPath)
+{
+  Request req = {SANDBOX_FILE_UNLINK, 0};
+  return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
+int
+SandboxBrokerClient::Rmdir(const char* aPath)
+{
+  Request req = {SANDBOX_FILE_RMDIR, 0};
+  return DoCall(&req, aPath, nullptr, nullptr, false);
+}
+
 } // namespace mozilla
 
--- a/security/sandbox/linux/SandboxBrokerClient.h
+++ b/security/sandbox/linux/SandboxBrokerClient.h
@@ -31,16 +31,20 @@ class SandboxBrokerClient final : privat
 
   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);
 
  private:
   int mFileDesc;
 
   int DoCall(const Request* aReq,
              const char* aPath,
              const char* aPath2,
              struct stat* aStat,
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -418,23 +418,49 @@ class ContentSandboxPolicy : public Sand
 
   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 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:
@@ -528,16 +554,24 @@ public:
       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);
       }
     } else {
       // No broker; allow the syscalls directly.  )-:
       switch(sysno) {
       case __NR_open:
       case __NR_openat:
       case __NR_access:
       case __NR_faccessat:
@@ -550,24 +584,20 @@ 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_rmdir:
     case __NR_getcwd:
     CASES_FOR_statfs:
     CASES_FOR_fstatfs:
-    case __NR_rename:
-    case __NR_symlink:
     case __NR_quotactl:
-    case __NR_unlink:
     CASES_FOR_fchown:
     case __NR_fchmod:
     case __NR_flock:
 #endif
       return Allow();
 
     case __NR_readlink:
     case __NR_readlinkat:
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -371,27 +371,51 @@ DoStat(const char* aPath, struct stat* a
 
 static int
 DoChmod(const char* aPath, int aMode)
 {
   return chmod(aPath, aMode);
 }
 
 static int
-DoLink(const char* aPath, const char* aPath2)
+DoLink(const char* aPath, const char* aPath2,
+       SandboxBrokerCommon::Operation aOper)
 {
-  return link(aPath, aPath2);
+  if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) {
+    return link(aPath, aPath2);
+  } else if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
+    return symlink(aPath, aPath2);
+  }
+  MOZ_CRASH("SandboxBroker: Unknown link operation");
+}
+
+static int
+DoRename(const char* aPath, const char* aPath2)
+{
+  return rename(aPath, aPath2);
 }
 
 static int
 DoMkdir(const char* aPath, int aMode)
 {
   return mkdir(aPath, aMode);
 }
 
+static int
+DoRmdir(const char* aPath)
+{
+  return rmdir(aPath);
+}
+
+static int
+DoUnlink(const char* aPath)
+{
+  return unlink(aPath);
+}
+
 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';
@@ -588,34 +612,65 @@ SandboxBroker::ThreadMain(void)
             resp.mError = 0;
           } else {
             resp.mError = errno;
           }
         }
         break;
 
       case SANDBOX_FILE_LINK:
+      case SANDBOX_FILE_SYMLINK:
         if (permissive || AllowOperation(W_OK, perms)) {
-          if (DoLink(pathBuf, pathBuf2) == 0) {
+          if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) {
+            resp.mError = 0;
+          } else {
+            resp.mError = errno;
+          }
+        }
+        break;
+
+      case SANDBOX_FILE_RENAME:
+        if (permissive || AllowOperation(W_OK, perms)) {
+          if (DoRename(pathBuf, pathBuf2) == 0) {
             resp.mError = 0;
           } else {
             resp.mError = errno;
           }
         }
         break;
 
       case SANDBOX_FILE_MKDIR:
         if (permissive || AllowOperation(W_OK | X_OK, perms)) {
           if (DoMkdir(pathBuf, req.mFlags) == 0) {
             resp.mError = 0;
           } else {
             resp.mError = errno;
           }
         }
         break;
+
+      case SANDBOX_FILE_UNLINK:
+        if (permissive || AllowOperation(W_OK, perms)) {
+          if (DoUnlink(pathBuf) == 0) {
+            resp.mError = 0;
+          } else {
+            resp.mError = errno;
+          }
+        }
+        break;
+
+      case SANDBOX_FILE_RMDIR:
+        if (permissive || AllowOperation(W_OK | X_OK, perms)) {
+          if (DoRmdir(pathBuf) == 0) {
+            resp.mError = 0;
+          } else {
+            resp.mError = errno;
+          }
+        }
+        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);
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.h
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -26,17 +26,21 @@ 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,
   };
 
   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.
--- a/security/sandbox/linux/gtest/TestBroker.cpp
+++ b/security/sandbox/linux/gtest/TestBroker.cpp
@@ -64,16 +64,28 @@ protected:
     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);
+  }
 
   virtual void SetUp() {
     ipc::FileDescriptor fd;
 
     mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd);
     ASSERT_NE(mServer, nullptr);
     ASSERT_TRUE(fd.IsValid());
     auto rawFD = fd.ClonePlatformHandle();
@@ -221,16 +233,17 @@ TEST_F(SandboxBrokerTest, LStat)
   EXPECT_EQ(0, LStat("/proc/self", &brokeredStat));
   EXPECT_TRUE(S_ISLNK(brokeredStat.st_mode));
 }
 
 TEST_F(SandboxBrokerTest, Chmod)
 {
   unlink("/tmp/blublu");
   rmdir("/tmp/blublu");
+
   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:
@@ -245,42 +258,130 @@ TEST_F(SandboxBrokerTest, Chmod)
   EXPECT_EQ(0, unlink("/tmp/blublu"));
 }
 
 TEST_F(SandboxBrokerTest, Link)
 {
   unlink("/tmp/blublu");
   rmdir("/tmp/blublu");
   unlink("/tmp/blublublu");
+
   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"));
 }
 
+TEST_F(SandboxBrokerTest, Symlink)
+{
+  unlink("/tmp/blublu");
+  rmdir("/tmp/blublu");
+  unlink("/tmp/blublublu");
+
+  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"));
+}
+
 TEST_F(SandboxBrokerTest, Mkdir)
 {
   unlink("/tmp/blublu");
   rmdir("/tmp/blublu");
   unlink("/tmp/nope");
   rmdir("/tmp/nope");
-  ASSERT_EQ(mkdir("/tmp/blublu", 0600), 0)
+
+  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"));
 }
 
+TEST_F(SandboxBrokerTest, Rename)
+{
+  unlink("/tmp/blublu");
+  rmdir("/tmp/blublu");
+  unlink("/tmp/blublublu");
+  rmdir("/tmp/blublublu");
+
+  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"));
+}
+
+TEST_F(SandboxBrokerTest, Rmdir)
+{
+  unlink("/tmp/blublu");
+  rmdir("/tmp/blublu");
+  unlink("/tmp/blublublu");
+  rmdir("/tmp/blublublu");
+  unlink("/tmp/nope");
+  rmdir("/tmp/nope");
+
+  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"));
+
+  unlink("/tmp/blublu");
+  rmdir("/tmp/blublu");
+  unlink("/tmp/nope");
+  rmdir("/tmp/nope");
+}
+
+TEST_F(SandboxBrokerTest, Unlink)
+{
+  unlink("/tmp/blublu");
+  rmdir("/tmp/blublu");
+  unlink("/tmp/nope");
+  rmdir("/tmp/nope");
+
+  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);
+  ASSERT_GE(fd, 0) << "Opening /tmp/nope for writing failed.";
+  close(fd);
+  EXPECT_EQ(-EACCES, Unlink("/tmp/nope"));
+
+  unlink("/tmp/blublu");
+  rmdir("/tmp/blublu");
+}
+
 TEST_F(SandboxBrokerTest, MultiThreadOpen) {
   RunOnManyThreads<SandboxBrokerTest,
                    &SandboxBrokerTest::MultiThreadOpenWorker>();
 }
 void SandboxBrokerTest::MultiThreadOpenWorker() {
   static const int kNumLoops = 10000;
 
   for (int i = 1; i <= kNumLoops; ++i) {