tor-browser

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

TestBroker.cpp (22980B)


      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 "gtest/gtest.h"
      8 
      9 #include "broker/SandboxBroker.h"
     10 #include "broker/SandboxBrokerUtils.h"
     11 #include "SandboxBrokerClient.h"
     12 
     13 #include <errno.h>
     14 #include <fcntl.h>
     15 #include <pthread.h>
     16 #include <stdlib.h>
     17 #include <sched.h>
     18 #include <semaphore.h>
     19 #include <sys/resource.h>
     20 #include <sys/stat.h>
     21 #include <sys/types.h>
     22 #include <time.h>
     23 #include <unistd.h>
     24 
     25 #include "mozilla/Atomics.h"
     26 #include "mozilla/PodOperations.h"
     27 #include "mozilla/UniquePtr.h"
     28 #include "mozilla/ipc/FileDescriptor.h"
     29 #include "nsIThreadManager.h"
     30 
     31 namespace mozilla {
     32 
     33 class SandboxBrokerTest : public ::testing::Test {
     34  static const int MAY_ACCESS = SandboxBroker::MAY_ACCESS;
     35  static const int MAY_READ = SandboxBroker::MAY_READ;
     36  static const int MAY_WRITE = SandboxBroker::MAY_WRITE;
     37  static const int MAY_CREATE = SandboxBroker::MAY_CREATE;
     38  static const auto AddAlways = SandboxBroker::Policy::AddAlways;
     39 
     40  RefPtr<SandboxBroker> mServer;
     41  UniquePtr<SandboxBrokerClient> mClient;
     42 
     43  UniquePtr<const SandboxBroker::Policy> GetPolicy() const;
     44 
     45  template <class C, void (C::*Main)()>
     46  static void* ThreadMain(void* arg) {
     47    (static_cast<C*>(arg)->*Main)();
     48    return nullptr;
     49  }
     50 
     51 protected:
     52  int Open(const char* aPath, int aFlags) {
     53    return mClient->Open(aPath, aFlags);
     54  }
     55  int Access(const char* aPath, int aMode) {
     56    return mClient->Access(aPath, aMode);
     57  }
     58  int Stat(const char* aPath, statstruct* aStat) {
     59    return mClient->Stat(aPath, aStat);
     60  }
     61  int LStat(const char* aPath, statstruct* aStat) {
     62    return mClient->LStat(aPath, aStat);
     63  }
     64  int Chmod(const char* aPath, int aMode) {
     65    return mClient->Chmod(aPath, aMode);
     66  }
     67  int Link(const char* aPath, const char* bPath) {
     68    return mClient->Link(aPath, bPath);
     69  }
     70  int Mkdir(const char* aPath, int aMode) {
     71    return mClient->Mkdir(aPath, aMode);
     72  }
     73  int Symlink(const char* aPath, const char* bPath) {
     74    return mClient->Symlink(aPath, bPath);
     75  }
     76  int Rename(const char* aPath, const char* bPath) {
     77    return mClient->Rename(aPath, bPath);
     78  }
     79  int Rmdir(const char* aPath) { return mClient->Rmdir(aPath); }
     80  int Unlink(const char* aPath) { return mClient->Unlink(aPath); }
     81  ssize_t Readlink(const char* aPath, char* aBuff, size_t aSize) {
     82    return mClient->Readlink(aPath, aBuff, aSize);
     83  }
     84 
     85  void SetUp() override {
     86    ipc::FileDescriptor fd;
     87 
     88    mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd);
     89    ASSERT_NE(mServer, nullptr);
     90    ASSERT_TRUE(fd.IsValid());
     91    auto rawFD = fd.TakePlatformHandle();
     92    mClient.reset(new SandboxBrokerClient(rawFD.release()));
     93  }
     94 
     95  template <class C, void (C::*Main)()>
     96  void StartThread(pthread_t* aThread) {
     97    ASSERT_EQ(0, pthread_create(aThread, nullptr, ThreadMain<C, Main>,
     98                                static_cast<C*>(this)));
     99  }
    100 
    101  template <class C, void (C::*Main)()>
    102  void RunOnManyThreads() {
    103    static const int kNumThreads = 5;
    104    pthread_t threads[kNumThreads];
    105    for (pthread_t& thread : threads) {
    106      StartThread<C, Main>(&thread);
    107    }
    108    for (pthread_t thread : threads) {
    109      void* retval;
    110      ASSERT_EQ(pthread_join(thread, &retval), 0);
    111      ASSERT_EQ(retval, static_cast<void*>(nullptr));
    112    }
    113  }
    114 
    115 public:
    116  void MultiThreadOpenWorker();
    117  void MultiThreadStatWorker();
    118 };
    119 
    120 UniquePtr<const SandboxBroker::Policy> SandboxBrokerTest::GetPolicy() const {
    121  UniquePtr<SandboxBroker::Policy> policy(new SandboxBroker::Policy());
    122 
    123  policy->AddPath(MAY_READ | MAY_WRITE, "/dev/null", AddAlways);
    124  policy->AddPath(MAY_READ, "/dev/zero", AddAlways);
    125  policy->AddPath(MAY_READ, "/var/empty/qwertyuiop", AddAlways);
    126  policy->AddPath(MAY_ACCESS, "/proc/self",
    127                  AddAlways);  // Warning: Linux-specific.
    128  policy->AddPath(MAY_READ | MAY_WRITE, "/tmp", AddAlways);
    129  policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublu", AddAlways);
    130  policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublublu",
    131                  AddAlways);
    132  // This should be non-writable by the user running the test:
    133  policy->AddPath(MAY_READ | MAY_WRITE, "/etc", AddAlways);
    134 
    135  return std::move(policy);
    136 }
    137 
    138 TEST_F(SandboxBrokerTest, OpenForRead) {
    139  int fd;
    140 
    141  fd = Open("/dev/null", O_RDONLY);
    142  ASSERT_GE(fd, 0) << "Opening /dev/null failed.";
    143  close(fd);
    144  fd = Open("/dev/zero", O_RDONLY);
    145  ASSERT_GE(fd, 0) << "Opening /dev/zero failed.";
    146  close(fd);
    147  fd = Open("/var/empty/qwertyuiop", O_RDONLY);
    148  EXPECT_EQ(-ENOENT, fd) << "Opening allowed but nonexistent file succeeded.";
    149  fd = Open("/proc/self", O_RDONLY);
    150  EXPECT_EQ(-EACCES, fd) << "Opening stat-only file for read succeeded.";
    151  fd = Open("/proc/self/stat", O_RDONLY);
    152  EXPECT_EQ(-EACCES, fd) << "Opening disallowed file succeeded.";
    153 }
    154 
    155 TEST_F(SandboxBrokerTest, OpenForWrite) {
    156  int fd;
    157 
    158  fd = Open("/dev/null", O_WRONLY);
    159  ASSERT_GE(fd, 0) << "Opening /dev/null write-only failed.";
    160  close(fd);
    161  fd = Open("/dev/null", O_RDWR);
    162  ASSERT_GE(fd, 0) << "Opening /dev/null read/write failed.";
    163  close(fd);
    164  fd = Open("/dev/zero", O_WRONLY);
    165  ASSERT_EQ(-EACCES, fd)
    166      << "Opening read-only-by-policy file write-only succeeded.";
    167  fd = Open("/dev/zero", O_RDWR);
    168  ASSERT_EQ(-EACCES, fd)
    169      << "Opening read-only-by-policy file read/write succeeded.";
    170 }
    171 
    172 TEST_F(SandboxBrokerTest, SimpleRead) {
    173  int fd;
    174  char c;
    175 
    176  fd = Open("/dev/null", O_RDONLY);
    177  ASSERT_GE(fd, 0);
    178  EXPECT_EQ(0, read(fd, &c, 1));
    179  close(fd);
    180  fd = Open("/dev/zero", O_RDONLY);
    181  ASSERT_GE(fd, 0);
    182  ASSERT_EQ(1, read(fd, &c, 1));
    183  EXPECT_EQ(c, '\0');
    184 }
    185 
    186 TEST_F(SandboxBrokerTest, BadFlags) {
    187  int fd;
    188 
    189  fd = Open("/dev/null", O_RDWR | O_ASYNC);
    190  EXPECT_EQ(-EACCES, fd) << "O_ASYNC is banned.";
    191 
    192  fd = Open("/dev/null", O_RDWR | 0x40000000);
    193  EXPECT_EQ(-EACCES, fd) << "Unknown flag 0x40000000 is banned.";
    194 }
    195 
    196 TEST_F(SandboxBrokerTest, Access) {
    197  EXPECT_EQ(0, Access("/dev/null", F_OK));
    198  EXPECT_EQ(0, Access("/dev/null", R_OK));
    199  EXPECT_EQ(0, Access("/dev/null", W_OK));
    200  EXPECT_EQ(0, Access("/dev/null", R_OK | W_OK));
    201  EXPECT_EQ(-EACCES, Access("/dev/null", X_OK));
    202  EXPECT_EQ(-EACCES, Access("/dev/null", R_OK | X_OK));
    203 
    204  EXPECT_EQ(0, Access("/dev/zero", R_OK));
    205  EXPECT_EQ(-EACCES, Access("/dev/zero", W_OK));
    206  EXPECT_EQ(-EACCES, Access("/dev/zero", R_OK | W_OK));
    207 
    208  EXPECT_EQ(-ENOENT, Access("/var/empty/qwertyuiop", R_OK));
    209  EXPECT_EQ(-EACCES, Access("/var/empty/qwertyuiop", W_OK));
    210 
    211  EXPECT_EQ(0, Access("/proc/self", F_OK));
    212  EXPECT_EQ(-EACCES, Access("/proc/self", R_OK));
    213 
    214  EXPECT_EQ(-EACCES, Access("/proc/self/stat", F_OK));
    215 
    216  EXPECT_EQ(0, Access("/tmp", X_OK));
    217  EXPECT_EQ(0, Access("/tmp", R_OK | X_OK));
    218  EXPECT_EQ(0, Access("/tmp", R_OK | W_OK | X_OK));
    219  EXPECT_EQ(0, Access("/proc/self", X_OK));
    220 
    221  EXPECT_EQ(0, Access("/etc", R_OK | X_OK));
    222  EXPECT_EQ(-EACCES, Access("/etc", W_OK));
    223 }
    224 
    225 TEST_F(SandboxBrokerTest, Stat) {
    226  statstruct realStat, brokeredStat;
    227  ASSERT_EQ(0, statsyscall("/dev/null", &realStat)) << "Shouldn't ever fail!";
    228  EXPECT_EQ(0, Stat("/dev/null", &brokeredStat));
    229  EXPECT_EQ(realStat.st_ino, brokeredStat.st_ino);
    230  EXPECT_EQ(realStat.st_rdev, brokeredStat.st_rdev);
    231 
    232 #if defined(__clang__) || defined(__GNUC__)
    233 #  pragma GCC diagnostic push
    234 #  pragma GCC diagnostic ignored "-Wnonnull"
    235 #endif
    236  EXPECT_EQ(-1, statsyscall(nullptr, &realStat));
    237  EXPECT_EQ(errno, EFAULT);
    238 
    239  EXPECT_EQ(-EFAULT, Stat(nullptr, &brokeredStat));
    240 #if defined(__clang__) || defined(__GNUC__)
    241 #  pragma GCC diagnostic pop
    242 #endif
    243 
    244  EXPECT_EQ(-ENOENT, Stat("/var/empty/qwertyuiop", &brokeredStat));
    245  EXPECT_EQ(-EACCES, Stat("/dev", &brokeredStat));
    246 
    247  EXPECT_EQ(0, Stat("/proc/self", &brokeredStat));
    248  EXPECT_TRUE(S_ISDIR(brokeredStat.st_mode));
    249 }
    250 
    251 TEST_F(SandboxBrokerTest, LStat) {
    252  statstruct realStat, brokeredStat;
    253  ASSERT_EQ(0, lstatsyscall("/dev/null", &realStat));
    254  EXPECT_EQ(0, LStat("/dev/null", &brokeredStat));
    255  EXPECT_EQ(realStat.st_ino, brokeredStat.st_ino);
    256  EXPECT_EQ(realStat.st_rdev, brokeredStat.st_rdev);
    257 
    258  EXPECT_EQ(-ENOENT, LStat("/var/empty/qwertyuiop", &brokeredStat));
    259  EXPECT_EQ(-EACCES, LStat("/dev", &brokeredStat));
    260 
    261  EXPECT_EQ(0, LStat("/proc/self", &brokeredStat));
    262  EXPECT_TRUE(S_ISLNK(brokeredStat.st_mode));
    263 }
    264 
    265 static void PrePostTestCleanup(void) {
    266  unlink("/tmp/blublu");
    267  rmdir("/tmp/blublu");
    268  unlink("/tmp/nope");
    269  rmdir("/tmp/nope");
    270  unlink("/tmp/blublublu");
    271  rmdir("/tmp/blublublu");
    272 }
    273 
    274 TEST_F(SandboxBrokerTest, Chmod) {
    275  PrePostTestCleanup();
    276 
    277  int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
    278  ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
    279  close(fd);
    280  // Set read only. SandboxBroker enforces 0600 mode flags.
    281  ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR));
    282  EXPECT_EQ(-EACCES, Access("/tmp/blublu", W_OK));
    283  statstruct realStat;
    284  EXPECT_EQ(0, statsyscall("/tmp/blublu", &realStat));
    285  EXPECT_EQ((mode_t)S_IRUSR, realStat.st_mode & 0777);
    286 
    287  ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR | S_IWUSR));
    288  EXPECT_EQ(0, statsyscall("/tmp/blublu", &realStat));
    289  EXPECT_EQ((mode_t)(S_IRUSR | S_IWUSR), realStat.st_mode & 0777);
    290  EXPECT_EQ(0, unlink("/tmp/blublu"));
    291 
    292  PrePostTestCleanup();
    293 }
    294 
    295 TEST_F(SandboxBrokerTest, Link) {
    296  PrePostTestCleanup();
    297 
    298  int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
    299  ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
    300  close(fd);
    301  ASSERT_EQ(0, Link("/tmp/blublu", "/tmp/blublublu"));
    302  EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
    303  // Not whitelisted target path
    304  EXPECT_EQ(-EACCES, Link("/tmp/blublu", "/tmp/nope"));
    305  EXPECT_EQ(0, unlink("/tmp/blublublu"));
    306  EXPECT_EQ(0, unlink("/tmp/blublu"));
    307 
    308  PrePostTestCleanup();
    309 }
    310 
    311 TEST_F(SandboxBrokerTest, Symlink) {
    312  PrePostTestCleanup();
    313 
    314  int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
    315  ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
    316  close(fd);
    317  ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
    318  EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
    319  statstruct aStat;
    320  ASSERT_EQ(0, lstatsyscall("/tmp/blublublu", &aStat));
    321  EXPECT_EQ((mode_t)S_IFLNK, aStat.st_mode & S_IFMT);
    322  // Not whitelisted target path
    323  EXPECT_EQ(-EACCES, Symlink("/tmp/blublu", "/tmp/nope"));
    324  EXPECT_EQ(0, unlink("/tmp/blublublu"));
    325  EXPECT_EQ(0, unlink("/tmp/blublu"));
    326 
    327  PrePostTestCleanup();
    328 }
    329 
    330 TEST_F(SandboxBrokerTest, Mkdir) {
    331  PrePostTestCleanup();
    332 
    333  ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
    334      << "Creating dir /tmp/blublu failed.";
    335  EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
    336  // Not whitelisted target path
    337  EXPECT_EQ(-EACCES, Mkdir("/tmp/nope", 0600))
    338      << "Creating dir without MAY_CREATE succeed.";
    339  EXPECT_EQ(0, rmdir("/tmp/blublu"));
    340  EXPECT_EQ(-EEXIST, Mkdir("/proc/self", 0600))
    341      << "Creating uncreatable dir that already exists didn't fail correctly.";
    342  EXPECT_EQ(-EEXIST, Mkdir("/dev/zero", 0600))
    343      << "Creating uncreatable dir over preexisting file didn't fail "
    344         "correctly.";
    345 
    346  PrePostTestCleanup();
    347 }
    348 
    349 TEST_F(SandboxBrokerTest, Rename) {
    350  PrePostTestCleanup();
    351 
    352  ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
    353      << "Creating dir /tmp/blublu failed.";
    354  EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
    355  ASSERT_EQ(0, Rename("/tmp/blublu", "/tmp/blublublu"));
    356  EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
    357  EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
    358  // Not whitelisted target path
    359  EXPECT_EQ(-EACCES, Rename("/tmp/blublublu", "/tmp/nope"))
    360      << "Renaming dir without write access succeed.";
    361  EXPECT_EQ(0, rmdir("/tmp/blublublu"));
    362 
    363  PrePostTestCleanup();
    364 }
    365 
    366 TEST_F(SandboxBrokerTest, Rmdir) {
    367  PrePostTestCleanup();
    368 
    369  ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
    370      << "Creating dir /tmp/blublu failed.";
    371  EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
    372  ASSERT_EQ(0, Rmdir("/tmp/blublu"));
    373  EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
    374  // Bypass sandbox to create a non-deletable dir
    375  ASSERT_EQ(0, mkdir("/tmp/nope", 0600));
    376  EXPECT_EQ(-EACCES, Rmdir("/tmp/nope"));
    377 
    378  PrePostTestCleanup();
    379 }
    380 
    381 TEST_F(SandboxBrokerTest, Unlink) {
    382  PrePostTestCleanup();
    383 
    384  int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
    385  ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
    386  close(fd);
    387  EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
    388  EXPECT_EQ(0, Unlink("/tmp/blublu"));
    389  EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
    390  // Bypass sandbox to write a non-deletable file
    391  fd = open("/tmp/nope", O_WRONLY | O_CREAT, 0600);
    392  ASSERT_GE(fd, 0) << "Opening /tmp/nope for writing failed.";
    393  close(fd);
    394  EXPECT_EQ(-EACCES, Unlink("/tmp/nope"));
    395 
    396  PrePostTestCleanup();
    397 }
    398 
    399 TEST_F(SandboxBrokerTest, Readlink) {
    400  PrePostTestCleanup();
    401 
    402  int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
    403  ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
    404  close(fd);
    405  ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
    406  EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
    407  char linkBuff[256];
    408  EXPECT_EQ(11, Readlink("/tmp/blublublu", linkBuff, sizeof(linkBuff)));
    409  linkBuff[11] = '\0';
    410  EXPECT_EQ(0, strcmp(linkBuff, "/tmp/blublu"));
    411 
    412  PrePostTestCleanup();
    413 }
    414 
    415 TEST_F(SandboxBrokerTest, MultiThreadOpen) {
    416  RunOnManyThreads<SandboxBrokerTest,
    417                   &SandboxBrokerTest::MultiThreadOpenWorker>();
    418 }
    419 void SandboxBrokerTest::MultiThreadOpenWorker() {
    420  static const int kNumLoops = 10000;
    421 
    422  for (int i = 1; i <= kNumLoops; ++i) {
    423    int nullfd = Open("/dev/null", O_RDONLY);
    424    int zerofd = Open("/dev/zero", O_RDONLY);
    425    ASSERT_GE(nullfd, 0) << "Loop " << i << "/" << kNumLoops;
    426    ASSERT_GE(zerofd, 0) << "Loop " << i << "/" << kNumLoops;
    427    char c;
    428    ASSERT_EQ(0, read(nullfd, &c, 1)) << "Loop " << i << "/" << kNumLoops;
    429    ASSERT_EQ(1, read(zerofd, &c, 1)) << "Loop " << i << "/" << kNumLoops;
    430    ASSERT_EQ('\0', c) << "Loop " << i << "/" << kNumLoops;
    431    close(nullfd);
    432    close(zerofd);
    433  }
    434 }
    435 
    436 TEST_F(SandboxBrokerTest, MultiThreadStat) {
    437  RunOnManyThreads<SandboxBrokerTest,
    438                   &SandboxBrokerTest::MultiThreadStatWorker>();
    439 }
    440 void SandboxBrokerTest::MultiThreadStatWorker() {
    441  static const int kNumLoops = 7500;
    442  statstruct nullStat, zeroStat, selfStat;
    443  dev_t realNullDev, realZeroDev;
    444  ino_t realSelfInode;
    445 
    446  ASSERT_EQ(0, statsyscall("/dev/null", &nullStat)) << "Shouldn't ever fail!";
    447  ASSERT_EQ(0, statsyscall("/dev/zero", &zeroStat)) << "Shouldn't ever fail!";
    448  ASSERT_EQ(0, lstatsyscall("/proc/self", &selfStat)) << "Shouldn't ever fail!";
    449  ASSERT_TRUE(S_ISLNK(selfStat.st_mode))
    450  << "Shouldn't ever fail!";
    451  realNullDev = nullStat.st_rdev;
    452  realZeroDev = zeroStat.st_rdev;
    453  realSelfInode = selfStat.st_ino;
    454  for (int i = 1; i <= kNumLoops; ++i) {
    455    ASSERT_EQ(0, Stat("/dev/null", &nullStat))
    456        << "Loop " << i << "/" << kNumLoops;
    457    ASSERT_EQ(0, Stat("/dev/zero", &zeroStat))
    458        << "Loop " << i << "/" << kNumLoops;
    459    ASSERT_EQ(0, LStat("/proc/self", &selfStat))
    460        << "Loop " << i << "/" << kNumLoops;
    461 
    462    ASSERT_EQ(realNullDev, nullStat.st_rdev)
    463        << "Loop " << i << "/" << kNumLoops;
    464    ASSERT_EQ(realZeroDev, zeroStat.st_rdev)
    465        << "Loop " << i << "/" << kNumLoops;
    466    ASSERT_TRUE(S_ISLNK(selfStat.st_mode))
    467    << "Loop " << i << "/" << kNumLoops;
    468    ASSERT_EQ(realSelfInode, selfStat.st_ino)
    469        << "Loop " << i << "/" << kNumLoops;
    470  }
    471 }
    472 
    473 #if 0
    474 class SandboxBrokerSigStress : public SandboxBrokerTest
    475 {
    476  int mSigNum;
    477  struct sigaction mOldAction;
    478  Atomic<void*> mVoidPtr;
    479 
    480  static void SigHandler(int aSigNum, siginfo_t* aSigInfo, void *aCtx) {
    481    ASSERT_EQ(SI_QUEUE, aSigInfo->si_code);
    482    SandboxBrokerSigStress* that =
    483      static_cast<SandboxBrokerSigStress*>(aSigInfo->si_value.sival_ptr);
    484    ASSERT_EQ(that->mSigNum, aSigNum);
    485    that->DoSomething();
    486  }
    487 
    488 protected:
    489  Atomic<int> mTestIter;
    490  sem_t mSemaphore;
    491 
    492  void SignalThread(pthread_t aThread) {
    493    union sigval sv;
    494    sv.sival_ptr = this;
    495    ASSERT_NE(0, mSigNum);
    496    ASSERT_EQ(0, pthread_sigqueue(aThread, mSigNum, sv));
    497  }
    498 
    499  virtual void SetUp() {
    500    ASSERT_EQ(0, sem_init(&mSemaphore, 0, 0));
    501    mVoidPtr = nullptr;
    502    mSigNum = 0;
    503    for (int sigNum = SIGRTMIN; sigNum < SIGRTMAX; ++sigNum) {
    504      ASSERT_EQ(0, sigaction(sigNum, nullptr, &mOldAction));
    505      if ((mOldAction.sa_flags & SA_SIGINFO) == 0 &&
    506          mOldAction.sa_handler == SIG_DFL) {
    507        struct sigaction newAction;
    508        PodZero(&newAction);
    509        newAction.sa_flags = SA_SIGINFO;
    510        newAction.sa_sigaction = SigHandler;
    511        ASSERT_EQ(0, sigaction(sigNum, &newAction, nullptr));
    512        mSigNum = sigNum;
    513        break;
    514      }
    515    }
    516    ASSERT_NE(mSigNum, 0);
    517 
    518    SandboxBrokerTest::SetUp();
    519  }
    520 
    521  virtual void TearDown() {
    522    ASSERT_EQ(0, sem_destroy(&mSemaphore));
    523    if (mSigNum != 0) {
    524      ASSERT_EQ(0, sigaction(mSigNum, &mOldAction, nullptr));
    525    }
    526    if (mVoidPtr) {
    527      free(mVoidPtr);
    528    }
    529  }
    530 
    531  void DoSomething();
    532 
    533 public:
    534  void MallocWorker();
    535  void FreeWorker();
    536 };
    537 
    538 TEST_F(SandboxBrokerSigStress, StressTest)
    539 {
    540  static const int kIters = 6250;
    541  static const int kNsecPerIterPerIter = 4;
    542  struct timespec delay = { 0, 0 };
    543  pthread_t threads[2];
    544 
    545  mTestIter = kIters;
    546 
    547  StartThread<SandboxBrokerSigStress,
    548              &SandboxBrokerSigStress::MallocWorker>(&threads[0]);
    549  StartThread<SandboxBrokerSigStress,
    550              &SandboxBrokerSigStress::FreeWorker>(&threads[1]);
    551 
    552  for (int i = kIters; i > 0; --i) {
    553    SignalThread(threads[i % 2]);
    554    while (sem_wait(&mSemaphore) == -1 && errno == EINTR)
    555      /* retry */;
    556    ASSERT_EQ(i - 1, mTestIter);
    557    delay.tv_nsec += kNsecPerIterPerIter;
    558    struct timespec req = delay, rem;
    559    while (nanosleep(&req, &rem) == -1 && errno == EINTR) {
    560      req = rem;
    561    }
    562  }
    563  void *retval;
    564  ASSERT_EQ(0, pthread_join(threads[0], &retval));
    565  ASSERT_EQ(nullptr, retval);
    566  ASSERT_EQ(0, pthread_join(threads[1], &retval));
    567  ASSERT_EQ(nullptr, retval);
    568 }
    569 
    570 void
    571 SandboxBrokerSigStress::MallocWorker()
    572 {
    573  static const size_t kSize = 64;
    574 
    575  void* mem = malloc(kSize);
    576  while (mTestIter > 0) {
    577    ASSERT_NE(mem, mVoidPtr);
    578    mem = mVoidPtr.exchange(mem);
    579    if (mem) {
    580      sched_yield();
    581    } else {
    582      mem = malloc(kSize);
    583    }
    584  }
    585  if (mem) {
    586    free(mem);
    587  }
    588 }
    589 
    590 void
    591 SandboxBrokerSigStress::FreeWorker()
    592 {
    593  void *mem = nullptr;
    594  while (mTestIter > 0) {
    595    mem = mVoidPtr.exchange(mem);
    596    if (mem) {
    597      free(mem);
    598      mem = nullptr;
    599    } else {
    600      sched_yield();
    601    }
    602  }
    603 }
    604 
    605 void
    606 SandboxBrokerSigStress::DoSomething()
    607 {
    608  int fd;
    609  char c;
    610  struct stat st;
    611 
    612  //fprintf(stderr, "Don't try this at home: %d\n", static_cast<int>(mTestIter));
    613  switch (mTestIter % 5) {
    614  case 0:
    615    fd = Open("/dev/null", O_RDWR);
    616    ASSERT_GE(fd, 0);
    617    ASSERT_EQ(0, read(fd, &c, 1));
    618    close(fd);
    619    break;
    620  case 1:
    621    fd = Open("/dev/zero", O_RDONLY);
    622    ASSERT_GE(fd, 0);
    623    ASSERT_EQ(1, read(fd, &c, 1));
    624    ASSERT_EQ('\0', c);
    625    close(fd);
    626    break;
    627  case 2:
    628    ASSERT_EQ(0, Access("/dev/null", W_OK));
    629    break;
    630  case 3:
    631    ASSERT_EQ(0, Stat("/proc/self", &st));
    632    ASSERT_TRUE(S_ISDIR(st.st_mode));
    633    break;
    634  case 4:
    635    ASSERT_EQ(0, LStat("/proc/self", &st));
    636    ASSERT_TRUE(S_ISLNK(st.st_mode));
    637    break;
    638  }
    639  mTestIter--;
    640  sem_post(&mSemaphore);
    641 }
    642 #endif
    643 
    644 // Check for fd leaks when creating/destroying a broker instance (bug
    645 // 1719391).
    646 //
    647 // (This uses a different test group because it doesn't use the
    648 // fixture class, and gtest doesn't allow mixing TEST and TEST_F in
    649 // the same group.)
    650 TEST(SandboxBrokerMisc, FileDescLeak)
    651 {
    652  // If this value is increased in the future, check that it won't
    653  // cause the test to take an excessive amount of time.
    654  // (Alternately, this test could be changed to run a smaller number
    655  // of cycles and check for increased fd usage afterwards; some care
    656  // would be needed to avoid false positives.)
    657  static constexpr size_t kCycles = 4096;
    658  struct rlimit oldLimit;
    659  bool changedLimit = false;
    660 
    661  // At the time of this writing, we raise the fd soft limit to 4096
    662  // (or to the hard limit, if less than that), but we don't lower it
    663  // if it was higher than 4096.  To allow for that case, or if
    664  // Gecko's preferred limit changes, the limit is reduced while this
    665  // test is running:
    666  ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno);
    667  if (oldLimit.rlim_cur == RLIM_INFINITY ||
    668      oldLimit.rlim_cur > static_cast<rlim_t>(kCycles)) {
    669    struct rlimit newLimit = oldLimit;
    670    newLimit.rlim_cur = static_cast<rlim_t>(kCycles);
    671    ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &newLimit), 0) << strerror(errno);
    672    changedLimit = true;
    673  }
    674 
    675  pid_t pid = getpid();
    676  for (size_t i = 0; i < kCycles; ++i) {
    677    auto policy = MakeUnique<SandboxBroker::Policy>();
    678    // Currently nothing in `Create` tries to check for or
    679    // special-case an empty policy, but just in case:
    680    policy->AddPath(SandboxBroker::MAY_READ, "/dev/null",
    681                    SandboxBroker::Policy::AddAlways);
    682    ipc::FileDescriptor fd;
    683    RefPtr<SandboxBroker> broker =
    684        SandboxBroker::Create(std::move(policy), pid, fd);
    685    ASSERT_TRUE(broker != nullptr);
    686    ASSERT_TRUE(fd.IsValid());
    687    broker->Terminate();
    688  }
    689 
    690  if (changedLimit) {
    691    ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno);
    692  }
    693 }
    694 
    695 // Bug 1958444: In theory this test could fail intermittently: it
    696 // creates a large number of threads which will do a small amount of
    697 // work and then exit, but intentionally doesn't join them, so there
    698 // could be a large number of threads actually live at once, even if
    699 // they would be cleaned up correctly when they exit.
    700 //
    701 // To test locally, delete the `DISABLED_` prefix and rebuild.
    702 TEST(SandboxBrokerMisc, DISABLED_StackLeak)
    703 {
    704  // The idea is that kCycles * DEFAULT_STACK_SIZE is more than 4GiB
    705  // so this fails on 32-bit if the thread stack is leaked.  This
    706  // isn't ideal; it would be better to either use resource limits
    707  // (maybe RLIMIT_AS) or measure address space usage (getrusage or
    708  // procfs), to detect such bugs with a smaller amount of leakage.
    709  static constexpr size_t kCycles = 16384;
    710 
    711  if (nsIThreadManager::DEFAULT_STACK_SIZE) {
    712    EXPECT_GE(nsIThreadManager::DEFAULT_STACK_SIZE, size_t(256 * 1024));
    713  }
    714 
    715  pid_t pid = getpid();
    716  for (size_t i = 0; i < kCycles; ++i) {
    717    auto policy = MakeUnique<SandboxBroker::Policy>();
    718    // Currently nothing in `Create` tries to check for or
    719    // special-case an empty policy, but just in case:
    720    policy->AddPath(SandboxBroker::MAY_READ, "/dev/null",
    721                    SandboxBroker::Policy::AddAlways);
    722    ipc::FileDescriptor fd;
    723    RefPtr<SandboxBroker> broker =
    724        SandboxBroker::Create(std::move(policy), pid, fd);
    725    ASSERT_TRUE(broker != nullptr)
    726    << "iter " << i;
    727    ASSERT_TRUE(fd.IsValid())
    728    << "iter " << i;
    729    broker = nullptr;
    730  }
    731 }
    732 
    733 }  // namespace mozilla