summaryrefslogtreecommitdiffstats
path: root/sandbox
diff options
context:
space:
mode:
authorjln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-02-08 04:52:46 +0000
committerjln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-02-08 04:52:46 +0000
commitf330b76298ce76aa490c787dc10b5d018ab1d6e5 (patch)
tree2b4988a31437983ede37110e02d1032c0eb027b9 /sandbox
parent8f1a8b337a5d6d41eddc739028703d23088906cc (diff)
downloadchromium_src-f330b76298ce76aa490c787dc10b5d018ab1d6e5.zip
chromium_src-f330b76298ce76aa490c787dc10b5d018ab1d6e5.tar.gz
chromium_src-f330b76298ce76aa490c787dc10b5d018ab1d6e5.tar.bz2
Linux Sandbox: Stop GPU watchdog in accountable way.
The Linux sandbox can sometimes detect a spurious running thread if it has just been stopped. We add a new LinuxSandbox::StopThread() method to safely stop threads and make sure they won't be counted as still running. BUG=328620 NOTRY=true Review URL: https://codereview.chromium.org/147203005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@249937 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox')
-rw-r--r--sandbox/linux/sandbox_linux.gypi2
-rw-r--r--sandbox/linux/sandbox_linux_test_sources.gypi1
-rw-r--r--sandbox/linux/services/thread_helpers.cc84
-rw-r--r--sandbox/linux/services/thread_helpers.h33
-rw-r--r--sandbox/linux/services/thread_helpers_unittests.cc93
5 files changed, 213 insertions, 0 deletions
diff --git a/sandbox/linux/sandbox_linux.gypi b/sandbox/linux/sandbox_linux.gypi
index 366e55a..77a066a 100644
--- a/sandbox/linux/sandbox_linux.gypi
+++ b/sandbox/linux/sandbox_linux.gypi
@@ -181,6 +181,8 @@
'services/broker_process.h',
'services/init_process_reaper.cc',
'services/init_process_reaper.h',
+ 'services/thread_helpers.cc',
+ 'services/thread_helpers.h',
],
'dependencies': [
'../base/base.gyp:base',
diff --git a/sandbox/linux/sandbox_linux_test_sources.gypi b/sandbox/linux/sandbox_linux_test_sources.gypi
index 9255be9..37e48f8 100644
--- a/sandbox/linux/sandbox_linux_test_sources.gypi
+++ b/sandbox/linux/sandbox_linux_test_sources.gypi
@@ -18,6 +18,7 @@
'tests/unit_tests.cc',
'tests/unit_tests.h',
'services/broker_process_unittest.cc',
+ 'services/thread_helpers_unittests.cc',
],
'conditions': [
[ 'compile_suid_client==1', {
diff --git a/sandbox/linux/services/thread_helpers.cc b/sandbox/linux/services/thread_helpers.cc
new file mode 100644
index 0000000..e0794f8
--- /dev/null
+++ b/sandbox/linux/services/thread_helpers.cc
@@ -0,0 +1,84 @@
+// Copyright 2014 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 "sandbox/linux/services/thread_helpers.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+
+namespace sandbox {
+
+bool ThreadHelpers::IsSingleThreaded(int proc_self_task) {
+ CHECK_LE(0, proc_self_task);
+ struct stat task_stat;
+ int fstat_ret = fstat(proc_self_task, &task_stat);
+ PCHECK(0 == fstat_ret);
+
+ // At least "..", "." and the current thread should be present.
+ CHECK_LE(3UL, task_stat.st_nlink);
+ // Counting threads via /proc/self/task could be racy. For the purpose of
+ // determining if the current proces is monothreaded it works: if at any
+ // time it becomes monothreaded, it'll stay so.
+ return task_stat.st_nlink == 3;
+}
+
+bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_self_task,
+ base::Thread* thread) {
+ DCHECK_LE(0, proc_self_task);
+ DCHECK(thread);
+ const base::PlatformThreadId thread_id = thread->thread_id();
+ const std::string thread_id_dir_str = base::IntToString(thread_id) + "/";
+
+ // The kernel is at liberty to wake the thread id futex before updating
+ // /proc. Following Stop(), the thread is joined, but entries in /proc may
+ // 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);
+ }
+
+ return true;
+}
+
+} // namespace sandbox
diff --git a/sandbox/linux/services/thread_helpers.h b/sandbox/linux/services/thread_helpers.h
new file mode 100644
index 0000000..651e5d9
--- /dev/null
+++ b/sandbox/linux/services/thread_helpers.h
@@ -0,0 +1,33 @@
+// Copyright 2014 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.
+
+#ifndef SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_
+#define SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_
+
+#include "base/basictypes.h"
+
+namespace base { class Thread; }
+
+namespace sandbox {
+
+class ThreadHelpers {
+ public:
+ // Check whether the current process is single threaded. |proc_self_tasks|
+ // should be a file descriptor to /proc/self/task/ and remains owned by the
+ // caller.
+ static bool IsSingleThreaded(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,
+ base::Thread* thread);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadHelpers);
+};
+
+} // namespace content
+
+#endif // SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_
diff --git a/sandbox/linux/services/thread_helpers_unittests.cc b/sandbox/linux/services/thread_helpers_unittests.cc
new file mode 100644
index 0000000..725a62e
--- /dev/null
+++ b/sandbox/linux/services/thread_helpers_unittests.cc
@@ -0,0 +1,93 @@
+// Copyright 2014 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 "sandbox/linux/services/thread_helpers.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/process/process_metrics.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "sandbox/linux/tests/unit_tests.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::PlatformThread;
+
+namespace sandbox {
+
+namespace {
+
+int GetRaceTestIterations() {
+ if (IsRunningOnValgrind()) {
+ return 2;
+ } else {
+ return 1000;
+ }
+}
+
+class ScopedProcSelfTask {
+ public:
+ ScopedProcSelfTask() : fd_(-1) {
+ fd_ = open("/proc/self/task/", O_RDONLY | O_DIRECTORY);
+ CHECK_LE(0, fd_);
+ }
+
+ ~ScopedProcSelfTask() { PCHECK(0 == IGNORE_EINTR(close(fd_))); }
+
+ int fd() { return fd_; }
+
+ private:
+ int fd_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedProcSelfTask);
+};
+
+TEST(ThreadHelpers, IsSingleThreadedBasic) {
+ ScopedProcSelfTask task;
+ ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd()));
+
+ base::Thread thread("sandbox_tests");
+ ASSERT_TRUE(thread.Start());
+ ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(task.fd()));
+}
+
+TEST(ThreadHelpers, IsSingleThreadedIterated) {
+ ScopedProcSelfTask task;
+ ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd()));
+
+ // Iterate to check for race conditions.
+ for (int i = 0; i < GetRaceTestIterations(); ++i) {
+ base::Thread thread("sandbox_tests");
+ ASSERT_TRUE(thread.Start());
+ ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(task.fd()));
+ }
+}
+
+TEST(ThreadHelpers, IsSingleThreadedStartAndStop) {
+ ScopedProcSelfTask task;
+ ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd()));
+
+ base::Thread thread("sandbox_tests");
+ // This is testing for a race condition, so iterate.
+ // Manually, this has been tested with more that 1M iterations.
+ for (int i = 0; i < GetRaceTestIterations(); ++i) {
+ ASSERT_TRUE(thread.Start());
+ ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(task.fd()));
+
+ ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(task.fd(), &thread));
+ ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd()));
+ ASSERT_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle()));
+ }
+}
+
+} // namespace
+
+} // namespace sandbox