Bug 1222500 - Handle unexpected thread creation better on desktop Linux. r=gdestuynder, a=sylvestre
authorJed Davis <jld@mozilla.com>
Mon, 30 Nov 2015 18:21:00 +0100
changeset 310749 ff935f7d85179ec858a84f61fd6cd91638b5c57f
parent 310748 c42506273272fcdd223ff9717b4de8ed8ddbf61b
child 310750 ae6a25a60355526058bffa8862aef1c2fb5a2468
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgdestuynder, sylvestre
bugs1222500
milestone45.0a2
Bug 1222500 - Handle unexpected thread creation better on desktop Linux. r=gdestuynder, a=sylvestre
security/sandbox/linux/Sandbox.cpp
security/sandbox/linux/common/SandboxInfo.cpp
security/sandbox/linux/common/SandboxInfo.h
security/sandbox/linux/common/moz.build
security/sandbox/linux/gtest/TestSandboxUtil.cpp
toolkit/xre/nsAppRunner.cpp
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -519,18 +519,21 @@ SandboxEarlyInit(GeckoProcessType aType,
   // finished exiting.  If/when any type of sandboxing is used for the
   // Nuwa process (e.g., unsharing the network namespace there instead
   // of for each content process, to save memory), this will need to be
   // changed by moving the SandboxEarlyInit call to an earlier point.
   if (aIsNuwa) {
     return;
   }
 
+  const SandboxInfo info = SandboxInfo::Get();
+  if (info.Test(SandboxInfo::kUnexpectedThreads)) {
+    return;
+  }
   MOZ_RELEASE_ASSERT(IsSingleThreaded());
-  const SandboxInfo info = SandboxInfo::Get();
 
   // Which kinds of resource isolation (of those that need to be set
   // up at this point) can be used by this process?
   bool canChroot = false;
   bool canUnshareNet = false;
   bool canUnshareIPC = false;
 
   switch (aType) {
--- a/security/sandbox/linux/common/SandboxInfo.cpp
+++ b/security/sandbox/linux/common/SandboxInfo.cpp
@@ -1,20 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "SandboxInfo.h"
+#include "SandboxLogging.h"
 #include "LinuxSched.h"
 
 #include <errno.h>
 #include <stdlib.h>
 #include <sys/prctl.h>
+#include <sys/stat.h>
 #include <sys/syscall.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
 #include "base/posix/eintr_wrapper.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/ArrayUtils.h"
 #include "sandbox/linux/seccomp-bpf/linux_seccomp.h"
@@ -31,16 +33,40 @@
 // to fail without the help of bugs in the kernel or system libraries.
 //
 // Regardless of assertion type, whatever condition caused it to fail
 // should generally also disable the corresponding feature on builds
 // that omit the assertion.
 
 namespace mozilla {
 
+// Bug 1229136: this is copied from ../SandboxUtil.cpp to avoid
+// complicated build issues; renamespaced to avoid the possibility of
+// symbol conflict.
+namespace {
+
+static bool
+IsSingleThreaded()
+{
+  // This detects the thread count indirectly.  /proc/<pid>/task has a
+  // subdirectory for each thread in <pid>'s thread group, and the
+  // link count on the "task" directory follows Unix expectations: the
+  // link from its parent, the "." link from itself, and the ".." link
+  // from each subdirectory; thus, 2+N links for N threads.
+  struct stat sb;
+  if (stat("/proc/self/task", &sb) < 0) {
+    MOZ_DIAGNOSTIC_ASSERT(false, "Couldn't access /proc/self/task!");
+    return false;
+  }
+  MOZ_DIAGNOSTIC_ASSERT(sb.st_nlink >= 3);
+  return sb.st_nlink == 3;
+}
+
+} // anonymous namespace
+
 static bool
 HasSeccompBPF()
 {
   // Allow simulating the absence of seccomp-bpf support, for testing.
   if (getenv("MOZ_FAKE_NO_SANDBOX")) {
     return false;
   }
   // Determine whether seccomp-bpf is supported by trying to
@@ -154,33 +180,38 @@ CanCreateUserNamespace()
   if (!waitpid_ok) {
     return false;
   }
   setenv(kCacheEnvName, "1", 1);
   return true;
 }
 
 /* static */
-const SandboxInfo SandboxInfo::sSingleton = SandboxInfo();
+SandboxInfo SandboxInfo::sSingleton = SandboxInfo();
 
 SandboxInfo::SandboxInfo() {
   int flags = 0;
   static_assert(sizeof(flags) >= sizeof(Flags), "enum Flags fits in an int");
 
   if (HasSeccompBPF()) {
     flags |= kHasSeccompBPF;
     if (HasSeccompTSync()) {
       flags |= kHasSeccompTSync;
     }
   }
 
-  if (HasUserNamespaceSupport()) {
-    flags |= kHasPrivilegedUserNamespaces;
-    if (CanCreateUserNamespace()) {
-      flags |= kHasUserNamespaces;
+  // Detect the threading-problem signal from the parent process.
+  if (getenv("MOZ_SANDBOX_UNEXPECTED_THREADS")) {
+    flags |= kUnexpectedThreads;
+  } else {
+    if (HasUserNamespaceSupport()) {
+      flags |= kHasPrivilegedUserNamespaces;
+      if (CanCreateUserNamespace()) {
+        flags |= kHasUserNamespaces;
+      }
     }
   }
 
 #ifdef MOZ_CONTENT_SANDBOX
   if (!getenv("MOZ_DISABLE_CONTENT_SANDBOX")) {
     flags |= kEnabledForContent;
   }
   if (getenv("MOZ_PERMISSIVE_CONTENT_SANDBOX")) {
@@ -194,9 +225,33 @@ SandboxInfo::SandboxInfo() {
 #endif
   if (getenv("MOZ_SANDBOX_VERBOSE")) {
     flags |= kVerbose;
   }
 
   mFlags = static_cast<Flags>(flags);
 }
 
+/* static */ void
+SandboxInfo::ThreadingCheck()
+{
+  // Allow MOZ_SANDBOX_UNEXPECTED_THREADS to be set manually for testing.
+  if (IsSingleThreaded() &&
+      !getenv("MOZ_SANDBOX_UNEXPECTED_THREADS")) {
+    return;
+  }
+  SANDBOX_LOG_ERROR("unexpected multithreading found; this prevents using"
+                    " namespace sandboxing.%s",
+                    // getenv isn't thread-safe, but see below.
+                    getenv("LD_PRELOAD") ? "  (If you're LD_PRELOAD'ing"
+                    " nVidia GL: that's not necessary for Gecko.)" : "");
+
+  // Propagate this information for use by child processes.  (setenv
+  // isn't thread-safe, but other threads are from non-Gecko code so
+  // they wouldn't be using NSPR; we have to hope for the best.)
+  setenv("MOZ_SANDBOX_UNEXPECTED_THREADS", "1", 0);
+  int flags = sSingleton.mFlags;
+  flags |= kUnexpectedThreads;
+  flags &= ~(kHasUserNamespaces | kHasPrivilegedUserNamespaces);
+  sSingleton.mFlags = static_cast<Flags>(flags);
+}
+
 } // namespace mozilla
--- a/security/sandbox/linux/common/SandboxInfo.h
+++ b/security/sandbox/linux/common/SandboxInfo.h
@@ -34,32 +34,43 @@ public:
     // Kernel can atomically set system call filtering on entire thread group.
     kHasSeccompTSync   = 1 << 4,
     // Can this process create user namespaces? (Man page user_namespaces(7).)
     kHasUserNamespaces = 1 << 5,
     // Could a more privileged process have user namespaces, even if we can't?
     kHasPrivilegedUserNamespaces = 1 << 6,
     // Env var MOZ_PERMISSIVE_CONTENT_SANDBOX
     kPermissive        = 1 << 7,
+    // Something is creating threads when we need to still be single-threaded.
+    kUnexpectedThreads = 1 << 8,
   };
 
   bool Test(Flags aFlag) const { return (mFlags & aFlag) == aFlag; }
 
   // Returns true if SetContentProcessSandbox may be called.
   bool CanSandboxContent() const
   {
     return !Test(kEnabledForContent) || Test(kHasSeccompBPF);
   }
 
   // Returns true if SetMediaPluginSandbox may be called.
   bool CanSandboxMedia() const
   {
     return !Test(kEnabledForMedia) || Test(kHasSeccompBPF);
   }
+
+  // For bug 1222500 or anything else like it: On desktop, this is
+  // called in the parent process at a point when it should still be
+  // single-threaded, to check that the SandboxEarlyInit() call in a
+  // child process is early enough to be single-threaded.  If not,
+  // kUnexpectedThreads is set and affected flags (user namespaces;
+  // possibly others in the future) are cleared.
+  static void ThreadingCheck();
 private:
   enum Flags mFlags;
-  static MOZ_EXPORT const SandboxInfo sSingleton;
+  // This should be const, but has to allow for ThreadingCheck.
+  static MOZ_EXPORT SandboxInfo sSingleton;
   SandboxInfo();
 };
 
 } // namespace mozilla
 
 #endif // mozilla_SandboxInfo_h
--- a/security/sandbox/linux/common/moz.build
+++ b/security/sandbox/linux/common/moz.build
@@ -8,15 +8,16 @@ EXPORTS.mozilla += [
     'SandboxInfo.h',
 ]
 
 SOURCES += [
     'SandboxInfo.cpp',
 ]
 
 LOCAL_INCLUDES += [
-    '/security/sandbox/chromium'
+    '/security/sandbox/chromium',
+    '/security/sandbox/linux', # SandboxLogging.h
 ]
 
 if CONFIG['OS_TARGET'] == 'Android':
     FINAL_LIBRARY = 'mozsandbox'
 else:
     FINAL_LIBRARY = 'xul'
--- a/security/sandbox/linux/gtest/TestSandboxUtil.cpp
+++ b/security/sandbox/linux/gtest/TestSandboxUtil.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 
 #include "SandboxUtil.h"
+#include "SandboxInfo.h"
 
 #include <pthread.h>
 
 namespace mozilla {
 
 // In order to test IsSingleThreaded when the test-running process is
 // single-threaded, before assorted XPCOM components have created many
 // additional threads, a static initializer is used.
@@ -27,13 +28,17 @@ struct EarlyTest {
 };
 
 static const EarlyTest gEarlyTest;
 
 } // namespace
 
 TEST(SandboxUtil, IsSingleThreaded)
 {
+  // If the test system if affected by kUnexpectedThreads, (1) there's
+  // no point in doing this test, and (2) if that happens on Mozilla
+  // CI then burning the tree is an appropriate response.
+  ASSERT_FALSE(SandboxInfo::Get().Test(SandboxInfo::kUnexpectedThreads));
   EXPECT_TRUE(gEarlyTest.mWasSingleThreaded);
   EXPECT_FALSE(IsSingleThreaded());
 }
 
 } // namespace mozilla
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -204,16 +204,20 @@
 #ifdef MOZ_B2G_LOADER
 #include "ProcessUtils.h"
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #endif
 
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX) && !defined(ANDROID)
+#include "mozilla/SandboxInfo.h"
+#endif
+
 extern uint32_t gRestartMode;
 extern void InstallSignalHandlers(const char *ProgramName);
 
 #define FILE_COMPATIBILITY_INFO NS_LITERAL_CSTRING("compatibility.ini")
 #define FILE_INVALIDATE_CACHES NS_LITERAL_CSTRING(".purgecaches")
 
 int    gArgc;
 char **gArgv;
@@ -4316,16 +4320,20 @@ void XRE_GlibInit()
  *            Note that on OSX, aAppData->xreDirectory will point to
  *            .app/Contents/Resources.
  */
 int
 XREMain::XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
 {
   ScopedLogging log;
 
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX) && !defined(ANDROID)
+  SandboxInfo::ThreadingCheck();
+#endif
+
   char aLocal;
   GeckoProfilerInitRAII profilerGuard(&aLocal);
 
   PROFILER_LABEL("Startup", "XRE_Main",
     js::ProfileEntry::Category::OTHER);
 
   nsresult rv = NS_OK;