Bug 1043733 - Require sandboxing to load Gecko Media Plugins on Linux. r=jesup, r=kang, a=sledru
authorJed Davis <jld@mozilla.com>
Fri, 15 Aug 2014 15:00:00 -0400
changeset 216390 8bcaa2c7dab7df62ef08306059a8409077430736
parent 216389 90e489c8fc4b9e2373ba1651629e3caf024fc795
child 216391 d880059c952ecc82a9d1b3503788a711f96029ef
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup, kang, sledru
bugs1043733
milestone33.0a2
Bug 1043733 - Require sandboxing to load Gecko Media Plugins on Linux. r=jesup, r=kang, a=sledru Also refactors how sandbox support and disabling are handled, and allows simulating a lack of sandbox support with an env var (for testing without rebuilding a kernel).
content/media/gmp/GMPChild.cpp
content/media/gmp/GMPService.cpp
dom/ipc/ContentChild.cpp
security/sandbox/linux/Sandbox.cpp
security/sandbox/linux/Sandbox.h
--- a/content/media/gmp/GMPChild.cpp
+++ b/content/media/gmp/GMPChild.cpp
@@ -98,16 +98,17 @@ GMPChild::LoadPluginLibrary(const std::s
   libFile->AppendRelativePath(binaryName);
 
   nsAutoCString nativePath;
   libFile->GetNativePath(nativePath);
 
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
   // Enable sandboxing here -- we know the plugin file's path, but
   // this process's execution hasn't been affected by its content yet.
+  MOZ_ASSERT(mozilla::CanSandboxMediaPlugin());
   mozilla::SetMediaPluginSandbox(nativePath.get());
 #endif
 
   mLib = PR_LoadLibrary(nativePath.get());
   if (!mLib) {
     return false;
   }
 
--- a/content/media/gmp/GMPService.cpp
+++ b/content/media/gmp/GMPService.cpp
@@ -13,16 +13,19 @@
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SyncRunnable.h"
 #include "nsXPCOMPrivate.h"
 #include "mozilla/Services.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsIConsoleService.h"
 #include "mozilla/unused.h"
 #include "runnable_utils.h"
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+#include "mozilla/Sandbox.h"
+#endif
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 #ifdef PR_LOGGING
@@ -379,16 +382,21 @@ GeckoMediaPluginService::PathRunnable::R
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::AddPluginDirectory(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_IsMainThread());
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+  if (!mozilla::CanSandboxMediaPlugin()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+#endif
   nsCOMPtr<nsIThread> thread;
   nsresult rv = GetThread(getter_AddRefs(thread));
   if (NS_FAILED(rv)) {
     return rv;
   }
   nsCOMPtr<nsIRunnable> r = new PathRunnable(this, aDirectory, true);
   thread->Dispatch(r, NS_DISPATCH_NORMAL);
   return NS_OK;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -913,17 +913,19 @@ ContentChild::AllocPBackgroundChild(Tran
 
 bool
 ContentChild::RecvSetProcessSandbox()
 {
   // We may want to move the sandbox initialization somewhere else
   // at some point; see bug 880808.
 #if defined(MOZ_CONTENT_SANDBOX)
 #if defined(XP_LINUX)
-    SetContentProcessSandbox();
+    if (CanSandboxContentProcess()) {
+        SetContentProcessSandbox();
+    }
 #elif defined(XP_WIN)
     mozilla::SandboxTarget::Instance()->StartSandbox();
 #endif
 #endif
     return true;
 }
 
 bool
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -53,16 +53,50 @@ namespace mozilla {
 #ifdef MOZ_GMP_SANDBOX
 // For media plugins, we can start the sandbox before we dlopen the
 // module, so we have to pre-open the file and simulate the sandboxed
 // open().
 static int gMediaPluginFileDesc = -1;
 static const char *gMediaPluginFilePath;
 #endif
 
+struct SandboxFlags {
+  bool isSupported;
+#ifdef MOZ_CONTENT_SANDBOX
+  bool isDisabledForContent;
+#endif
+#ifdef MOZ_GMP_SANDBOX
+  bool isDisabledForGMP;
+#endif
+
+  SandboxFlags() {
+    // Allow simulating the absence of seccomp-bpf support, for testing.
+    if (getenv("MOZ_FAKE_NO_SANDBOX")) {
+      isSupported = false;
+    } else {
+      // Determine whether seccomp-bpf is supported by trying to
+      // enable it with an invalid pointer for the filter.  This will
+      // fail with EFAULT if supported and EINVAL if not, without
+      // changing the process's state.
+      if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, nullptr) != -1) {
+        MOZ_CRASH("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, nullptr) didn't fail");
+      }
+      isSupported = errno == EFAULT;
+    }
+#ifdef MOZ_CONTENT_SANDBOX
+    isDisabledForContent = getenv("MOZ_DISABLE_CONTENT_SANDBOX");
+#endif
+#ifdef MOZ_GMP_SANDBOX
+    isDisabledForGMP = getenv("MOZ_DISABLE_GMP_SANDBOX");
+#endif
+  }
+};
+
+static const SandboxFlags gSandboxFlags;
+
 /**
  * Log JS stack info in the same place as the sandbox violation
  * message.  Useful in case the responsible code is JS and all we have
  * are logs and a minidump with the C++ stacks (e.g., on TBPL).
  */
 static void
 SandboxLogJSStack(void)
 {
@@ -219,30 +253,33 @@ InstallSyscallReporter(void)
 /**
  * This function installs the syscall filter, a.k.a. seccomp.
  * PR_SET_NO_NEW_PRIVS ensures that it is impossible to grant more
  * syscalls to the process beyond this point (even after fork()).
  * SECCOMP_MODE_FILTER is the "bpf" mode of seccomp which allows
  * to pass a bpf program (in our case, it contains a syscall
  * whitelist).
  *
- * @return 0 on success, 1 on failure.
+ * Reports failure by crashing.
+ *
  * @see sock_fprog (the seccomp_prog).
  */
-static int
+static void
 InstallSyscallFilter(const sock_fprog *prog)
 {
   if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
-    return 1;
+    LOG_ERROR("prctl(PR_SET_NO_NEW_PRIVS) failed: %s", strerror(errno));
+    MOZ_CRASH("prctl(PR_SET_NO_NEW_PRIVS)");
   }
 
   if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)prog, 0, 0)) {
-    return 1;
+    LOG_ERROR("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER) failed: %s",
+              strerror(errno));
+    MOZ_CRASH("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)");
   }
