diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /security/sandbox/linux/gtest | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/sandbox/linux/gtest')
-rw-r--r-- | security/sandbox/linux/gtest/TestBroker.cpp | 689 | ||||
-rw-r--r-- | security/sandbox/linux/gtest/TestBrokerPolicy.cpp | 95 | ||||
-rw-r--r-- | security/sandbox/linux/gtest/TestLogging.cpp | 56 | ||||
-rw-r--r-- | security/sandbox/linux/gtest/moz.build | 26 |
4 files changed, 866 insertions, 0 deletions
diff --git a/security/sandbox/linux/gtest/TestBroker.cpp b/security/sandbox/linux/gtest/TestBroker.cpp new file mode 100644 index 0000000000..07fcaa889a --- /dev/null +++ b/security/sandbox/linux/gtest/TestBroker.cpp @@ -0,0 +1,689 @@ +/* -*- 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" + +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; + + UniquePtr<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, LeakCheck) +{ + // If this value is increased in the future, check that it won't + // cause the test to take an excessive amount of time: + 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; + auto broker = SandboxBroker::Create(std::move(policy), pid, fd); + ASSERT_TRUE(broker); + ASSERT_TRUE(fd.IsValid()); + } + + if (changedLimit) { + ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno); + } +} + +} // namespace mozilla diff --git a/security/sandbox/linux/gtest/TestBrokerPolicy.cpp b/security/sandbox/linux/gtest/TestBrokerPolicy.cpp new file mode 100644 index 0000000000..d881e6d290 --- /dev/null +++ b/security/sandbox/linux/gtest/TestBrokerPolicy.cpp @@ -0,0 +1,95 @@ +/* -*- 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" + +namespace mozilla { + +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 int RECURSIVE = SandboxBroker::RECURSIVE; +static const auto AddAlways = SandboxBroker::Policy::AddAlways; + +TEST(SandboxBrokerPolicyLookup, Simple) +{ + SandboxBroker::Policy p; + p.AddPath(MAY_READ, "/dev/urandom", AddAlways); + + EXPECT_NE(0, p.Lookup("/dev/urandom")) << "Added path not found."; + EXPECT_EQ(MAY_ACCESS | MAY_READ, p.Lookup("/dev/urandom")) + << "Added path found with wrong perms."; + EXPECT_EQ(0, p.Lookup("/etc/passwd")) << "Non-added path was found."; +} + +TEST(SandboxBrokerPolicyLookup, CopyCtor) +{ + SandboxBroker::Policy psrc; + psrc.AddPath(MAY_READ | MAY_WRITE, "/dev/null", AddAlways); + SandboxBroker::Policy pdst(psrc); + psrc.AddPath(MAY_READ, "/dev/zero", AddAlways); + pdst.AddPath(MAY_READ, "/dev/urandom", AddAlways); + + EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/null")) + << "Common path absent in copy source."; + EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, pdst.Lookup("/dev/null")) + << "Common path absent in copy destination."; + + EXPECT_EQ(MAY_ACCESS | MAY_READ, psrc.Lookup("/dev/zero")) + << "Source-only path is absent."; + EXPECT_EQ(0, pdst.Lookup("/dev/zero")) + << "Source-only path is present in copy destination."; + + EXPECT_EQ(0, psrc.Lookup("/dev/urandom")) + << "Destination-only path is present in copy source."; + EXPECT_EQ(MAY_ACCESS | MAY_READ, pdst.Lookup("/dev/urandom")) + << "Destination-only path is absent."; + + EXPECT_EQ(0, psrc.Lookup("/etc/passwd")) + << "Non-added path is present in copy source."; + EXPECT_EQ(0, pdst.Lookup("/etc/passwd")) + << "Non-added path is present in copy source."; +} + +TEST(SandboxBrokerPolicyLookup, Recursive) +{ + SandboxBroker::Policy psrc; + psrc.AddPath(MAY_READ | MAY_WRITE, "/dev/null", AddAlways); + psrc.AddPath(MAY_READ, "/dev/zero", AddAlways); + psrc.AddPath(MAY_READ, "/dev/urandom", AddAlways); + + EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/null")) + << "Basic path is present."; + EXPECT_EQ(MAY_ACCESS | MAY_READ, psrc.Lookup("/dev/zero")) + << "Basic path has no extra flags"; + + psrc.AddDir(MAY_READ | MAY_WRITE, "/dev/"); + + EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/random")) + << "Permission via recursive dir."; + EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/sd/0")) + << "Permission via recursive dir, nested deeper"; + EXPECT_EQ(0, psrc.Lookup("/dev/sd/0/")) << "Invalid path format."; + EXPECT_EQ(0, psrc.Lookup("/usr/dev/sd")) << "Match must be a prefix."; + + psrc.AddDir(MAY_READ, "/dev/sd/"); + EXPECT_EQ(MAY_ACCESS | MAY_READ | MAY_WRITE, psrc.Lookup("/dev/sd/0")) + << "Extra permissions from parent path granted."; + EXPECT_EQ(0, psrc.Lookup("/dev/..")) << "Refuse attempted subdir escape."; + + psrc.AddDir(MAY_READ, "/tmp"); + EXPECT_EQ(MAY_ACCESS | MAY_READ, psrc.Lookup("/tmp/good/a")) + << "Check whether dir add with no trailing / was sucessful."; + EXPECT_EQ(0, psrc.Lookup("/tmp_good_but_bad")) + << "Enforce terminator on directories."; + EXPECT_EQ(0, psrc.Lookup("/tmp/.")) + << "Do not allow opening a directory handle."; +} + +} // namespace mozilla diff --git a/security/sandbox/linux/gtest/TestLogging.cpp b/security/sandbox/linux/gtest/TestLogging.cpp new file mode 100644 index 0000000000..eb0c9b747e --- /dev/null +++ b/security/sandbox/linux/gtest/TestLogging.cpp @@ -0,0 +1,56 @@ +/* -*- 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 "SandboxLogging.h" + +#include <errno.h> + +namespace mozilla { + +TEST(SandboxLogging, ErrorName1) +{ + char buf[32]; + ssize_t n = GetLibcErrorName(buf, sizeof(buf), EINVAL); + EXPECT_EQ(n, 6); + EXPECT_STREQ(buf, "EINVAL"); +} + +TEST(SandboxLogging, ErrorName2) +{ + char buf[32]; + ssize_t n = GetLibcErrorName(buf, sizeof(buf), EIO); + EXPECT_EQ(n, 3); + EXPECT_STREQ(buf, "EIO"); +} + +TEST(SandboxLogging, ErrorName3) +{ + char buf[32]; + ssize_t n = GetLibcErrorName(buf, sizeof(buf), ESTALE); + EXPECT_EQ(n, 6); + EXPECT_STREQ(buf, "ESTALE"); +} + +TEST(SandboxLogging, ErrorNameFail) +{ + char buf[32]; + ssize_t n = GetLibcErrorName(buf, sizeof(buf), 4095); + EXPECT_EQ(n, 10); + EXPECT_STREQ(buf, "error 4095"); +} + +TEST(SandboxLogging, ErrorNameTrunc) +{ + char buf[] = "++++++++"; + ssize_t n = GetLibcErrorName(buf, 3, EINVAL); + EXPECT_EQ(n, 6); + EXPECT_STREQ(buf, "EI"); + EXPECT_STREQ(buf + 3, "+++++"); +} + +} // namespace mozilla diff --git a/security/sandbox/linux/gtest/moz.build b/security/sandbox/linux/gtest/moz.build new file mode 100644 index 0000000000..f80482aee3 --- /dev/null +++ b/security/sandbox/linux/gtest/moz.build @@ -0,0 +1,26 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Library("sandboxtest") + +UNIFIED_SOURCES = [ + "../SandboxBrokerClient.cpp", + "TestBroker.cpp", + "TestBrokerPolicy.cpp", + "TestLogging.cpp", +] + +LOCAL_INCLUDES += [ + "/security/sandbox/linux", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/security/sandbox/chromium", +] + +FINAL_LIBRARY = "xul-gtest" |