733 lines
22 KiB
C++
733 lines
22 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "broker/SandboxBroker.h"
|
|
#include "broker/SandboxBrokerUtils.h"
|
|
#include "SandboxBrokerClient.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <sched.h>
|
|
#include <semaphore.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/PodOperations.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/ipc/FileDescriptor.h"
|
|
#include "nsIThreadManager.h"
|
|
|
|
namespace mozilla {
|
|
|
|
class SandboxBrokerTest : public ::testing::Test {
|
|
static const int MAY_ACCESS = SandboxBroker::MAY_ACCESS;
|
|
static const int MAY_READ = SandboxBroker::MAY_READ;
|
|
static const int MAY_WRITE = SandboxBroker::MAY_WRITE;
|
|
static const int MAY_CREATE = SandboxBroker::MAY_CREATE;
|
|
static const auto AddAlways = SandboxBroker::Policy::AddAlways;
|
|
|
|
RefPtr<SandboxBroker> mServer;
|
|
UniquePtr<SandboxBrokerClient> mClient;
|
|
|
|
UniquePtr<const SandboxBroker::Policy> GetPolicy() const;
|
|
|
|
template <class C, void (C::*Main)()>
|
|
static void* ThreadMain(void* arg) {
|
|
(static_cast<C*>(arg)->*Main)();
|
|
return nullptr;
|
|
}
|
|
|
|
protected:
|
|
int Open(const char* aPath, int aFlags) {
|
|
return mClient->Open(aPath, aFlags);
|
|
}
|
|
int Access(const char* aPath, int aMode) {
|
|
return mClient->Access(aPath, aMode);
|
|
}
|
|
int Stat(const char* aPath, statstruct* aStat) {
|
|
return mClient->Stat(aPath, aStat);
|
|
}
|
|
int LStat(const char* aPath, statstruct* aStat) {
|
|
return mClient->LStat(aPath, aStat);
|
|
}
|
|
int Chmod(const char* aPath, int aMode) {
|
|
return mClient->Chmod(aPath, aMode);
|
|
}
|
|
int Link(const char* aPath, const char* bPath) {
|
|
return mClient->Link(aPath, bPath);
|
|
}
|
|
int Mkdir(const char* aPath, int aMode) {
|
|
return mClient->Mkdir(aPath, aMode);
|
|
}
|
|
int Symlink(const char* aPath, const char* bPath) {
|
|
return mClient->Symlink(aPath, bPath);
|
|
}
|
|
int Rename(const char* aPath, const char* bPath) {
|
|
return mClient->Rename(aPath, bPath);
|
|
}
|
|
int Rmdir(const char* aPath) { return mClient->Rmdir(aPath); }
|
|
int Unlink(const char* aPath) { return mClient->Unlink(aPath); }
|
|
ssize_t Readlink(const char* aPath, char* aBuff, size_t aSize) {
|
|
return mClient->Readlink(aPath, aBuff, aSize);
|
|
}
|
|
|
|
void SetUp() override {
|
|
ipc::FileDescriptor fd;
|
|
|
|
mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd);
|
|
ASSERT_NE(mServer, nullptr);
|
|
ASSERT_TRUE(fd.IsValid());
|
|
auto rawFD = fd.TakePlatformHandle();
|
|
mClient.reset(new SandboxBrokerClient(rawFD.release()));
|
|
}
|
|
|
|
template <class C, void (C::*Main)()>
|
|
void StartThread(pthread_t* aThread) {
|
|
ASSERT_EQ(0, pthread_create(aThread, nullptr, ThreadMain<C, Main>,
|
|
static_cast<C*>(this)));
|
|
}
|
|
|
|
template <class C, void (C::*Main)()>
|
|
void RunOnManyThreads() {
|
|
static const int kNumThreads = 5;
|
|
pthread_t threads[kNumThreads];
|
|
for (pthread_t& thread : threads) {
|
|
StartThread<C, Main>(&thread);
|
|
}
|
|
for (pthread_t thread : threads) {
|
|
void* retval;
|
|
ASSERT_EQ(pthread_join(thread, &retval), 0);
|
|
ASSERT_EQ(retval, static_cast<void*>(nullptr));
|
|
}
|
|
}
|
|
|
|
public:
|
|
void MultiThreadOpenWorker();
|
|
void MultiThreadStatWorker();
|
|
};
|
|
|
|
UniquePtr<const SandboxBroker::Policy> SandboxBrokerTest::GetPolicy() const {
|
|
UniquePtr<SandboxBroker::Policy> policy(new SandboxBroker::Policy());
|
|
|
|
policy->AddPath(MAY_READ | MAY_WRITE, "/dev/null", AddAlways);
|
|
policy->AddPath(MAY_READ, "/dev/zero", AddAlways);
|
|
policy->AddPath(MAY_READ, "/var/empty/qwertyuiop", AddAlways);
|
|
policy->AddPath(MAY_ACCESS, "/proc/self",
|
|
AddAlways); // Warning: Linux-specific.
|
|
policy->AddPath(MAY_READ | MAY_WRITE, "/tmp", AddAlways);
|
|
policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublu", AddAlways);
|
|
policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublublu",
|
|
AddAlways);
|
|
// This should be non-writable by the user running the test:
|
|
policy->AddPath(MAY_READ | MAY_WRITE, "/etc", AddAlways);
|
|
|
|
return std::move(policy);
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, OpenForRead) {
|
|
int fd;
|
|
|
|
fd = Open("/dev/null", O_RDONLY);
|
|
ASSERT_GE(fd, 0) << "Opening /dev/null failed.";
|
|
close(fd);
|
|
fd = Open("/dev/zero", O_RDONLY);
|
|
ASSERT_GE(fd, 0) << "Opening /dev/zero failed.";
|
|
close(fd);
|
|
fd = Open("/var/empty/qwertyuiop", O_RDONLY);
|
|
EXPECT_EQ(-ENOENT, fd) << "Opening allowed but nonexistent file succeeded.";
|
|
fd = Open("/proc/self", O_RDONLY);
|
|
EXPECT_EQ(-EACCES, fd) << "Opening stat-only file for read succeeded.";
|
|
fd = Open("/proc/self/stat", O_RDONLY);
|
|
EXPECT_EQ(-EACCES, fd) << "Opening disallowed file succeeded.";
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, OpenForWrite) {
|
|
int fd;
|
|
|
|
fd = Open("/dev/null", O_WRONLY);
|
|
ASSERT_GE(fd, 0) << "Opening /dev/null write-only failed.";
|
|
close(fd);
|
|
fd = Open("/dev/null", O_RDWR);
|
|
ASSERT_GE(fd, 0) << "Opening /dev/null read/write failed.";
|
|
close(fd);
|
|
fd = Open("/dev/zero", O_WRONLY);
|
|
ASSERT_EQ(-EACCES, fd)
|
|
<< "Opening read-only-by-policy file write-only succeeded.";
|
|
fd = Open("/dev/zero", O_RDWR);
|
|
ASSERT_EQ(-EACCES, fd)
|
|
<< "Opening read-only-by-policy file read/write succeeded.";
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, SimpleRead) {
|
|
int fd;
|
|
char c;
|
|
|
|
fd = Open("/dev/null", O_RDONLY);
|
|
ASSERT_GE(fd, 0);
|
|
EXPECT_EQ(0, read(fd, &c, 1));
|
|
close(fd);
|
|
fd = Open("/dev/zero", O_RDONLY);
|
|
ASSERT_GE(fd, 0);
|
|
ASSERT_EQ(1, read(fd, &c, 1));
|
|
EXPECT_EQ(c, '\0');
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, BadFlags) {
|
|
int fd;
|
|
|
|
fd = Open("/dev/null", O_RDWR | O_ASYNC);
|
|
EXPECT_EQ(-EACCES, fd) << "O_ASYNC is banned.";
|
|
|
|
fd = Open("/dev/null", O_RDWR | 0x40000000);
|
|
EXPECT_EQ(-EACCES, fd) << "Unknown flag 0x40000000 is banned.";
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Access) {
|
|
EXPECT_EQ(0, Access("/dev/null", F_OK));
|
|
EXPECT_EQ(0, Access("/dev/null", R_OK));
|
|
EXPECT_EQ(0, Access("/dev/null", W_OK));
|
|
EXPECT_EQ(0, Access("/dev/null", R_OK | W_OK));
|
|
EXPECT_EQ(-EACCES, Access("/dev/null", X_OK));
|
|
EXPECT_EQ(-EACCES, Access("/dev/null", R_OK | X_OK));
|
|
|
|
EXPECT_EQ(0, Access("/dev/zero", R_OK));
|
|
EXPECT_EQ(-EACCES, Access("/dev/zero", W_OK));
|
|
EXPECT_EQ(-EACCES, Access("/dev/zero", R_OK | W_OK));
|
|
|
|
EXPECT_EQ(-ENOENT, Access("/var/empty/qwertyuiop", R_OK));
|
|
EXPECT_EQ(-EACCES, Access("/var/empty/qwertyuiop", W_OK));
|
|
|
|
EXPECT_EQ(0, Access("/proc/self", F_OK));
|
|
EXPECT_EQ(-EACCES, Access("/proc/self", R_OK));
|
|
|
|
EXPECT_EQ(-EACCES, Access("/proc/self/stat", F_OK));
|
|
|
|
EXPECT_EQ(0, Access("/tmp", X_OK));
|
|
EXPECT_EQ(0, Access("/tmp", R_OK | X_OK));
|
|
EXPECT_EQ(0, Access("/tmp", R_OK | W_OK | X_OK));
|
|
EXPECT_EQ(0, Access("/proc/self", X_OK));
|
|
|
|
EXPECT_EQ(0, Access("/etc", R_OK | X_OK));
|
|
EXPECT_EQ(-EACCES, Access("/etc", W_OK));
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Stat) {
|
|
statstruct realStat, brokeredStat;
|
|
ASSERT_EQ(0, statsyscall("/dev/null", &realStat)) << "Shouldn't ever fail!";
|
|
EXPECT_EQ(0, Stat("/dev/null", &brokeredStat));
|
|
EXPECT_EQ(realStat.st_ino, brokeredStat.st_ino);
|
|
EXPECT_EQ(realStat.st_rdev, brokeredStat.st_rdev);
|
|
|
|
#if defined(__clang__) || defined(__GNUC__)
|
|
# pragma GCC diagnostic push
|
|
# pragma GCC diagnostic ignored "-Wnonnull"
|
|
#endif
|
|
EXPECT_EQ(-1, statsyscall(nullptr, &realStat));
|
|
EXPECT_EQ(errno, EFAULT);
|
|
|
|
EXPECT_EQ(-EFAULT, Stat(nullptr, &brokeredStat));
|
|
#if defined(__clang__) || defined(__GNUC__)
|
|
# pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
EXPECT_EQ(-ENOENT, Stat("/var/empty/qwertyuiop", &brokeredStat));
|
|
EXPECT_EQ(-EACCES, Stat("/dev", &brokeredStat));
|
|
|
|
EXPECT_EQ(0, Stat("/proc/self", &brokeredStat));
|
|
EXPECT_TRUE(S_ISDIR(brokeredStat.st_mode));
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, LStat) {
|
|
statstruct realStat, brokeredStat;
|
|
ASSERT_EQ(0, lstatsyscall("/dev/null", &realStat));
|
|
EXPECT_EQ(0, LStat("/dev/null", &brokeredStat));
|
|
EXPECT_EQ(realStat.st_ino, brokeredStat.st_ino);
|
|
EXPECT_EQ(realStat.st_rdev, brokeredStat.st_rdev);
|
|
|
|
EXPECT_EQ(-ENOENT, LStat("/var/empty/qwertyuiop", &brokeredStat));
|
|
EXPECT_EQ(-EACCES, LStat("/dev", &brokeredStat));
|
|
|
|
EXPECT_EQ(0, LStat("/proc/self", &brokeredStat));
|
|
EXPECT_TRUE(S_ISLNK(brokeredStat.st_mode));
|
|
}
|
|
|
|
static void PrePostTestCleanup(void) {
|
|
unlink("/tmp/blublu");
|
|
rmdir("/tmp/blublu");
|
|
unlink("/tmp/nope");
|
|
rmdir("/tmp/nope");
|
|
unlink("/tmp/blublublu");
|
|
rmdir("/tmp/blublublu");
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Chmod) {
|
|
PrePostTestCleanup();
|
|
|
|
int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
|
|
ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
|
|
close(fd);
|
|
// Set read only. SandboxBroker enforces 0600 mode flags.
|
|
ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR));
|
|
EXPECT_EQ(-EACCES, Access("/tmp/blublu", W_OK));
|
|
statstruct realStat;
|
|
EXPECT_EQ(0, statsyscall("/tmp/blublu", &realStat));
|
|
EXPECT_EQ((mode_t)S_IRUSR, realStat.st_mode & 0777);
|
|
|
|
ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR | S_IWUSR));
|
|
EXPECT_EQ(0, statsyscall("/tmp/blublu", &realStat));
|
|
EXPECT_EQ((mode_t)(S_IRUSR | S_IWUSR), realStat.st_mode & 0777);
|
|
EXPECT_EQ(0, unlink("/tmp/blublu"));
|
|
|
|
PrePostTestCleanup();
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Link) {
|
|
PrePostTestCleanup();
|
|
|
|
int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
|
|
ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
|
|
close(fd);
|
|
ASSERT_EQ(0, Link("/tmp/blublu", "/tmp/blublublu"));
|
|
EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
|
|
// Not whitelisted target path
|
|
EXPECT_EQ(-EACCES, Link("/tmp/blublu", "/tmp/nope"));
|
|
EXPECT_EQ(0, unlink("/tmp/blublublu"));
|
|
EXPECT_EQ(0, unlink("/tmp/blublu"));
|
|
|
|
PrePostTestCleanup();
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Symlink) {
|
|
PrePostTestCleanup();
|
|
|
|
int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
|
|
ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
|
|
close(fd);
|
|
ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
|
|
EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
|
|
statstruct aStat;
|
|
ASSERT_EQ(0, lstatsyscall("/tmp/blublublu", &aStat));
|
|
EXPECT_EQ((mode_t)S_IFLNK, aStat.st_mode & S_IFMT);
|
|
// Not whitelisted target path
|
|
EXPECT_EQ(-EACCES, Symlink("/tmp/blublu", "/tmp/nope"));
|
|
EXPECT_EQ(0, unlink("/tmp/blublublu"));
|
|
EXPECT_EQ(0, unlink("/tmp/blublu"));
|
|
|
|
PrePostTestCleanup();
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Mkdir) {
|
|
PrePostTestCleanup();
|
|
|
|
ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
|
|
<< "Creating dir /tmp/blublu failed.";
|
|
EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
|
|
// Not whitelisted target path
|
|
EXPECT_EQ(-EACCES, Mkdir("/tmp/nope", 0600))
|
|
<< "Creating dir without MAY_CREATE succeed.";
|
|
EXPECT_EQ(0, rmdir("/tmp/blublu"));
|
|
EXPECT_EQ(-EEXIST, Mkdir("/proc/self", 0600))
|
|
<< "Creating uncreatable dir that already exists didn't fail correctly.";
|
|
EXPECT_EQ(-EEXIST, Mkdir("/dev/zero", 0600))
|
|
<< "Creating uncreatable dir over preexisting file didn't fail "
|
|
"correctly.";
|
|
|
|
PrePostTestCleanup();
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Rename) {
|
|
PrePostTestCleanup();
|
|
|
|
ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
|
|
<< "Creating dir /tmp/blublu failed.";
|
|
EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
|
|
ASSERT_EQ(0, Rename("/tmp/blublu", "/tmp/blublublu"));
|
|
EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
|
|
EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
|
|
// Not whitelisted target path
|
|
EXPECT_EQ(-EACCES, Rename("/tmp/blublublu", "/tmp/nope"))
|
|
<< "Renaming dir without write access succeed.";
|
|
EXPECT_EQ(0, rmdir("/tmp/blublublu"));
|
|
|
|
PrePostTestCleanup();
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Rmdir) {
|
|
PrePostTestCleanup();
|
|
|
|
ASSERT_EQ(0, mkdir("/tmp/blublu", 0600))
|
|
<< "Creating dir /tmp/blublu failed.";
|
|
EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
|
|
ASSERT_EQ(0, Rmdir("/tmp/blublu"));
|
|
EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
|
|
// Bypass sandbox to create a non-deletable dir
|
|
ASSERT_EQ(0, mkdir("/tmp/nope", 0600));
|
|
EXPECT_EQ(-EACCES, Rmdir("/tmp/nope"));
|
|
|
|
PrePostTestCleanup();
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Unlink) {
|
|
PrePostTestCleanup();
|
|
|
|
int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
|
|
ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
|
|
close(fd);
|
|
EXPECT_EQ(0, Access("/tmp/blublu", F_OK));
|
|
EXPECT_EQ(0, Unlink("/tmp/blublu"));
|
|
EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK));
|
|
// Bypass sandbox to write a non-deletable file
|
|
fd = open("/tmp/nope", O_WRONLY | O_CREAT, 0600);
|
|
ASSERT_GE(fd, 0) << "Opening /tmp/nope for writing failed.";
|
|
close(fd);
|
|
EXPECT_EQ(-EACCES, Unlink("/tmp/nope"));
|
|
|
|
PrePostTestCleanup();
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, Readlink) {
|
|
PrePostTestCleanup();
|
|
|
|
int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT);
|
|
ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed.";
|
|
close(fd);
|
|
ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu"));
|
|
EXPECT_EQ(0, Access("/tmp/blublublu", F_OK));
|
|
char linkBuff[256];
|
|
EXPECT_EQ(11, Readlink("/tmp/blublublu", linkBuff, sizeof(linkBuff)));
|
|
linkBuff[11] = '\0';
|
|
EXPECT_EQ(0, strcmp(linkBuff, "/tmp/blublu"));
|
|
|
|
PrePostTestCleanup();
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, MultiThreadOpen) {
|
|
RunOnManyThreads<SandboxBrokerTest,
|
|
&SandboxBrokerTest::MultiThreadOpenWorker>();
|
|
}
|
|
void SandboxBrokerTest::MultiThreadOpenWorker() {
|
|
static const int kNumLoops = 10000;
|
|
|
|
for (int i = 1; i <= kNumLoops; ++i) {
|
|
int nullfd = Open("/dev/null", O_RDONLY);
|
|
int zerofd = Open("/dev/zero", O_RDONLY);
|
|
ASSERT_GE(nullfd, 0) << "Loop " << i << "/" << kNumLoops;
|
|
ASSERT_GE(zerofd, 0) << "Loop " << i << "/" << kNumLoops;
|
|
char c;
|
|
ASSERT_EQ(0, read(nullfd, &c, 1)) << "Loop " << i << "/" << kNumLoops;
|
|
ASSERT_EQ(1, read(zerofd, &c, 1)) << "Loop " << i << "/" << kNumLoops;
|
|
ASSERT_EQ('\0', c) << "Loop " << i << "/" << kNumLoops;
|
|
close(nullfd);
|
|
close(zerofd);
|
|
}
|
|
}
|
|
|
|
TEST_F(SandboxBrokerTest, MultiThreadStat) {
|
|
RunOnManyThreads<SandboxBrokerTest,
|
|
&SandboxBrokerTest::MultiThreadStatWorker>();
|
|
}
|
|
void SandboxBrokerTest::MultiThreadStatWorker() {
|
|
static const int kNumLoops = 7500;
|
|
statstruct nullStat, zeroStat, selfStat;
|
|
dev_t realNullDev, realZeroDev;
|
|
ino_t realSelfInode;
|
|
|
|
ASSERT_EQ(0, statsyscall("/dev/null", &nullStat)) << "Shouldn't ever fail!";
|
|
ASSERT_EQ(0, statsyscall("/dev/zero", &zeroStat)) << "Shouldn't ever fail!";
|
|
ASSERT_EQ(0, lstatsyscall("/proc/self", &selfStat)) << "Shouldn't ever fail!";
|
|
ASSERT_TRUE(S_ISLNK(selfStat.st_mode))
|
|
<< "Shouldn't ever fail!";
|
|
realNullDev = nullStat.st_rdev;
|
|
realZeroDev = zeroStat.st_rdev;
|
|
realSelfInode = selfStat.st_ino;
|
|
for (int i = 1; i <= kNumLoops; ++i) {
|
|
ASSERT_EQ(0, Stat("/dev/null", &nullStat))
|
|
<< "Loop " << i << "/" << kNumLoops;
|
|
ASSERT_EQ(0, Stat("/dev/zero", &zeroStat))
|
|
<< "Loop " << i << "/" << kNumLoops;
|
|
ASSERT_EQ(0, LStat("/proc/self", &selfStat))
|
|
<< "Loop " << i << "/" << kNumLoops;
|
|
|
|
ASSERT_EQ(realNullDev, nullStat.st_rdev)
|
|
<< "Loop " << i << "/" << kNumLoops;
|
|
ASSERT_EQ(realZeroDev, zeroStat.st_rdev)
|
|
<< "Loop " << i << "/" << kNumLoops;
|
|
ASSERT_TRUE(S_ISLNK(selfStat.st_mode))
|
|
<< "Loop " << i << "/" << kNumLoops;
|
|
ASSERT_EQ(realSelfInode, selfStat.st_ino)
|
|
<< "Loop " << i << "/" << kNumLoops;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
class SandboxBrokerSigStress : public SandboxBrokerTest
|
|
{
|
|
int mSigNum;
|
|
struct sigaction mOldAction;
|
|
Atomic<void*> mVoidPtr;
|
|
|
|
static void SigHandler(int aSigNum, siginfo_t* aSigInfo, void *aCtx) {
|
|
ASSERT_EQ(SI_QUEUE, aSigInfo->si_code);
|
|
SandboxBrokerSigStress* that =
|
|
static_cast<SandboxBrokerSigStress*>(aSigInfo->si_value.sival_ptr);
|
|
ASSERT_EQ(that->mSigNum, aSigNum);
|
|
that->DoSomething();
|
|
}
|
|
|
|
protected:
|
|
Atomic<int> mTestIter;
|
|
sem_t mSemaphore;
|
|
|
|
void SignalThread(pthread_t aThread) {
|
|
union sigval sv;
|
|
sv.sival_ptr = this;
|
|
ASSERT_NE(0, mSigNum);
|
|
ASSERT_EQ(0, pthread_sigqueue(aThread, mSigNum, sv));
|
|
}
|
|
|
|
virtual void SetUp() {
|
|
ASSERT_EQ(0, sem_init(&mSemaphore, 0, 0));
|
|
mVoidPtr = nullptr;
|
|
mSigNum = 0;
|
|
for (int sigNum = SIGRTMIN; sigNum < SIGRTMAX; ++sigNum) {
|
|
ASSERT_EQ(0, sigaction(sigNum, nullptr, &mOldAction));
|
|
if ((mOldAction.sa_flags & SA_SIGINFO) == 0 &&
|
|
mOldAction.sa_handler == SIG_DFL) {
|
|
struct sigaction newAction;
|
|
PodZero(&newAction);
|
|
newAction.sa_flags = SA_SIGINFO;
|
|
newAction.sa_sigaction = SigHandler;
|
|
ASSERT_EQ(0, sigaction(sigNum, &newAction, nullptr));
|
|
mSigNum = sigNum;
|
|
break;
|
|
}
|
|
}
|
|
ASSERT_NE(mSigNum, 0);
|
|
|
|
SandboxBrokerTest::SetUp();
|
|
}
|
|
|
|
virtual void TearDown() {
|
|
ASSERT_EQ(0, sem_destroy(&mSemaphore));
|
|
if (mSigNum != 0) {
|
|
ASSERT_EQ(0, sigaction(mSigNum, &mOldAction, nullptr));
|
|
}
|
|
if (mVoidPtr) {
|
|
free(mVoidPtr);
|
|
}
|
|
}
|
|
|
|
void DoSomething();
|
|
|
|
public:
|
|
void MallocWorker();
|
|
void FreeWorker();
|
|
};
|
|
|
|
TEST_F(SandboxBrokerSigStress, StressTest)
|
|
{
|
|
static const int kIters = 6250;
|
|
static const int kNsecPerIterPerIter = 4;
|
|
struct timespec delay = { 0, 0 };
|
|
pthread_t threads[2];
|
|
|
|
mTestIter = kIters;
|
|
|
|
StartThread<SandboxBrokerSigStress,
|
|
&SandboxBrokerSigStress::MallocWorker>(&threads[0]);
|
|
StartThread<SandboxBrokerSigStress,
|
|
&SandboxBrokerSigStress::FreeWorker>(&threads[1]);
|
|
|
|
for (int i = kIters; i > 0; --i) {
|
|
SignalThread(threads[i % 2]);
|
|
while (sem_wait(&mSemaphore) == -1 && errno == EINTR)
|
|
/* retry */;
|
|
ASSERT_EQ(i - 1, mTestIter);
|
|
delay.tv_nsec += kNsecPerIterPerIter;
|
|
struct timespec req = delay, rem;
|
|
while (nanosleep(&req, &rem) == -1 && errno == EINTR) {
|
|
req = rem;
|
|
}
|
|
}
|
|
void *retval;
|
|
ASSERT_EQ(0, pthread_join(threads[0], &retval));
|
|
ASSERT_EQ(nullptr, retval);
|
|
ASSERT_EQ(0, pthread_join(threads[1], &retval));
|
|
ASSERT_EQ(nullptr, retval);
|
|
}
|
|
|
|
void
|
|
SandboxBrokerSigStress::MallocWorker()
|
|
{
|
|
static const size_t kSize = 64;
|
|
|
|
void* mem = malloc(kSize);
|
|
while (mTestIter > 0) {
|
|
ASSERT_NE(mem, mVoidPtr);
|
|
mem = mVoidPtr.exchange(mem);
|
|
if (mem) {
|
|
sched_yield();
|
|
} else {
|
|
mem = malloc(kSize);
|
|
}
|
|
}
|
|
if (mem) {
|
|
free(mem);
|
|
}
|
|
}
|
|
|
|
void
|
|
SandboxBrokerSigStress::FreeWorker()
|
|
{
|
|
void *mem = nullptr;
|
|
while (mTestIter > 0) {
|
|
mem = mVoidPtr.exchange(mem);
|
|
if (mem) {
|
|
free(mem);
|
|
mem = nullptr;
|
|
} else {
|
|
sched_yield();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
SandboxBrokerSigStress::DoSomething()
|
|
{
|
|
int fd;
|
|
char c;
|
|
struct stat st;
|
|
|
|
//fprintf(stderr, "Don't try this at home: %d\n", static_cast<int>(mTestIter));
|
|
switch (mTestIter % 5) {
|
|
case 0:
|
|
fd = Open("/dev/null", O_RDWR);
|
|
ASSERT_GE(fd, 0);
|
|
ASSERT_EQ(0, read(fd, &c, 1));
|
|
close(fd);
|
|
break;
|
|
case 1:
|
|
fd = Open("/dev/zero", O_RDONLY);
|
|
ASSERT_GE(fd, 0);
|
|
ASSERT_EQ(1, read(fd, &c, 1));
|
|
ASSERT_EQ('\0', c);
|
|
close(fd);
|
|
break;
|
|
case 2:
|
|
ASSERT_EQ(0, Access("/dev/null", W_OK));
|
|
break;
|
|
case 3:
|
|
ASSERT_EQ(0, Stat("/proc/self", &st));
|
|
ASSERT_TRUE(S_ISDIR(st.st_mode));
|
|
break;
|
|
case 4:
|
|
ASSERT_EQ(0, LStat("/proc/self", &st));
|
|
ASSERT_TRUE(S_ISLNK(st.st_mode));
|
|
break;
|
|
}
|
|
mTestIter--;
|
|
sem_post(&mSemaphore);
|
|
}
|
|
#endif
|
|
|
|
// Check for fd leaks when creating/destroying a broker instance (bug
|
|
// 1719391).
|
|
//
|
|
// (This uses a different test group because it doesn't use the
|
|
// fixture class, and gtest doesn't allow mixing TEST and TEST_F in
|
|
// the same group.)
|
|
TEST(SandboxBrokerMisc, FileDescLeak)
|
|
{
|
|
// If this value is increased in the future, check that it won't
|
|
// cause the test to take an excessive amount of time.
|
|
// (Alternately, this test could be changed to run a smaller number
|
|
// of cycles and check for increased fd usage afterwards; some care
|
|
// would be needed to avoid false positives.)
|
|
static constexpr size_t kCycles = 4096;
|
|
struct rlimit oldLimit;
|
|
bool changedLimit = false;
|
|
|
|
// At the time of this writing, we raise the fd soft limit to 4096
|
|
// (or to the hard limit, if less than that), but we don't lower it
|
|
// if it was higher than 4096. To allow for that case, or if
|
|
// Gecko's preferred limit changes, the limit is reduced while this
|
|
// test is running:
|
|
ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno);
|
|
if (oldLimit.rlim_cur == RLIM_INFINITY ||
|
|
oldLimit.rlim_cur > static_cast<rlim_t>(kCycles)) {
|
|
struct rlimit newLimit = oldLimit;
|
|
newLimit.rlim_cur = static_cast<rlim_t>(kCycles);
|
|
ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &newLimit), 0) << strerror(errno);
|
|
changedLimit = true;
|
|
}
|
|
|
|
pid_t pid = getpid();
|
|
for (size_t i = 0; i < kCycles; ++i) {
|
|
auto policy = MakeUnique<SandboxBroker::Policy>();
|
|
// Currently nothing in `Create` tries to check for or
|
|
// special-case an empty policy, but just in case:
|
|
policy->AddPath(SandboxBroker::MAY_READ, "/dev/null",
|
|
SandboxBroker::Policy::AddAlways);
|
|
ipc::FileDescriptor fd;
|
|
RefPtr<SandboxBroker> broker =
|
|
SandboxBroker::Create(std::move(policy), pid, fd);
|
|
ASSERT_TRUE(broker != nullptr);
|
|
ASSERT_TRUE(fd.IsValid());
|
|
broker->Terminate();
|
|
}
|
|
|
|
if (changedLimit) {
|
|
ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno);
|
|
}
|
|
}
|
|
|
|
// Bug 1958444: In theory this test could fail intermittently: it
|
|
// creates a large number of threads which will do a small amount of
|
|
// work and then exit, but intentionally doesn't join them, so there
|
|
// could be a large number of threads actually live at once, even if
|
|
// they would be cleaned up correctly when they exit.
|
|
//
|
|
// To test locally, delete the `DISABLED_` prefix and rebuild.
|
|
TEST(SandboxBrokerMisc, DISABLED_StackLeak)
|
|
{
|
|
// The idea is that kCycles * DEFAULT_STACK_SIZE is more than 4GiB
|
|
// so this fails on 32-bit if the thread stack is leaked. This
|
|
// isn't ideal; it would be better to either use resource limits
|
|
// (maybe RLIMIT_AS) or measure address space usage (getrusage or
|
|
// procfs), to detect such bugs with a smaller amount of leakage.
|
|
static constexpr size_t kCycles = 16384;
|
|
|
|
if (nsIThreadManager::DEFAULT_STACK_SIZE) {
|
|
EXPECT_GE(nsIThreadManager::DEFAULT_STACK_SIZE, size_t(256 * 1024));
|
|
}
|
|
|
|
pid_t pid = getpid();
|
|
for (size_t i = 0; i < kCycles; ++i) {
|
|
auto policy = MakeUnique<SandboxBroker::Policy>();
|
|
// Currently nothing in `Create` tries to check for or
|
|
// special-case an empty policy, but just in case:
|
|
policy->AddPath(SandboxBroker::MAY_READ, "/dev/null",
|
|
SandboxBroker::Policy::AddAlways);
|
|
ipc::FileDescriptor fd;
|
|
RefPtr<SandboxBroker> broker =
|
|
SandboxBroker::Create(std::move(policy), pid, fd);
|
|
ASSERT_TRUE(broker != nullptr)
|
|
<< "iter " << i;
|
|
ASSERT_TRUE(fd.IsValid())
|
|
<< "iter " << i;
|
|
broker = nullptr;
|
|
}
|
|
}
|
|
|
|
} // namespace mozilla
|