-  return 0;
 }
 
 // Use signals for permissions that need to be set per-thread.
 // The communication channel from the signal handler back to the main thread.
 static mozilla::Atomic<int> sSetSandboxDone;
 // Pass the filter itself through a global.
 static const sock_fprog *sSetSandboxFilter;
 
@@ -263,32 +300,26 @@ FindFreeSignalNumber()
         (sa.sa_flags & SA_SIGINFO) == 0 &&
         sa.sa_handler == SIG_DFL) {
       return signum;
     }
   }
   return 0;
 }
 
+// Returns true if sandboxing was enabled, or false if sandboxing
+// already was enabled.  Crashes if sandboxing could not be enabled.
 static bool
 SetThreadSandbox()
 {
-  bool didAnything = false;
-
   if (prctl(PR_GET_SECCOMP, 0, 0, 0, 0) == 0) {
-    if (InstallSyscallFilter(sSetSandboxFilter) == 0) {
-      didAnything = true;
-    }
-    /*
-     * Bug 880797: when all B2G devices are required to support
-     * seccomp-bpf, this should exit/crash if InstallSyscallFilter
-     * returns nonzero (ifdef MOZ_WIDGET_GONK).
-     */
+    InstallSyscallFilter(sSetSandboxFilter);
+    return true;
   }
-  return didAnything;
+  return false;
 }
 
 static void
 SetThreadSandboxHandler(int signum)
 {
   // The non-zero number sent back to the main thread indicates
   // whether action was taken.
   if (SetThreadSandbox()) {
@@ -430,53 +461,49 @@ BroadcastSetThreadSandbox(SandboxType aT
     LOG_ERROR("handler for signal %d was changed to %p!", signum, oldHandler);
     MOZ_CRASH();
   }
   unused << closedir(taskdp);
   // And now, deprivilege the main thread:
   SetThreadSandbox();
 }
 
-// This function can overapproximate (i.e., return true even if
-// sandboxing isn't supported, but not the reverse).  See bug 993145.
-static bool
-IsSandboxingSupported(void)
-{
-  return prctl(PR_GET_SECCOMP) != -1;
-}
-
 // Common code for sandbox startup.
 static void
 SetCurrentProcessSandbox(SandboxType aType)
 {
   if (InstallSyscallReporter()) {
     LOG_ERROR("install_syscall_reporter() failed\n");
   }
 
-  if (IsSandboxingSupported()) {
-    BroadcastSetThreadSandbox(aType);
-  }
+  BroadcastSetThreadSandbox(aType);
 }
 
 #ifdef MOZ_CONTENT_SANDBOX
 /**
  * Starts the seccomp sandbox for a content process.  Should be called
  * only once, and before any potentially harmful content is loaded.
  *
  * Will normally make the process exit on failure.
 */
 void
 SetContentProcessSandbox()
 {
-  if (PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+  if (gSandboxFlags.isDisabledForContent) {
     return;
   }
 
   SetCurrentProcessSandbox(kSandboxContentProcess);
 }
+
+bool
+CanSandboxContentProcess()
+{
+  return gSandboxFlags.isSupported || gSandboxFlags.isDisabledForContent;
+}
 #endif // MOZ_CONTENT_SANDBOX
 
 #ifdef MOZ_GMP_SANDBOX
 /**
  * Starts the seccomp sandbox for a media plugin process.  Should be
  * called only once, and before any potentially harmful content is
  * loaded -- including the plugin itself, if it's considered untrusted.
  *
@@ -484,31 +511,37 @@ SetContentProcessSandbox()
  * read-only after the sandbox starts; it should be the .so file
  * implementing the not-yet-loaded plugin.
  *
  * Will normally make the process exit on failure.
 */
 void
 SetMediaPluginSandbox(const char *aFilePath)
 {
-  if (PR_GetEnv("MOZ_DISABLE_GMP_SANDBOX")) {
+  if (gSandboxFlags.isDisabledForGMP) {
     return;
   }
 
   if (aFilePath) {
     gMediaPluginFilePath = strdup(aFilePath);
     gMediaPluginFileDesc = open(aFilePath, O_RDONLY | O_CLOEXEC);
     if (gMediaPluginFileDesc == -1) {
       LOG_ERROR("failed to open plugin file %s: %s", aFilePath, strerror(errno));
       MOZ_CRASH();
     }
   }
   // Finally, start the sandbox.
   SetCurrentProcessSandbox(kSandboxMediaPlugin);
 }
+
+bool
+CanSandboxMediaPlugin()
+{
+  return gSandboxFlags.isSupported || gSandboxFlags.isDisabledForGMP;
+}
 #endif // MOZ_GMP_SANDBOX
 
 } // namespace mozilla
 
 
 // "Polyfill" for sandbox::Die, the real version of which requires
 // Chromium's logging code.
 namespace sandbox {
--- a/security/sandbox/linux/Sandbox.h
+++ b/security/sandbox/linux/Sandbox.h
@@ -4,19 +4,30 @@
  * 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/. */
 
 #ifndef mozilla_Sandbox_h
 #define mozilla_Sandbox_h
 
 namespace mozilla {
 
+// The Set*Sandbox() functions must not be called if the corresponding
+// CanSandbox*() function has returned false; if sandboxing is
+// attempted, any failure to enable it is fatal.
+//
+// If sandboxing is disabled for a process type with the corresponding
+// environment variable, Set*Sandbox() does nothing and CanSandbox*()
+// returns true.
+
 #ifdef MOZ_CONTENT_SANDBOX
+// Disabled by setting env var MOZ_DISABLE_CONTENT_SANDBOX.
+bool CanSandboxContentProcess();
 void SetContentProcessSandbox();
 #endif
 #ifdef MOZ_GMP_SANDBOX
+// Disabled by setting env var MOZ_DISABLE_GMP_SANDBOX.
+bool CanSandboxMediaPlugin();
 void SetMediaPluginSandbox(const char *aFilePath);
 #endif
 
 } // namespace mozilla
 
 #endif // mozilla_Sandbox_h
-