Bug 1289718 - Implement readlink. draft
authorGian-Carlo Pascutto <gcp@mozilla.com>
Fri, 02 Sep 2016 17:57:44 +0200
changeset 409938 ce977f89f04ba481c64ab6b3ffeaaf2591f5a8fa
parent 409937 0694a1b511323346af25c9c743f34436386d024f
child 409939 97da480d8413efe100bca989fa9325b1944f1d78
push id28613
push usergpascutto@mozilla.com
push dateMon, 05 Sep 2016 18:00:21 +0000
bugs1289718
milestone51.0a1
Bug 1289718 - Implement readlink. MozReview-Commit-ID: CcTyHn76p46
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
--- a/security/sandbox/linux/SandboxBrokerClient.cpp
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -30,17 +30,17 @@ SandboxBrokerClient::SandboxBrokerClient
 
 SandboxBrokerClient::~SandboxBrokerClient()
 {
   close(mFileDesc);
 }
 
 int
 SandboxBrokerClient::DoCall(const Request* aReq, const char* aPath,
-                            const char* aPath2, struct stat* aStat,
+                            const char* aPath2, void* aBuff,
                             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.
@@ -101,44 +101,44 @@ SandboxBrokerClient::DoCall(const Reques
     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 (aBuff) {
+    ios[1].iov_base = aBuff;
+    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, aBuff ? 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);
+  MOZ_ASSERT(static_cast<size_t>(recvd) <= ios[0].iov_len + ios[1].iov_len);
   if (resp.mError == 0) {
     // Success!
     if (expectFd) {
       MOZ_ASSERT(openedFd >= 0);
       return openedFd;
     }
     return 0;
   }
@@ -154,93 +154,98 @@ SandboxBrokerClient::DoCall(const Reques
     close(openedFd);
   }
   return -resp.mError;
 }
 
 int
 SandboxBrokerClient::Open(const char* aPath, int aFlags)
 {
-  Request req = { SANDBOX_FILE_OPEN, aFlags };
+  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 };
+  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, nullptr, aStat, false);
+  Request req = { SANDBOX_FILE_STAT, 0, sizeof(struct stat) };
+  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, nullptr, aStat, false);
+  Request req = { SANDBOX_FILE_STAT, O_NOFOLLOW, sizeof(struct stat) };
+  return DoCall(&req, aPath, nullptr, (void*)aStat, false);
 }
 
 int
 SandboxBrokerClient::Chmod(const char* aPath, int aMode)
 {
-  Request req = {SANDBOX_FILE_CHMOD, 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};
+  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};
+  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};
+  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};
+  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};
+  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};
+  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
@@ -35,22 +35,23 @@ class SandboxBrokerClient final : privat
   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,
              const char* aPath2,
-             struct stat* aStat,
+             void *aBuff,
              bool expectFd);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_SandboxBrokerClient_h
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -451,16 +451,24 @@ class ContentSandboxPolicy : public Sand
   }
 
   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:
@@ -562,16 +570,18 @@ public:
       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:
@@ -594,17 +604,16 @@ public:
     CASES_FOR_fstatfs:
     case __NR_quotactl:
     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
@@ -356,22 +356,22 @@ 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 (aFlags & O_NOFOLLOW) {
-    return lstat(aPath, aStat);
+    return lstat(aPath, (struct stat*)aBuff);
   }
-  return stat(aPath, aStat);
+  return stat(aPath, (struct stat*)aBuff);
 }
 
 static int
 DoChmod(const char* aPath, int aMode)
 {
   return chmod(aPath, aMode);
 }
 
@@ -406,16 +406,22 @@ DoRmdir(const char* aPath)
 }
 
 static int
 DoUnlink(const char* aPath)
 {
   return unlink(aPath);
 }
 
+static ssize_t
+DoReadlink(const char* aPath, void* aBuff, size_t aBufSize)
+{
+  return readlink(aPath, (char*)aBuff, aBufSize);
+}
+
 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';
@@ -457,21 +463,24 @@ SandboxBroker::ThreadMain(void)
   while (true) {
     struct iovec ios[2];
     // We will receive the path strings in 1 buffer an split them back up.
     char recvBuf[2 * (kMaxPathLen + 1)];
     char pathBuf[kMaxPathLen + 1];
     char pathBuf2[kMaxPathLen + 1];
     size_t pathLen;
     size_t pathLen2;
-    struct stat statBuf;
+    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 = recvBuf;
     ios[1].iov_len = sizeof(recvBuf);
 
@@ -500,17 +509,17 @@ 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));
+    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;
 
     // Clear permissions
@@ -581,31 +590,31 @@ SandboxBroker::ThreadMain(void)
           // 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;
           }
         } 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 = sizeof(struct stat);
         } else {
           resp.mError = errno;
         }
         break;
 
       case SANDBOX_FILE_CHMOD:
         if (permissive || AllowOperation(W_OK, perms)) {
           if (DoChmod(pathBuf, req.mFlags) == 0) {
@@ -661,16 +670,30 @@ SandboxBroker::ThreadMain(void)
         if (permissive || AllowOperation(W_OK | X_OK, perms)) {
           if (DoRmdir(pathBuf) == 0) {
             resp.mError = 0;
           } else {
             resp.mError = errno;
           }
         }
         break;
+
+      case SANDBOX_FILE_READLINK:
+        if (permissive || AllowOperation(R_OK, perms)) {
+          ssize_t respSize = DoReadlink(pathBuf, &respBuf, sizeof(respBuf));
+          if (respSize >= 0) {
+            resp.mError = 0;
+            ios[1].iov_base = &respBuf;
+            ios[1].iov_len = respSize;
+          } 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
@@ -31,22 +31,25 @@ public:
     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
     // Followed by struct stat for stat/lstat.
     // SCM_RIGHTS attached for successful open.