tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SandboxBroker.cpp (35822B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "SandboxBroker.h"
      8 #include "SandboxInfo.h"
      9 
     10 #include "SandboxProfilerParent.h"
     11 #include "SandboxLogging.h"
     12 
     13 #include "SandboxBrokerUtils.h"
     14 
     15 #include <dirent.h>
     16 #include <errno.h>
     17 #include <fcntl.h>
     18 #include <sys/socket.h>
     19 #include <sys/stat.h>
     20 #include <sys/types.h>
     21 #include <sys/un.h>
     22 #include <unistd.h>
     23 
     24 #ifdef XP_LINUX
     25 #  include <sys/prctl.h>
     26 #endif
     27 
     28 #include <utility>
     29 
     30 #include "GeckoProfiler.h"
     31 #include "SpecialSystemDirectory.h"
     32 #include "base/string_util.h"
     33 #include "mozilla/Assertions.h"
     34 #include "mozilla/Atomics.h"
     35 #include "mozilla/Sprintf.h"
     36 #include "mozilla/ipc/FileDescriptor.h"
     37 #include "nsAppDirectoryServiceDefs.h"
     38 #include "nsDirectoryServiceDefs.h"
     39 #include "nsThreadUtils.h"
     40 #include "sandbox/linux/system_headers/linux_syscalls.h"
     41 
     42 namespace mozilla {
     43 
     44 namespace {
     45 
     46 // kernel level limit defined at
     47 // https://elixir.bootlin.com/linux/latest/source/include/linux/sched.h#L301
     48 // used at
     49 // https://elixir.bootlin.com/linux/latest/source/include/linux/sched.h#L1087
     50 static const int kThreadNameMaxSize = 16;
     51 
     52 static Atomic<size_t> gNumBrokers;
     53 
     54 }  // namespace
     55 
     56 // This constructor signals failure by setting mFileDesc and aClientFd to -1.
     57 SandboxBroker::SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
     58                             int& aClientFd)
     59    : mChildPid(aChildPid), mPolicy(std::move(aPolicy)) {
     60  ++gNumBrokers;
     61  int fds[2];
     62  if (0 != socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, fds)) {
     63    SANDBOX_LOG_ERRNO("SandboxBroker: socketpair failed (%d brokers)",
     64                      gNumBrokers.operator size_t());
     65    mFileDesc = -1;
     66    aClientFd = -1;
     67    return;
     68  }
     69  mFileDesc = fds[0];
     70  aClientFd = fds[1];
     71 
     72  // When the thread will start it may already be too late to correctly care
     73  // about reference handling. Make sure the increment happens before the thread
     74  // is created to avoid races. Details in SandboxBroker::ThreadMain().
     75  NS_ADDREF_THIS();
     76 
     77  if (!PlatformThread::Create(0, this, &mThread)) {
     78    SANDBOX_LOG_ERRNO("SandboxBroker: thread creation failed (%d brokers)",
     79                      gNumBrokers.operator size_t());
     80    close(mFileDesc);
     81    close(aClientFd);
     82    mFileDesc = -1;
     83    aClientFd = -1;
     84  }
     85 }
     86 
     87 already_AddRefed<SandboxBroker> SandboxBroker::Create(
     88    UniquePtr<const Policy> aPolicy, int aChildPid,
     89    ipc::FileDescriptor& aClientFdOut) {
     90  int clientFd;
     91  // Can't use MakeRefPtr here because the constructor is private.
     92  RefPtr<SandboxBroker> rv(
     93      new SandboxBroker(std::move(aPolicy), aChildPid, clientFd));
     94  if (clientFd < 0) {
     95    rv = nullptr;
     96  } else {
     97    // FileDescriptor can be constructed from an int, but that dup()s
     98    // the fd; instead, transfer ownership:
     99    aClientFdOut = ipc::FileDescriptor(UniqueFileHandle(clientFd));
    100  }
    101  return rv.forget();
    102 }
    103 
    104 void SandboxBroker::Terminate() {
    105  // If the constructor failed, there's nothing to be done here.
    106  if (mFileDesc < 0) {
    107    return;
    108  }
    109 
    110  // Join() on the same thread while working with errno EDEADLK is technically
    111  // not POSIX compliant:
    112  // https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_join.html#:~:text=refers%20to%20the%20calling%20thread
    113  if (mThread != pthread_self()) {
    114    shutdown(mFileDesc, SHUT_RD);
    115    // The thread will now get EOF even if the client hasn't exited.
    116    PlatformThread::Join(mThread);
    117  } else {
    118    // Nothing is waiting for this thread, so detach it to avoid
    119    // memory leaks.
    120    int rv = pthread_detach(pthread_self());
    121    MOZ_ALWAYS_TRUE(rv == 0);
    122  }
    123 
    124  // Now that the thread has exited, the fd will no longer be accessed.
    125  close(mFileDesc);
    126  // Having ensured that this object outlives the thread, this
    127  // destructor can now return.
    128 
    129  mFileDesc = -1;
    130 }
    131 
    132 SandboxBroker::~SandboxBroker() {
    133  Terminate();
    134  --gNumBrokers;
    135 }
    136 
    137 SandboxBroker::Policy::Policy() = default;
    138 SandboxBroker::Policy::~Policy() = default;
    139 
    140 SandboxBroker::Policy::Policy(const Policy& aOther)
    141    : mMap(aOther.mMap.Clone()) {}
    142 
    143 // Chromium
    144 // sandbox/linux/syscall_broker/broker_file_permission.cc
    145 // Async signal safe
    146 bool SandboxBroker::Policy::ValidatePath(const char* path) const {
    147  if (!path) return false;
    148 
    149  const size_t len = strlen(path);
    150  // No empty paths
    151  if (len == 0) return false;
    152  // Paths must be absolute and not relative
    153  if (path[0] != '/') return false;
    154  // No trailing / (but "/" is valid)
    155  if (len > 1 && path[len - 1] == '/') return false;
    156  // No trailing /.
    157  if (len >= 2 && path[len - 2] == '/' && path[len - 1] == '.') return false;
    158  // No trailing /..
    159  if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' &&
    160      path[len - 1] == '.')
    161    return false;
    162  // No /../ anywhere
    163  for (size_t i = 0; i < len; i++) {
    164    if (path[i] == '/' && (len - i) > 3) {
    165      if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') {
    166        return false;
    167      }
    168    }
    169  }
    170  return true;
    171 }
    172 
    173 void SandboxBroker::Policy::AddPath(int aPerms, const char* aPath,
    174                                    AddCondition aCond) {
    175  nsDependentCString path(aPath);
    176  MOZ_ASSERT(path.Length() <= kMaxPathLen);
    177  if (aCond == AddIfExistsNow) {
    178    struct stat statBuf;
    179    if (lstat(aPath, &statBuf) != 0) {
    180      return;
    181    }
    182  }
    183  auto& perms = mMap.LookupOrInsert(path, MAY_ACCESS);
    184  MOZ_ASSERT(perms & MAY_ACCESS);
    185 
    186  if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    187    SANDBOX_LOG("policy for %s: %d -> %d", aPath, perms, perms | aPerms);
    188  }
    189  perms |= aPerms;
    190 }
    191 
    192 void SandboxBroker::Policy::AddTree(int aPerms, const char* aPath) {
    193  struct stat statBuf;
    194 
    195  if (stat(aPath, &statBuf) != 0) {
    196    return;
    197  }
    198 
    199  if (!S_ISDIR(statBuf.st_mode)) {
    200    return;
    201  }
    202 
    203  Policy::AddTreeInternal(aPerms, aPath);
    204 }
    205 
    206 void SandboxBroker::Policy::AddFutureDir(int aPerms, const char* aPath) {
    207  Policy::AddTreeInternal(aPerms, aPath);
    208 }
    209 
    210 void SandboxBroker::Policy::AddTreeInternal(int aPerms, const char* aPath) {
    211  // Add a Prefix permission on things inside the dir.
    212  nsDependentCString path(aPath);
    213  MOZ_ASSERT(path.Length() <= kMaxPathLen - 1);
    214  // Enforce trailing / on aPath
    215  if (path.Last() != '/') {
    216    path.Append('/');
    217  }
    218  Policy::AddPrefixInternal(aPerms, path);
    219 
    220  // Add a path permission on the dir itself so it can
    221  // be opened. We're guaranteed to have a trailing / now,
    222  // so just cut that.
    223  path.Truncate(path.Length() - 1);
    224  if (!path.IsEmpty()) {
    225    Policy::AddPath(aPerms, path.get(), AddAlways);
    226  }
    227 }
    228 
    229 void SandboxBroker::Policy::AddPrefix(int aPerms, const char* aPath) {
    230  Policy::AddPrefixInternal(aPerms, nsDependentCString(aPath));
    231 }
    232 
    233 void SandboxBroker::Policy::AddPrefixInternal(int aPerms,
    234                                              const nsACString& aPath) {
    235  auto& perms = mMap.LookupOrInsert(aPath, MAY_ACCESS);
    236  MOZ_ASSERT(perms & MAY_ACCESS);
    237 
    238  int newPerms = perms | aPerms | RECURSIVE;
    239  if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    240    SANDBOX_LOG("policy for %s: %d -> %d", PromiseFlatCString(aPath).get(),
    241                perms, newPerms);
    242  }
    243  perms = newPerms;
    244 }
    245 
    246 void SandboxBroker::Policy::AddFilePrefix(int aPerms, const char* aDir,
    247                                          const char* aPrefix) {
    248  size_t prefixLen = strlen(aPrefix);
    249  DIR* dirp = opendir(aDir);
    250  struct dirent* de;
    251  if (!dirp) {
    252    return;
    253  }
    254  while ((de = readdir(dirp))) {
    255    if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0 &&
    256        strncmp(de->d_name, aPrefix, prefixLen) == 0) {
    257      nsAutoCString subPath;
    258      subPath.Assign(aDir);
    259      subPath.Append('/');
    260      subPath.Append(de->d_name);
    261      AddPath(aPerms, subPath.get(), AddAlways);
    262    }
    263  }
    264  closedir(dirp);
    265 }
    266 
    267 void SandboxBroker::Policy::AddDynamic(int aPerms, const char* aPath) {
    268  struct stat statBuf;
    269  bool exists = (stat(aPath, &statBuf) == 0);
    270 
    271  if (!exists) {
    272    AddPrefix(aPerms, aPath);
    273  } else {
    274    size_t len = strlen(aPath);
    275    if (!len) return;
    276    if (aPath[len - 1] == '/') {
    277      AddTree(aPerms, aPath);
    278    } else {
    279      AddPath(aPerms, aPath);
    280    }
    281  }
    282 }
    283 
    284 void SandboxBroker::Policy::AddAncestors(const char* aPath, int aPerms) {
    285  nsAutoCString path(aPath);
    286 
    287  while (true) {
    288    const auto lastSlash = path.RFindCharInSet("/");
    289    if (lastSlash <= 0) {
    290      MOZ_ASSERT(lastSlash == 0);
    291      return;
    292    }
    293    path.Truncate(lastSlash);
    294    AddPath(aPerms, path.get());
    295  }
    296 }
    297 
    298 void SandboxBroker::Policy::FixRecursivePermissions() {
    299  // This builds an entirely new hashtable in order to avoid iterator
    300  // invalidation problems.
    301  PathPermissionMap oldMap;
    302  mMap.SwapElements(oldMap);
    303 
    304  if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    305    SANDBOX_LOG("fixing recursive policy entries");
    306  }
    307 
    308  for (const auto& entry : oldMap) {
    309    const nsACString& path = entry.GetKey();
    310    const int& localPerms = entry.GetData();
    311    int inheritedPerms = 0;
    312 
    313    nsAutoCString ancestor(path);
    314    // This is slightly different from the loop in AddAncestors: it
    315    // leaves the trailing slashes attached so they'll match AddTree
    316    // entries.
    317    while (true) {
    318      // Last() release-asserts that the string is not empty.  We
    319      // should never have empty keys in the map, and the Truncate()
    320      // below will always give us a non-empty string.
    321      if (ancestor.Last() == '/') {
    322        ancestor.Truncate(ancestor.Length() - 1);
    323      }
    324      const auto lastSlash = ancestor.RFindCharInSet("/");
    325      if (lastSlash < 0) {
    326        MOZ_ASSERT(ancestor.IsEmpty());
    327        break;
    328      }
    329      ancestor.Truncate(lastSlash + 1);
    330      const int ancestorPerms = oldMap.Get(ancestor);
    331      if (ancestorPerms & RECURSIVE) {
    332        // if a child is set with FORCE_DENY, do not compute inheritedPerms
    333        if ((localPerms & FORCE_DENY) == FORCE_DENY) {
    334          if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    335            SANDBOX_LOG("skip inheritence policy for %s: %d",
    336                        PromiseFlatCString(path).get(), localPerms);
    337          }
    338        } else {
    339          inheritedPerms |= ancestorPerms & ~RECURSIVE;
    340        }
    341      }
    342    }
    343 
    344    const int newPerms = localPerms | inheritedPerms;
    345    if ((newPerms & ~RECURSIVE) == inheritedPerms) {
    346      if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    347        SANDBOX_LOG("removing redundant %s: %d -> %d",
    348                    PromiseFlatCString(path).get(), localPerms, newPerms);
    349      }
    350      // Skip adding this entry to the new map.
    351      continue;
    352    }
    353    if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    354      SANDBOX_LOG("new policy for %s: %d -> %d", PromiseFlatCString(path).get(),
    355                  localPerms, newPerms);
    356    }
    357    mMap.InsertOrUpdate(path, newPerms);
    358  }
    359 }
    360 
    361 int SandboxBroker::Policy::Lookup(const nsACString& aPath) const {
    362  // Early exit for paths explicitly found in the
    363  // whitelist.
    364  // This means they will not gain extra permissions
    365  // from recursive paths.
    366  int perms = mMap.Get(aPath);
    367  if (perms) {
    368    return perms;
    369  }
    370 
    371  // Not a legally constructed path
    372  if (!ValidatePath(PromiseFlatCString(aPath).get())) return 0;
    373 
    374  // Now it's either an illegal access, or a recursive
    375  // directory permission. We'll have to check the entire
    376  // whitelist for the best match (slower).
    377  int allPerms = 0;
    378  for (const auto& entry : mMap) {
    379    const nsACString& whiteListPath = entry.GetKey();
    380    const int& perms = entry.GetData();
    381 
    382    if (!(perms & RECURSIVE)) continue;
    383 
    384    // passed part starts with something on the whitelist
    385    if (StringBeginsWith(aPath, whiteListPath)) {
    386      allPerms |= perms;
    387    }
    388  }
    389 
    390  // Strip away the RECURSIVE flag as it doesn't
    391  // necessarily apply to aPath.
    392  return allPerms & ~RECURSIVE;
    393 }
    394 
    395 static bool AllowOperation(int aReqFlags, int aPerms) {
    396  int needed = 0;
    397  if (aReqFlags & R_OK) {
    398    needed |= SandboxBroker::MAY_READ;
    399  }
    400  if (aReqFlags & W_OK) {
    401    needed |= SandboxBroker::MAY_WRITE;
    402  }
    403  // We don't really allow executing anything,
    404  // so in true unix tradition we hijack this
    405  // for directory access (creation).
    406  if (aReqFlags & X_OK) {
    407    needed |= SandboxBroker::MAY_CREATE;
    408  }
    409  return (aPerms & needed) == needed;
    410 }
    411 
    412 static bool AllowAccess(int aReqFlags, int aPerms) {
    413  if (aReqFlags & ~(R_OK | W_OK | X_OK | F_OK)) {
    414    return false;
    415  }
    416  int needed = 0;
    417  if (aReqFlags & R_OK) {
    418    needed |= SandboxBroker::MAY_READ;
    419  }
    420  if (aReqFlags & W_OK) {
    421    needed |= SandboxBroker::MAY_WRITE;
    422  }
    423  return (aPerms & needed) == needed;
    424 }
    425 
    426 // These flags are added to all opens to prevent possible side-effects
    427 // on this process.  These shouldn't be relevant to the child process
    428 // in any case due to the sandboxing restrictions on it.  (See also
    429 // the use of MSG_CMSG_CLOEXEC in SandboxBrokerCommon.cpp).
    430 static const int kRequiredOpenFlags = O_CLOEXEC | O_NOCTTY;
    431 
    432 // Linux originally assigned a flag bit to O_SYNC but implemented the
    433 // semantics standardized as O_DSYNC; later, that bit was renamed and
    434 // a new bit was assigned to the full O_SYNC, and O_SYNC was redefined
    435 // to be both bits.  As a result, this #define is needed to compensate
    436 // for outdated kernel headers like Android's.
    437 #define O_SYNC_NEW 04010000
    438 static const int kAllowedOpenFlags =
    439    O_APPEND | O_DIRECT | O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME |
    440    O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY | O_SYNC_NEW | O_TRUNC |
    441    O_CLOEXEC | O_CREAT;
    442 #undef O_SYNC_NEW
    443 
    444 static bool AllowOpen(int aReqFlags, int aPerms) {
    445  if (aReqFlags & ~O_ACCMODE & ~kAllowedOpenFlags) {
    446    return false;
    447  }
    448  int needed;
    449  switch (aReqFlags & O_ACCMODE) {
    450    case O_RDONLY:
    451      needed = SandboxBroker::MAY_READ;
    452      break;
    453    case O_WRONLY:
    454      needed = SandboxBroker::MAY_WRITE;
    455      break;
    456    case O_RDWR:
    457      needed = SandboxBroker::MAY_READ | SandboxBroker::MAY_WRITE;
    458      break;
    459    default:
    460      return false;
    461  }
    462  if (aReqFlags & O_CREAT) {
    463    needed |= SandboxBroker::MAY_CREATE;
    464  }
    465  // Linux allows O_TRUNC even with O_RDONLY
    466  if (aReqFlags & O_TRUNC) {
    467    needed |= SandboxBroker::MAY_WRITE;
    468  }
    469  return (aPerms & needed) == needed;
    470 }
    471 
    472 static int DoStat(const char* aPath, statstruct* aBuff, int aFlags) {
    473  if (aFlags & O_NOFOLLOW) {
    474    return lstatsyscall(aPath, aBuff);
    475  }
    476  return statsyscall(aPath, aBuff);
    477 }
    478 
    479 static int DoLink(const char* aPath, const char* aPath2,
    480                  SandboxBrokerCommon::Operation aOper) {
    481  if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) {
    482    return link(aPath, aPath2);
    483  }
    484  if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
    485    return symlink(aPath, aPath2);
    486  }
    487  MOZ_CRASH("SandboxBroker: Unknown link operation");
    488 }
    489 
    490 static int DoConnect(const char* aPath, size_t aLen, int aType,
    491                     bool aIsAbstract) {
    492  // Deny SOCK_DGRAM for the same reason it's denied for socketpair.
    493  if (aType != SOCK_STREAM && aType != SOCK_SEQPACKET) {
    494    errno = EACCES;
    495    return -1;
    496  }
    497  // Ensure that the address is a pathname.  (An empty string
    498  // resulting from an abstract address probably shouldn't have made
    499  // it past the policy check, but check explicitly just in case.)
    500  if (aPath[0] == '\0') {
    501    errno = ENETUNREACH;
    502    return -1;
    503  }
    504 
    505  // Try to copy the name into a normal-sized sockaddr_un, with
    506  // null-termination. Specifically, from man page:
    507  //
    508  // When the address of an abstract socket is returned, the returned addrlen is
    509  // greater than sizeof(sa_family_t) (i.e., greater than 2), and the name of
    510  // the socket is contained in the first (addrlen - sizeof(sa_family_t)) bytes
    511  // of sun_path.
    512  //
    513  // As mentionned in `SandboxBrokerClient::Connect()`, `DoCall` expects a
    514  // null-terminated string while abstract socket are not. So we receive a copy
    515  // here and we have to put things back correctly as a real abstract socket to
    516  // perform the brokered `connect()` call.
    517  struct sockaddr_un sun;
    518  memset(&sun, 0, sizeof(sun));
    519  sun.sun_family = AF_UNIX;
    520  char* sunPath = sun.sun_path;
    521  size_t sunLen = sizeof(sun.sun_path);
    522  size_t addrLen = sizeof(sun);
    523  if (aIsAbstract) {
    524    *sunPath++ = '\0';
    525    sunLen--;
    526    addrLen = offsetof(struct sockaddr_un, sun_path) + aLen + 1;
    527  }
    528  if (aLen + 1 > sunLen) {
    529    errno = ENAMETOOLONG;
    530    return -1;
    531  }
    532  memcpy(sunPath, aPath, aLen);
    533 
    534  // Finally, the actual socket connection.
    535  const int fd = socket(AF_UNIX, aType | SOCK_CLOEXEC, 0);
    536  if (fd < 0) {
    537    return -1;
    538  }
    539  if (connect(fd, reinterpret_cast<struct sockaddr*>(&sun), addrLen) < 0) {
    540    close(fd);
    541    return -1;
    542  }
    543  return fd;
    544 }
    545 
    546 size_t SandboxBroker::RealPath(char* aPath, size_t aBufSize, size_t aPathLen) {
    547  char* result = realpath(aPath, nullptr);
    548  if (result != nullptr) {
    549    base::strlcpy(aPath, result, aBufSize);
    550    free(result);
    551    // Size changed, but guaranteed to be 0 terminated
    552    aPathLen = strlen(aPath);
    553  }
    554  return aPathLen;
    555 }
    556 
    557 size_t SandboxBroker::ConvertRelativePath(char* aPath, size_t aBufSize,
    558                                          size_t aPathLen) {
    559  if (strstr(aPath, "..") != nullptr) {
    560    return RealPath(aPath, aBufSize, aPathLen);
    561  }
    562  return aPathLen;
    563 }
    564 
    565 nsCString SandboxBroker::ReverseSymlinks(const nsACString& aPath) {
    566  // Revert any symlinks we previously resolved.
    567  int32_t cutLength = aPath.Length();
    568  nsCString cutPath(Substring(aPath, 0, cutLength));
    569 
    570  for (;;) {
    571    nsCString orig;
    572    bool found = mSymlinkMap.Get(cutPath, &orig);
    573    if (found) {
    574      orig.Append(Substring(aPath, cutLength, aPath.Length() - cutLength));
    575      return orig;
    576    }
    577    // Not found? Remove a path component and try again.
    578    int32_t pos = cutPath.RFindChar('/');
    579    if (pos == kNotFound || pos <= 0) {
    580      // will be empty
    581      return orig;
    582    } else {
    583      // Cut until just before the /
    584      cutLength = pos;
    585      cutPath.Assign(Substring(cutPath, 0, cutLength));
    586    }
    587  }
    588 }
    589 
    590 int SandboxBroker::SymlinkPermissions(const char* aPath,
    591                                      const size_t aPathLen) {
    592  // Work on a temporary copy, so we can reverse it.
    593  // Because we bail on a writable dir, SymlinkPath
    594  // might not restore the callers' path exactly.
    595  char pathBufSymlink[kMaxPathLen + 1];
    596  strcpy(pathBufSymlink, aPath);
    597 
    598  nsCString orig =
    599      ReverseSymlinks(nsDependentCString(pathBufSymlink, aPathLen));
    600  if (!orig.IsEmpty()) {
    601    if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    602      SANDBOX_LOG("Reversing %s -> %s", aPath, orig.get());
    603    }
    604    base::strlcpy(pathBufSymlink, orig.get(), sizeof(pathBufSymlink));
    605  }
    606 
    607  int perms = 0;
    608  // Resolve relative paths, propagate permissions and
    609  // fail if a symlink is in a writable path. The output is in perms.
    610  char* result =
    611      SandboxBroker::SymlinkPath(mPolicy.get(), pathBufSymlink, NULL, &perms);
    612  if (result != NULL) {
    613    free(result);
    614    // We finished the translation, so we have a usable return in "perms".
    615    return perms;
    616  } else {
    617    // Empty path means we got a writable dir in the chain or tried
    618    // to back out of a link target.
    619    return 0;
    620  }
    621 }
    622 
    623 void SandboxBroker::ThreadMain(void) {
    624  // Create a nsThread wrapper for the current platform thread, and register it
    625  // with the thread manager.
    626  (void)NS_GetCurrentThread();
    627 
    628  char threadName[kThreadNameMaxSize];
    629  // mChildPid can be max 7 digits because of the previous string size,
    630  // and 'FSBroker' is 8 bytes. The maximum thread size is 16 with the null byte
    631  // included. That leaves us 7 digits.
    632  SprintfLiteral(threadName, "FSBroker%d", mChildPid);
    633  PlatformThread::SetName(threadName);
    634 
    635  AUTO_PROFILER_REGISTER_THREAD(threadName);
    636 
    637  // The gtest SandboxBrokerMisc.* will loop and create / destroy SandboxBroker
    638  // taking a RefPtr<SandboxBroker> that gets destructed when getting out of
    639  // scope.
    640  //
    641  // Because Create() will start this thread we get into a situation where:
    642  //  - SandboxBrokerMisc creates SandboxBroker
    643  //    RefPtr = 1
    644  //  - SandboxBrokerMisc gtest is leaving the scope so destructor got called
    645  //    RefPtr = 0
    646  //  - this thread starts and add a new reference via that RefPtr
    647  //    RefPtr = 1
    648  //  - destructor does its job and closes the FD which triggers EOF and ends
    649  //    the while {} here
    650  //  - thread ends and gets out of scope so destructor gets called
    651  //    RefPtr = 0
    652  //
    653  // NS_ADDREF_THIS() / dont_AddRef(this) avoid this.
    654  RefPtr<SandboxBroker> deathGrip = dont_AddRef(this);
    655 
    656  // Permissive mode can only be enabled through an environment variable,
    657  // therefore it is sufficient to fetch the value once
    658  // before the main thread loop starts
    659  bool permissive = SandboxInfo::Get().Test(SandboxInfo::kPermissive);
    660 
    661  while (true) {
    662    struct iovec ios[2];
    663    // We will receive the path strings in 1 buffer and split them back up.
    664    char recvBuf[2 * (kMaxPathLen + 1)];
    665    char pathBuf[kMaxPathLen + 1];
    666    char pathBuf2[kMaxPathLen + 1];
    667    size_t pathLen = 0;
    668    size_t pathLen2 = 0;
    669    char respBuf[kMaxPathLen + 1];  // Also serves as struct stat
    670    Request req;
    671    Response resp;
    672    int respfd;
    673 
    674    // Make sure stat responses fit in the response buffer
    675    MOZ_ASSERT((kMaxPathLen + 1) > sizeof(struct stat));
    676 
    677    // This makes our string handling below a bit less error prone.
    678    memset(recvBuf, 0, sizeof(recvBuf));
    679 
    680    ios[0].iov_base = &req;
    681    ios[0].iov_len = sizeof(req);
    682    ios[1].iov_base = recvBuf;
    683    ios[1].iov_len = sizeof(recvBuf);
    684 
    685    const ssize_t recvd = RecvWithFd(mFileDesc, ios, 2, &respfd);
    686    if (recvd == 0) {
    687      if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    688        SANDBOX_LOG("EOF from pid %d", mChildPid);
    689      }
    690      break;
    691    }
    692    // It could be possible to continue after errors and short reads,
    693    // at least in some cases, but protocol violation indicates a
    694    // hostile client, so terminate the broker instead.
    695    if (recvd < 0) {
    696      SANDBOX_LOG_ERRNO("bad read from pid %d", mChildPid);
    697      shutdown(mFileDesc, SHUT_RD);
    698      break;
    699    }
    700    if (recvd < static_cast<ssize_t>(sizeof(req))) {
    701      SANDBOX_LOG("bad read from pid %d (%d < %d)", mChildPid, recvd,
    702                  sizeof(req));
    703      shutdown(mFileDesc, SHUT_RD);
    704      break;
    705    }
    706    if (respfd == -1) {
    707      SANDBOX_LOG("no response fd from pid %d", mChildPid);
    708      shutdown(mFileDesc, SHUT_RD);
    709      break;
    710    }
    711 
    712    // Initialize the response with the default failure.
    713    memset(&resp, 0, sizeof(resp));
    714    memset(&respBuf, 0, sizeof(respBuf));
    715    resp.mError = -EACCES;
    716    ios[0].iov_base = &resp;
    717    ios[0].iov_len = sizeof(resp);
    718    ios[1].iov_base = nullptr;
    719    ios[1].iov_len = 0;
    720    int openedFd = -1;
    721 
    722    // Clear permissions
    723    int perms;
    724 
    725    // Find end of first string, make sure the buffer is still
    726    // 0 terminated.
    727    size_t recvBufLen = static_cast<size_t>(recvd) - sizeof(req);
    728    if (recvBufLen > 0 && recvBuf[recvBufLen - 1] != 0) {
    729      SANDBOX_LOG("corrupted path buffer from pid %d", mChildPid);
    730      shutdown(mFileDesc, SHUT_RD);
    731      break;
    732    }
    733 
    734    // First path should fit in maximum path length buffer.
    735    size_t first_len = strlen(recvBuf);
    736    if (first_len <= kMaxPathLen) {
    737      strcpy(pathBuf, recvBuf);
    738      // Skip right over the terminating 0, and try to copy in the
    739      // second path, if any. If there's no path, this will hit a
    740      // 0 immediately (we nulled the buffer before receiving).
    741      // We do not assume the second path is 0-terminated, this is
    742      // enforced below.
    743      strncpy(pathBuf2, recvBuf + first_len + 1, kMaxPathLen);
    744 
    745      // First string is guaranteed to be 0-terminated.
    746      pathLen = first_len;
    747 
    748      // Look up the first pathname but first translate relative paths.
    749      pathLen = ConvertRelativePath(pathBuf, sizeof(pathBuf), pathLen);
    750      perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
    751 
    752      if (!perms) {
    753        // Did we arrive from a symlink in a path that is not writable?
    754        // Then try to figure out the original path and see if that is
    755        // readable. Work on the original path, this reverses
    756        // ConvertRelative above.
    757        int symlinkPerms = SymlinkPermissions(recvBuf, first_len);
    758        if (symlinkPerms > 0) {
    759          perms = symlinkPerms;
    760        }
    761      }
    762      if (!perms) {
    763        // Now try the opposite case: translate symlinks to their
    764        // actual destination file. Firefox always resolves symlinks,
    765        // and in most cases we have whitelisted fixed paths that
    766        // libraries will rely on and try to open. So this codepath
    767        // is mostly useful for Mesa which had its kernel interface
    768        // moved around.
    769        pathLen = RealPath(pathBuf, sizeof(pathBuf), pathLen);
    770        perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
    771      }
    772 
    773      // Same for the second path.
    774      pathLen2 = strnlen(pathBuf2, kMaxPathLen);
    775      if (pathLen2 > 0) {
    776        // Force 0 termination.
    777        pathBuf2[pathLen2] = '\0';
    778        pathLen2 = ConvertRelativePath(pathBuf2, sizeof(pathBuf2), pathLen2);
    779        int perms2 = mPolicy->Lookup(nsDependentCString(pathBuf2, pathLen2));
    780 
    781        // Take the intersection of the permissions for both paths.
    782        perms &= perms2;
    783      }
    784    } else {
    785      // Failed to receive intelligible paths.
    786      perms = 0;
    787    }
    788 
    789    // And now perform the operation if allowed.
    790    if (perms & CRASH_INSTEAD) {
    791      // This is somewhat nonmodular, but it works.
    792      resp.mError = -ENOSYS;
    793    } else if ((perms & FORCE_DENY) == FORCE_DENY) {
    794      resp.mError = -EACCES;
    795    } else if (permissive || perms & MAY_ACCESS) {
    796      // If the operation was only allowed because of permissive mode, log it.
    797      if (permissive && !(perms & MAY_ACCESS)) {
    798        AuditPermissive(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    799      }
    800 
    801      switch (req.mOp) {
    802        case SANDBOX_FILE_OPEN:
    803          if (permissive || AllowOpen(req.mFlags, perms)) {
    804            // Permissions for O_CREAT hardwired to 0600; if that's
    805            // ever a problem we can change the protocol (but really we
    806            // should be trying to remove uses of MAY_CREATE, not add
    807            // new ones).
    808            openedFd = open(pathBuf, req.mFlags | kRequiredOpenFlags, 0600);
    809            if (openedFd >= 0) {
    810              resp.mError = 0;
    811            } else {
    812              resp.mError = -errno;
    813            }
    814          } else {
    815            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    816          }
    817          break;
    818 
    819        case SANDBOX_FILE_ACCESS:
    820          if (permissive || AllowAccess(req.mFlags, perms)) {
    821            if (access(pathBuf, req.mFlags) == 0) {
    822              resp.mError = 0;
    823            } else {
    824              resp.mError = -errno;
    825            }
    826          } else {
    827            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    828          }
    829          break;
    830 
    831        case SANDBOX_FILE_STAT:
    832          MOZ_ASSERT(req.mBufSize == sizeof(statstruct));
    833          if (DoStat(pathBuf, (statstruct*)&respBuf, req.mFlags) == 0) {
    834            resp.mError = 0;
    835            ios[1].iov_base = &respBuf;
    836            ios[1].iov_len = sizeof(statstruct);
    837          } else {
    838            resp.mError = -errno;
    839          }
    840          break;
    841 
    842        case SANDBOX_FILE_CHMOD:
    843          if (permissive || AllowOperation(W_OK, perms)) {
    844            if (chmod(pathBuf, req.mFlags) == 0) {
    845              resp.mError = 0;
    846            } else {
    847              resp.mError = -errno;
    848            }
    849          } else {
    850            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    851          }
    852          break;
    853 
    854        case SANDBOX_FILE_LINK:
    855        case SANDBOX_FILE_SYMLINK:
    856          if (permissive || AllowOperation(W_OK | X_OK, perms)) {
    857            if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) {
    858              resp.mError = 0;
    859            } else {
    860              resp.mError = -errno;
    861            }
    862          } else {
    863            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    864          }
    865          break;
    866 
    867        case SANDBOX_FILE_RENAME:
    868          if (permissive || AllowOperation(W_OK | X_OK, perms)) {
    869            if (rename(pathBuf, pathBuf2) == 0) {
    870              resp.mError = 0;
    871            } else {
    872              resp.mError = -errno;
    873            }
    874          } else {
    875            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    876          }
    877          break;
    878 
    879        case SANDBOX_FILE_MKDIR:
    880          if (permissive || AllowOperation(W_OK | X_OK, perms)) {
    881            if (mkdir(pathBuf, req.mFlags) == 0) {
    882              resp.mError = 0;
    883            } else {
    884              resp.mError = -errno;
    885            }
    886          } else {
    887            struct stat sb;
    888            // This doesn't need an additional policy check because
    889            // MAY_ACCESS is required to even enter this switch statement.
    890            if (lstat(pathBuf, &sb) == 0) {
    891              resp.mError = -EEXIST;
    892            } else {
    893              AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    894            }
    895          }
    896          break;
    897 
    898        case SANDBOX_FILE_UNLINK:
    899          if (permissive || AllowOperation(W_OK | X_OK, perms)) {
    900            if (unlink(pathBuf) == 0) {
    901              resp.mError = 0;
    902            } else {
    903              resp.mError = -errno;
    904            }
    905          } else {
    906            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    907          }
    908          break;
    909 
    910        case SANDBOX_FILE_RMDIR:
    911          if (permissive || AllowOperation(W_OK | X_OK, perms)) {
    912            if (rmdir(pathBuf) == 0) {
    913              resp.mError = 0;
    914            } else {
    915              resp.mError = -errno;
    916            }
    917          } else {
    918            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    919          }
    920          break;
    921 
    922        case SANDBOX_FILE_READLINK:
    923          if (permissive || AllowOperation(R_OK, perms)) {
    924            ssize_t respSize =
    925                readlink(pathBuf, (char*)&respBuf, sizeof(respBuf));
    926            if (respSize >= 0) {
    927              if (respSize > 0) {
    928                // Record the mapping so we can invert the file to the original
    929                // symlink.
    930                nsDependentCString orig(pathBuf, pathLen);
    931                nsDependentCString xlat(respBuf, respSize);
    932                if (!orig.Equals(xlat) && xlat[0] == '/') {
    933                  if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    934                    SANDBOX_LOG("Recording mapping %s -> %s", xlat.get(),
    935                                orig.get());
    936                  }
    937                  mSymlinkMap.InsertOrUpdate(xlat, orig);
    938                }
    939                // Make sure we can invert a fully resolved mapping too. If our
    940                // caller is realpath, and there's a relative path involved, the
    941                // client side will try to open this one.
    942                char* resolvedBuf = realpath(pathBuf, nullptr);
    943                if (resolvedBuf) {
    944                  nsDependentCString resolvedXlat(resolvedBuf);
    945                  if (!orig.Equals(resolvedXlat) &&
    946                      !xlat.Equals(resolvedXlat)) {
    947                    if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    948                      SANDBOX_LOG("Recording mapping %s -> %s",
    949                                  resolvedXlat.get(), orig.get());
    950                    }
    951                    mSymlinkMap.InsertOrUpdate(resolvedXlat, orig);
    952                  }
    953                  free(resolvedBuf);
    954                }
    955              }
    956              // Truncate the reply to the size of the client's
    957              // buffer, matching the real readlink()'s behavior in
    958              // that case, and being careful with the input data.
    959              ssize_t callerSize =
    960                  std::max(AssertedCast<ssize_t>(req.mBufSize), ssize_t(0));
    961              respSize = std::min(respSize, callerSize);
    962              resp.mError = AssertedCast<int>(respSize);
    963              ios[1].iov_base = &respBuf;
    964              ios[1].iov_len = ReleaseAssertedCast<size_t>(respSize);
    965              MOZ_RELEASE_ASSERT(ios[1].iov_len <= sizeof(respBuf));
    966            } else {
    967              resp.mError = -errno;
    968            }
    969          } else {
    970            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    971          }
    972          break;
    973 
    974        case SANDBOX_SOCKET_CONNECT:
    975        case SANDBOX_SOCKET_CONNECT_ABSTRACT:
    976          if (permissive || (perms & MAY_CONNECT) != 0) {
    977            openedFd = DoConnect(pathBuf, pathLen, req.mFlags,
    978                                 req.mOp == SANDBOX_SOCKET_CONNECT_ABSTRACT);
    979            if (openedFd >= 0) {
    980              resp.mError = 0;
    981            } else {
    982              resp.mError = -errno;
    983            }
    984          } else {
    985            AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    986          }
    987          break;
    988      }
    989    } else {
    990      MOZ_ASSERT(perms == 0);
    991      AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf);
    992    }
    993 
    994    const size_t numIO = ios[1].iov_len > 0 ? 2 : 1;
    995    const ssize_t sent = SendWithFd(respfd, ios, numIO, openedFd);
    996    if (sent < 0) {
    997      SANDBOX_LOG_ERRNO("failed to send broker response to pid %d", mChildPid);
    998    } else {
    999      MOZ_ASSERT(static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len);
   1000    }
   1001 
   1002    // Work around Linux kernel bug: recvmsg checks for pending data
   1003    // and then checks for EOF or shutdown, without synchronization;
   1004    // if the sendmsg and last close occur between those points, it
   1005    // will see no pending data (before) and a closed socket (after),
   1006    // and incorrectly return EOF even though there is a message to be
   1007    // read.  To avoid this, we send an extra message with a reference
   1008    // to respfd, so the last close can't happen until after the real
   1009    // response is read.
   1010    //
   1011    // See also: https://bugzil.la/1243108#c48
   1012    const struct Response fakeResp = {-4095};
   1013    const struct iovec fakeIO = {const_cast<Response*>(&fakeResp),
   1014                                 sizeof(fakeResp)};
   1015    // If the client has already read the real response and closed its
   1016    // socket then this will fail, but that's fine.
   1017    if (SendWithFd(respfd, &fakeIO, 1, respfd) < 0) {
   1018      MOZ_ASSERT(errno == EPIPE || errno == ECONNREFUSED || errno == ENOTCONN);
   1019    }
   1020 
   1021    close(respfd);
   1022 
   1023    if (openedFd >= 0) {
   1024      close(openedFd);
   1025    }
   1026  }
   1027 }
   1028 
   1029 void SandboxBroker::AuditPermissive(int aOp, int aFlags, uint64_t aId,
   1030                                    int aPerms, const char* aPath) {
   1031  MOZ_RELEASE_ASSERT(SandboxInfo::Get().Test(SandboxInfo::kPermissive));
   1032 
   1033  struct stat statBuf;
   1034 
   1035  if (lstat(aPath, &statBuf) == 0) {
   1036    // Path exists, set errno to 0 to indicate "success".
   1037    errno = 0;
   1038  }
   1039 
   1040  SANDBOX_LOG_ERRNO(
   1041      "SandboxBroker: would have denied op=%s rflags=%o perms=%d path=%s for "
   1042      "pid=%d permissive=1; real status",
   1043      OperationDescription[aOp], aFlags, aPerms, aPath, mChildPid);
   1044  SandboxProfiler::ReportAudit("SandboxBroker::AuditPermissive",
   1045                               OperationDescription[aOp], aFlags, aId, aPerms,
   1046                               aPath, mChildPid);
   1047 }
   1048 
   1049 void SandboxBroker::AuditDenial(int aOp, int aFlags, uint64_t aId, int aPerms,
   1050                                const char* aPath) {
   1051  if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
   1052    SANDBOX_LOG(
   1053        "SandboxBroker: denied op=%s rflags=%o perms=%d path=%s for pid=%d",
   1054        OperationDescription[aOp], aFlags, aPerms, aPath, mChildPid);
   1055  }
   1056  SandboxProfiler::ReportAudit("SandboxBroker::AuditDenial",
   1057                               OperationDescription[aOp], aFlags, aId, aPerms,
   1058                               aPath, mChildPid);
   1059 }
   1060 
   1061 }  // namespace mozilla