diff options
author | jln <jln@chromium.org> | 2015-02-09 15:34:17 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-02-09 23:34:54 +0000 |
commit | 7b02f56d77aa785784f117c2c71e56987d80a57b (patch) | |
tree | dde4e453e4372d047ea4b0614bf6b121181871d6 /sandbox | |
parent | 5e62781dabadaf144659e957932ec1f32fe3b078 (diff) | |
download | chromium_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.cc | 4 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.cc | 7 | ||||
-rw-r--r-- | sandbox/linux/services/thread_helpers.cc | 137 | ||||
-rw-r--r-- | sandbox/linux/services/thread_helpers.h | 12 | ||||
-rw-r--r-- | sandbox/linux/services/thread_helpers_unittests.cc | 63 |
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 |