| Index: sandbox/linux/tests/unit_tests.cc
|
| diff --git a/sandbox/linux/tests/unit_tests.cc b/sandbox/linux/tests/unit_tests.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b7c3af673e231aac68a57a2428ab4f18ba4a6221
|
| --- /dev/null
|
| +++ b/sandbox/linux/tests/unit_tests.cc
|
| @@ -0,0 +1,326 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include <fcntl.h>
|
| +#include <poll.h>
|
| +#include <signal.h>
|
| +#include <stdio.h>
|
| +#include <sys/resource.h>
|
| +#include <sys/time.h>
|
| +#include <time.h>
|
| +#include <unistd.h>
|
| +
|
| +#include "base/debug/leak_annotations.h"
|
| +#include "base/files/file_util.h"
|
| +#include "base/posix/eintr_wrapper.h"
|
| +#include "base/third_party/valgrind/valgrind.h"
|
| +#include "build/build_config.h"
|
| +#include "sandbox/linux/tests/unit_tests.h"
|
| +
|
| +namespace {
|
| +std::string TestFailedMessage(const std::string& msg) {
|
| + return msg.empty() ? std::string() : "Actual test failure: " + msg;
|
| +}
|
| +
|
| +int GetSubProcessTimeoutTimeInSeconds() {
|
| + // 10s ought to be enough for anybody.
|
| + return 10;
|
| +}
|
| +
|
| +// Returns the number of threads of the current process or -1.
|
| +int CountThreads() {
|
| + struct stat task_stat;
|
| + int task_d = stat("/proc/self/task", &task_stat);
|
| + // task_stat.st_nlink should be the number of tasks + 2 (accounting for
|
| + // "." and "..".
|
| + if (task_d != 0 || task_stat.st_nlink < 3)
|
| + return -1;
|
| + const int num_threads = task_stat.st_nlink - 2;
|
| + return num_threads;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace sandbox {
|
| +
|
| +bool IsAndroid() {
|
| +#if defined(OS_ANDROID)
|
| + return true;
|
| +#else
|
| + return false;
|
| +#endif
|
| +}
|
| +
|
| +bool IsArchitectureArm() {
|
| +#if defined(ARCH_CPU_ARM_FAMILY)
|
| + return true;
|
| +#else
|
| + return false;
|
| +#endif
|
| +}
|
| +
|
| +// TODO(jln): figure out why base/.../dynamic_annotations.h's
|
| +// RunningOnValgrind() cannot link.
|
| +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; }
|
| +
|
| +static const int kExpectedValue = 42;
|
| +static const int kIgnoreThisTest = 43;
|
| +static const int kExitWithAssertionFailure = 1;
|
| +static const int kExitForTimeout = 2;
|
| +
|
| +#if !defined(OS_ANDROID)
|
| +// This is due to StackDumpSignalHandler() performing _exit(1).
|
| +// TODO(jln): get rid of the collision with kExitWithAssertionFailure.
|
| +const int kExitAfterSIGSEGV = 1;
|
| +#endif
|
| +
|
| +static void SigAlrmHandler(int) {
|
| + const char failure_message[] = "Timeout reached!\n";
|
| + // Make sure that we never block here.
|
| + if (!fcntl(2, F_SETFL, O_NONBLOCK)) {
|
| + ignore_result(write(2, failure_message, sizeof(failure_message) - 1));
|
| + }
|
| + _exit(kExitForTimeout);
|
| +}
|
| +
|
| +// Set a timeout with a handler that will automatically fail the
|
| +// test.
|
| +static void SetProcessTimeout(int time_in_seconds) {
|
| + struct sigaction act = {};
|
| + act.sa_handler = SigAlrmHandler;
|
| + SANDBOX_ASSERT(sigemptyset(&act.sa_mask) == 0);
|
| + act.sa_flags = 0;
|
| +
|
| + struct sigaction old_act;
|
| + SANDBOX_ASSERT(sigaction(SIGALRM, &act, &old_act) == 0);
|
| +
|
| + // We don't implemenet signal chaining, so make sure that nothing else
|
| + // is expecting to handle SIGALRM.
|
| + SANDBOX_ASSERT((old_act.sa_flags & SA_SIGINFO) == 0);
|
| + SANDBOX_ASSERT(old_act.sa_handler == SIG_DFL);
|
| + sigset_t sigalrm_set;
|
| + SANDBOX_ASSERT(sigemptyset(&sigalrm_set) == 0);
|
| + SANDBOX_ASSERT(sigaddset(&sigalrm_set, SIGALRM) == 0);
|
| + SANDBOX_ASSERT(sigprocmask(SIG_UNBLOCK, &sigalrm_set, NULL) == 0);
|
| + SANDBOX_ASSERT(alarm(time_in_seconds) == 0); // There should be no previous
|
| + // alarm.
|
| +}
|
| +
|
| +// Runs a test in a sub-process. This is necessary for most of the code
|
| +// in the BPF sandbox, as it potentially makes global state changes and as
|
| +// it also tends to raise fatal errors, if the code has been used in an
|
| +// insecure manner.
|
| +void UnitTests::RunTestInProcess(SandboxTestRunner* test_runner,
|
| + DeathCheck death,
|
| + const void* death_aux) {
|
| + CHECK(test_runner);
|
| + // We need to fork(), so we can't be multi-threaded, as threads could hold
|
| + // locks.
|
| + int num_threads = CountThreads();
|
| +#if !defined(THREAD_SANITIZER)
|
| + const int kNumExpectedThreads = 1;
|
| +#else
|
| + // Under TSAN, there is a special helper thread. It should be completely
|
| + // invisible to our testing, so we ignore it. It should be ok to fork()
|
| + // with this thread. It's currently buggy, but it's the best we can do until
|
| + // there is a way to delay the start of the thread
|
| + // (https://code.google.com/p/thread-sanitizer/issues/detail?id=19).
|
| + const int kNumExpectedThreads = 2;
|
| +#endif
|
| +
|
| + // The kernel is at liberty to wake a thread id futex before updating /proc.
|
| + // If another test running in the same process has stopped a thread, it may
|
| + // appear as still running in /proc.
|
| + // We poll /proc, with an exponential back-off. At most, we'll sleep around
|
| + // 2^iterations nanoseconds in nanosleep().
|
| + for (unsigned int iteration = 0; iteration < 30; iteration++) {
|
| + struct timespec ts = {0, 1L << iteration /* nanoseconds */};
|
| + PCHECK(0 == HANDLE_EINTR(nanosleep(&ts, &ts)));
|
| + num_threads = CountThreads();
|
| + if (kNumExpectedThreads == num_threads)
|
| + break;
|
| + }
|
| +
|
| + ASSERT_EQ(kNumExpectedThreads, num_threads)
|
| + << "Running sandbox tests with multiple threads "
|
| + << "is not supported and will make the tests flaky.";
|
| + int fds[2];
|
| + ASSERT_EQ(0, pipe(fds));
|
| + // Check that our pipe is not on one of the standard file descriptor.
|
| + SANDBOX_ASSERT(fds[0] > 2 && fds[1] > 2);
|
| +
|
| + pid_t pid;
|
| + ASSERT_LE(0, (pid = fork()));
|
| + if (!pid) {
|
| + // In child process
|
| + // Redirect stderr to our pipe. This way, we can capture all error
|
| + // messages, if we decide we want to do so in our tests.
|
| + SANDBOX_ASSERT(dup2(fds[1], 2) == 2);
|
| + SANDBOX_ASSERT(!close(fds[0]));
|
| + SANDBOX_ASSERT(!close(fds[1]));
|
| +
|
| + // Don't set a timeout if running on Valgrind, since it's generally much
|
| + // slower.
|
| + if (!IsRunningOnValgrind()) {
|
| + SetProcessTimeout(GetSubProcessTimeoutTimeInSeconds());
|
| + }
|
| +
|
| + // Disable core files. They are not very useful for our individual test
|
| + // cases.
|
| + struct rlimit no_core = {0};
|
| + setrlimit(RLIMIT_CORE, &no_core);
|
| +
|
| + test_runner->Run();
|
| + if (test_runner->ShouldCheckForLeaks()) {
|
| +#if defined(LEAK_SANITIZER)
|
| + __lsan_do_leak_check();
|
| +#endif
|
| + }
|
| + _exit(kExpectedValue);
|
| + }
|
| +
|
| + close(fds[1]);
|
| + std::vector<char> msg_buf;
|
| + ssize_t rc;
|
| +
|
| + // Make sure read() will never block as we'll use poll() to
|
| + // block with a timeout instead.
|
| + const int fcntl_ret = fcntl(fds[0], F_SETFL, O_NONBLOCK);
|
| + ASSERT_EQ(0, fcntl_ret);
|
| + struct pollfd poll_fd = {fds[0], POLLIN | POLLRDHUP, 0};
|
| +
|
| + int poll_ret;
|
| + // We prefer the SIGALRM timeout to trigger in the child than this timeout
|
| + // so we double the common value here.
|
| + int poll_timeout = GetSubProcessTimeoutTimeInSeconds() * 2 * 1000;
|
| + while ((poll_ret = poll(&poll_fd, 1, poll_timeout) > 0)) {
|
| + const size_t kCapacity = 256;
|
| + const size_t len = msg_buf.size();
|
| + msg_buf.resize(len + kCapacity);
|
| + rc = HANDLE_EINTR(read(fds[0], &msg_buf[len], kCapacity));
|
| + msg_buf.resize(len + std::max(rc, static_cast<ssize_t>(0)));
|
| + if (rc <= 0)
|
| + break;
|
| + }
|
| + ASSERT_NE(poll_ret, -1) << "poll() failed";
|
| + ASSERT_NE(poll_ret, 0) << "Timeout while reading child state";
|
| + close(fds[0]);
|
| + std::string msg(msg_buf.begin(), msg_buf.end());
|
| +
|
| + int status = 0;
|
| + int waitpid_returned = HANDLE_EINTR(waitpid(pid, &status, 0));
|
| + ASSERT_EQ(pid, waitpid_returned) << TestFailedMessage(msg);
|
| +
|
| + // At run-time, we sometimes decide that a test shouldn't actually
|
| + // run (e.g. when testing sandbox features on a kernel that doesn't
|
| + // have sandboxing support). When that happens, don't attempt to
|
| + // call the "death" function, as it might be looking for a
|
| + // death-test condition that would never have triggered.
|
| + if (!WIFEXITED(status) || WEXITSTATUS(status) != kIgnoreThisTest ||
|
| + !msg.empty()) {
|
| + // We use gtest's ASSERT_XXX() macros instead of the DeathCheck
|
| + // functions. This means, on failure, "return" is called. This
|
| + // only works correctly, if the call of the "death" callback is
|
| + // the very last thing in our function.
|
| + death(status, msg, death_aux);
|
| + }
|
| +}
|
| +
|
| +void UnitTests::DeathSuccess(int status, const std::string& msg, const void*) {
|
| + std::string details(TestFailedMessage(msg));
|
| +
|
| + bool subprocess_terminated_normally = WIFEXITED(status);
|
| + ASSERT_TRUE(subprocess_terminated_normally) << details;
|
| + int subprocess_exit_status = WEXITSTATUS(status);
|
| + ASSERT_EQ(kExpectedValue, subprocess_exit_status) << details;
|
| + bool subprocess_exited_but_printed_messages = !msg.empty();
|
| + EXPECT_FALSE(subprocess_exited_but_printed_messages) << details;
|
| +}
|
| +
|
| +void UnitTests::DeathSuccessAllowNoise(int status,
|
| + const std::string& msg,
|
| + const void*) {
|
| + std::string details(TestFailedMessage(msg));
|
| +
|
| + bool subprocess_terminated_normally = WIFEXITED(status);
|
| + ASSERT_TRUE(subprocess_terminated_normally) << details;
|
| + int subprocess_exit_status = WEXITSTATUS(status);
|
| + ASSERT_EQ(kExpectedValue, subprocess_exit_status) << details;
|
| +}
|
| +
|
| +void UnitTests::DeathMessage(int status,
|
| + const std::string& msg,
|
| + const void* aux) {
|
| + std::string details(TestFailedMessage(msg));
|
| + const char* expected_msg = static_cast<const char*>(aux);
|
| +
|
| + bool subprocess_terminated_normally = WIFEXITED(status);
|
| + ASSERT_TRUE(subprocess_terminated_normally) << "Exit status: " << status
|
| + << " " << details;
|
| + int subprocess_exit_status = WEXITSTATUS(status);
|
| + ASSERT_EQ(1, subprocess_exit_status) << details;
|
| +
|
| + bool subprocess_exited_without_matching_message =
|
| + msg.find(expected_msg) == std::string::npos;
|
| + EXPECT_FALSE(subprocess_exited_without_matching_message) << details;
|
| +}
|
| +
|
| +void UnitTests::DeathSEGVMessage(int status,
|
| + const std::string& msg,
|
| + const void* aux) {
|
| + std::string details(TestFailedMessage(msg));
|
| + const char* expected_msg = static_cast<const char*>(aux);
|
| +
|
| +#if defined(OS_ANDROID)
|
| + const bool subprocess_got_sigsegv =
|
| + WIFSIGNALED(status) && (SIGSEGV == WTERMSIG(status));
|
| +#else
|
| + const bool subprocess_got_sigsegv =
|
| + WIFEXITED(status) && (kExitAfterSIGSEGV == WEXITSTATUS(status));
|
| +#endif
|
| +
|
| + ASSERT_TRUE(subprocess_got_sigsegv) << "Exit status: " << status
|
| + << " " << details;
|
| +
|
| + bool subprocess_exited_without_matching_message =
|
| + msg.find(expected_msg) == std::string::npos;
|
| + EXPECT_FALSE(subprocess_exited_without_matching_message) << details;
|
| +}
|
| +
|
| +void UnitTests::DeathExitCode(int status,
|
| + const std::string& msg,
|
| + const void* aux) {
|
| + int expected_exit_code = static_cast<int>(reinterpret_cast<intptr_t>(aux));
|
| + std::string details(TestFailedMessage(msg));
|
| +
|
| + bool subprocess_terminated_normally = WIFEXITED(status);
|
| + ASSERT_TRUE(subprocess_terminated_normally) << details;
|
| + int subprocess_exit_status = WEXITSTATUS(status);
|
| + ASSERT_EQ(expected_exit_code, subprocess_exit_status) << details;
|
| +}
|
| +
|
| +void UnitTests::DeathBySignal(int status,
|
| + const std::string& msg,
|
| + const void* aux) {
|
| + int expected_signo = static_cast<int>(reinterpret_cast<intptr_t>(aux));
|
| + std::string details(TestFailedMessage(msg));
|
| +
|
| + bool subprocess_terminated_by_signal = WIFSIGNALED(status);
|
| + ASSERT_TRUE(subprocess_terminated_by_signal) << details;
|
| + int subprocess_signal_number = WTERMSIG(status);
|
| + ASSERT_EQ(expected_signo, subprocess_signal_number) << details;
|
| +}
|
| +
|
| +void UnitTests::AssertionFailure(const char* expr, const char* file, int line) {
|
| + fprintf(stderr, "%s:%d:%s", file, line, expr);
|
| + fflush(stderr);
|
| + _exit(kExitWithAssertionFailure);
|
| +}
|
| +
|
| +void UnitTests::IgnoreThisTest() {
|
| + fflush(stderr);
|
| + _exit(kIgnoreThisTest);
|
| +}
|
| +
|
| +} // namespace
|
|
|