summaryrefslogtreecommitdiffstats
path: root/sandbox
diff options
context:
space:
mode:
authorjln <jln@chromium.org>2015-02-09 15:34:17 -0800
committerCommit bot <commit-bot@chromium.org>2015-02-09 23:34:54 +0000
commit7b02f56d77aa785784f117c2c71e56987d80a57b (patch)
treedde4e453e4372d047ea4b0614bf6b121181871d6 /sandbox
parent5e62781dabadaf144659e957932ec1f32fe3b078 (diff)
downloadchromium_src-7b02f56d77aa785784f117c2c71e56987d80a57b.zip
chromium_src-7b02f56d77aa785784f117c2c71e56987d80a57b.tar.gz
chromium_src-7b02f56d77aa785784f117c2c71e56987d80a57b.tar.bz2
Linux sandbox: Provide AssertSingleThreaded() helper
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}
Diffstat (limited to 'sandbox')
-rw-r--r--sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc4
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.cc7
-rw-r--r--sandbox/linux/services/thread_helpers.cc137
-rw-r--r--sandbox/linux/services/thread_helpers.h12
-rw-r--r--sandbox/linux/services/thread_helpers_unittests.cc63
5 files changed, 164 insertions, 59 deletions
diff --git a/sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc b/sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc
index 40a99e4..7ddf6fb 100644
--- a/sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc
+++ b/sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc
@@ -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"
@@ -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());
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
index d51fa78..d0a9ed3 100644
--- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
@@ -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; "
diff --git a/sandbox/linux/services/thread_helpers.cc b/sandbox/linux/services/thread_helpers.cc
index dbadbd4..3916aa7 100644
--- a/sandbox/linux/services/thread_helpers.cc
+++ b/sandbox/linux/services/thread_helpers.cc
@@ -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"
@@ -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;
@@ -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);
@@ -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
diff --git a/sandbox/linux/services/thread_helpers.h b/sandbox/linux/services/thread_helpers.h
index 377742a..88360af 100644
--- a/sandbox/linux/services/thread_helpers.h
+++ b/sandbox/linux/services/thread_helpers.h
@@ -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);
};
diff --git a/sandbox/linux/services/thread_helpers_unittests.cc b/sandbox/linux/services/thread_helpers_unittests.cc
index a36fd29..4674e69 100644
--- a/sandbox/linux/services/thread_helpers_unittests.cc
+++ b/sandbox/linux/services/thread_helpers_unittests.cc
@@ -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));
@@ -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()));
@@ -89,7 +90,7 @@ TEST(ThreadHelpers, MAYBE_IsSingleThreadedIterated) {
}
}
-TEST(ThreadHelpers, MAYBE_IsSingleThreadedStartAndStop) {
+TEST(ThreadHelpers, IsSingleThreadedStartAndStop) {
ScopedProcSelfTask task;
ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd()));
@@ -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