Skip to content

Commit

Permalink
Linux sandbox: Provide AssertSingleThreaded() helper
Browse files Browse the repository at this point in the history
Provide a new helper API to assert being single threaded that can
wait on the kernel to update /proc/self/task/

Use the helper when started the seccomp sandbox single threaded.

BUG=455407

Review URL: https://codereview.chromium.org/893993004

Cr-Commit-Position: refs/heads/master@{#315435}
  • Loading branch information
jln authored and Commit bot committed Feb 9, 2015
1 parent 5e62781 commit 7b02f56
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 59 deletions.
4 changes: 3 additions & 1 deletion sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "sandbox/linux/seccomp-bpf/trap.h"
#include "sandbox/linux/services/linux_syscalls.h"
#include "sandbox/linux/services/syscall_wrappers.h"
#include "sandbox/linux/services/thread_helpers.h"
#include "sandbox/linux/syscall_broker/broker_file_permission.h"
#include "sandbox/linux/syscall_broker/broker_process.h"
#include "sandbox/linux/tests/scoped_temporary_file.h"
Expand Down Expand Up @@ -2308,7 +2309,8 @@ class AllowAllPolicy : public Policy {
SANDBOX_DEATH_TEST(
SandboxBPF,
StartMultiThreadedAsSingleThreaded,
DEATH_MESSAGE("Cannot start sandbox; process is already multi-threaded")) {
DEATH_MESSAGE(
ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests())) {
base::Thread thread("sandbox.linux.StartMultiThreadedAsSingleThreaded");
BPF_ASSERT(thread.Start());

Expand Down
7 changes: 3 additions & 4 deletions sandbox/linux/seccomp-bpf/sandbox_bpf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,9 @@ bool SandboxBPF::StartSandbox(SeccompLevel seccomp_level) {
const bool supports_tsync = KernelSupportsSeccompTsync();

if (seccomp_level == SeccompLevel::SINGLE_THREADED) {
if (!IsSingleThreaded(proc_task_fd_.get())) {
SANDBOX_DIE("Cannot start sandbox; process is already multi-threaded");
return false;
}
// Wait for /proc/self/task/ to update if needed and assert the
// process is single threaded.
ThreadHelpers::AssertSingleThreaded(proc_task_fd_.get());
} else if (seccomp_level == SeccompLevel::MULTI_THREADED) {
if (IsSingleThreaded(proc_task_fd_.get())) {
SANDBOX_DIE("Cannot start sandbox; "
Expand Down
137 changes: 97 additions & 40 deletions sandbox/linux/services/thread_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
#include <string>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_number_conversions.h"
Expand All @@ -24,6 +27,9 @@ namespace sandbox {

namespace {

const char kAssertSingleThreadedError[] =
"Current process is not mono-threaded!";

bool IsSingleThreadedImpl(int proc_self_task) {
CHECK_LE(0, proc_self_task);
struct stat task_stat;
Expand All @@ -38,22 +44,94 @@ bool IsSingleThreadedImpl(int proc_self_task) {
return task_stat.st_nlink == 3;
}

} // namespace
bool IsThreadPresentInProcFS(int proc_self_task,
const std::string& thread_id_dir_str) {
struct stat task_stat;
const int fstat_ret =
fstatat(proc_self_task, thread_id_dir_str.c_str(), &task_stat, 0);
if (fstat_ret < 0) {
PCHECK(ENOENT == errno);
return false;
}
return true;
}

bool ThreadHelpers::IsSingleThreaded(int proc_self_task) {
// Run |cb| in a loop until it returns false. Every time |cb| runs, sleep
// for an exponentially increasing amount of time. |cb| is expected to return
// false very quickly and this will crash if it doesn't happen within ~64ms on
// Debug builds (2s on Release builds).
// This is guaranteed to not sleep more than twice as much as the bare minimum
// amount of time.
void RunWhileTrue(const base::Callback<bool(void)>& cb) {
#if defined(NDEBUG)
// In Release mode, crash after 30 iterations, which means having spent
// roughly 2s in
// nanosleep(2) cumulatively.
const unsigned int kMaxIterations = 30U;
#else
// In practice, this never goes through more than a couple iterations. In
// debug mode, crash after 64ms (+ eventually 25 times the granularity of
// the clock) in nanosleep(2). This ensures that this is not becoming too
// slow.
const unsigned int kMaxIterations = 25U;
#endif

// Run |cb| with an exponential back-off, sleeping 2^iterations nanoseconds
// in nanosleep(2).
// Note: the clock may not allow for nanosecond granularity, in this case the
// first iterations would sleep a tiny bit more instead, which would not
// change the calculations significantly.
for (unsigned int i = 0; i < kMaxIterations; ++i) {
if (!cb.Run()) {
return;
}

// Increase the waiting time exponentially.
struct timespec ts = {0, 1L << i /* nanoseconds */};
PCHECK(0 == HANDLE_EINTR(nanosleep(&ts, &ts)));
}

LOG(FATAL) << kAssertSingleThreadedError << " (iterations: " << kMaxIterations
<< ")";

NOTREACHED();
}

// Return a ScopedFD to /proc/self/task/. If |proc_self_task| is -1, try to
// open it directly, otherwise duplicate it.
base::ScopedFD OpenProcSelfTask(int proc_self_task) {
DCHECK_LE(-1, proc_self_task);
if (-1 == proc_self_task) {
const int task_fd =
open("/proc/self/task/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
PCHECK(0 <= task_fd);
const bool result = IsSingleThreadedImpl(task_fd);
PCHECK(0 == IGNORE_EINTR(close(task_fd)));
return result;
} else {
return IsSingleThreadedImpl(proc_self_task);
return base::ScopedFD(HANDLE_EINTR(
open("/proc/self/task/", O_RDONLY | O_DIRECTORY | O_CLOEXEC)));
}

return base::ScopedFD(HANDLE_EINTR(
openat(proc_self_task, "./", O_RDONLY | O_DIRECTORY | O_CLOEXEC)));
}

bool IsMultiThreaded(int proc_self_task) {
return !ThreadHelpers::IsSingleThreaded(proc_self_task);
}

} // namespace

// static
bool ThreadHelpers::IsSingleThreaded(int proc_self_task) {
DCHECK_LE(-1, proc_self_task);
base::ScopedFD task_fd(OpenProcSelfTask(proc_self_task));
CHECK(task_fd.is_valid());
return IsSingleThreadedImpl(task_fd.get());
}

// static
void ThreadHelpers::AssertSingleThreaded(int proc_self_task) {
const base::Callback<bool(void)> cb =
base::Bind(&IsMultiThreaded, proc_self_task);
RunWhileTrue(cb);
}

// static
bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_self_task,
base::Thread* thread) {
DCHECK_LE(0, proc_self_task);
Expand All @@ -66,38 +144,17 @@ bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_self_task,
// not have been updated.
thread->Stop();

unsigned int iterations = 0;
bool thread_present_in_procfs = true;
// Poll /proc with an exponential back-off, sleeping 2^iterations nanoseconds
// in nanosleep(2).
// Note: the clock may not allow for nanosecond granularity, in this case the
// first iterations would sleep a tiny bit more instead, which would not
// change the calculations significantly.
while (thread_present_in_procfs) {
struct stat task_stat;
const int fstat_ret =
fstatat(proc_self_task, thread_id_dir_str.c_str(), &task_stat, 0);
if (fstat_ret < 0) {
PCHECK(ENOENT == errno);
// The thread disappeared from /proc, we're done.
thread_present_in_procfs = false;
break;
}
// Increase the waiting time exponentially.
struct timespec ts = {0, 1L << iterations /* nanoseconds */};
PCHECK(0 == HANDLE_EINTR(nanosleep(&ts, &ts)));
++iterations;

// Crash after 30 iterations, which means having spent roughly 2s in
// nanosleep(2) cumulatively.
CHECK_GT(30U, iterations);
// In practice, this never goes through more than a couple iterations. In
// debug mode, crash after 64ms (+ eventually 25 times the granularity of
// the clock) in nanosleep(2).
DCHECK_GT(25U, iterations);
}
const base::Callback<bool(void)> cb =
base::Bind(&IsThreadPresentInProcFS, proc_self_task, thread_id_dir_str);

RunWhileTrue(cb);

return true;
}

// static
const char* ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests() {
return kAssertSingleThreadedError;
}

} // namespace sandbox
12 changes: 11 additions & 1 deletion sandbox/linux/services/thread_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ class SANDBOX_EXPORT ThreadHelpers {
// crash if it cannot.
static bool IsSingleThreaded(int proc_self_task);

// Crash if the current process is not single threaded. This will wait
// on /proc to be updated. In the case where this doesn't crash, this will
// return promptly. In the case where this does crash, this will first wait
// for a few ms in Debug mode, a few seconds in Release mode.
// If |proc_self_tasks| is -1, this method will open /proc/self/task/ and
// crash if it cannot.
static void AssertSingleThreaded(int proc_self_task);

// Stop |thread| and ensure that it does not have an entry in
// /proc/self/task/ from the point of view of the current thread. This is
// the way to stop threads before calling IsSingleThreaded().
static bool StopThreadAndWatchProcFS(int proc_self_tasks,
static bool StopThreadAndWatchProcFS(int proc_self_task,
base::Thread* thread);

static const char* GetAssertSingleThreadedErrorMessageForTests();

private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadHelpers);
};
Expand Down
63 changes: 50 additions & 13 deletions sandbox/linux/services/thread_helpers_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,10 @@ class ScopedProcSelfTask {
DISALLOW_COPY_AND_ASSIGN(ScopedProcSelfTask);
};

#if defined(THREAD_SANITIZER)
// These tests fail under ThreadSanitizer, see http://crbug.com/342305
#define MAYBE_IsSingleThreadedBasic DISABLED_IsSingleThreadedBasic
#define MAYBE_IsSingleThreadedIterated DISABLED_IsSingleThreadedIterated
#define MAYBE_IsSingleThreadedStartAndStop DISABLED_IsSingleThreadedStartAndStop
#else
#define MAYBE_IsSingleThreadedBasic IsSingleThreadedBasic
#define MAYBE_IsSingleThreadedIterated IsSingleThreadedIterated
#define MAYBE_IsSingleThreadedStartAndStop IsSingleThreadedStartAndStop
#endif

TEST(ThreadHelpers, MAYBE_IsSingleThreadedBasic) {
#if !defined(THREAD_SANITIZER)

TEST(ThreadHelpers, IsSingleThreadedBasic) {
ScopedProcSelfTask task;
ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd()));
ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(-1));
Expand All @@ -75,7 +67,16 @@ TEST(ThreadHelpers, MAYBE_IsSingleThreadedBasic) {
ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(task.fd(), &thread));
}

TEST(ThreadHelpers, MAYBE_IsSingleThreadedIterated) {
SANDBOX_TEST(ThreadHelpers, AssertSingleThreaded) {
ScopedProcSelfTask task;
SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded(task.fd()));
SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded(-1));

ThreadHelpers::AssertSingleThreaded(task.fd());
ThreadHelpers::AssertSingleThreaded(-1);
}

TEST(ThreadHelpers, IsSingleThreadedIterated) {
ScopedProcSelfTask task;
ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd()));

Expand All @@ -89,7 +90,7 @@ TEST(ThreadHelpers, MAYBE_IsSingleThreadedIterated) {
}
}

TEST(ThreadHelpers, MAYBE_IsSingleThreadedStartAndStop) {
TEST(ThreadHelpers, IsSingleThreadedStartAndStop) {
ScopedProcSelfTask task;
ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd()));

Expand All @@ -106,6 +107,42 @@ TEST(ThreadHelpers, MAYBE_IsSingleThreadedStartAndStop) {
}
}

SANDBOX_TEST(ThreadHelpers, AssertSingleThreadedAfterThreadStopped) {
SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded(-1));

base::Thread thread1("sandbox_tests");
base::Thread thread2("sandbox_tests");

for (int i = 0; i < GetRaceTestIterations(); ++i) {
SANDBOX_ASSERT(thread1.Start());
SANDBOX_ASSERT(thread2.Start());
SANDBOX_ASSERT(!ThreadHelpers::IsSingleThreaded(-1));

thread1.Stop();
thread2.Stop();
// This will wait on /proc/ to reflect the state of threads in the
// process.
ThreadHelpers::AssertSingleThreaded(-1);
SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded(-1));
}
}

// Only run this test in Debug mode, where AssertSingleThreaded() will return
// in less than 64ms.
#if !defined(NDEBUG)
SANDBOX_DEATH_TEST(
ThreadHelpers,
AssertSingleThreadedDies,
DEATH_MESSAGE(
ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests())) {
base::Thread thread1("sandbox_tests");
SANDBOX_ASSERT(thread1.Start());
ThreadHelpers::AssertSingleThreaded(-1);
}
#endif // !defined(NDEBUG)

#endif // !defined(THREAD_SANITIZER)

} // namespace

} // namespace sandbox

0 comments on commit 7b02f56

Please sign in to comment.