Bug 1088488 - Add GMPLoader interface to encapsulate loading GMPs, pass that to XRE_InitChildProcess. r=jesup,r=bsmedberg,r=glandium
authorChris Pearce <cpearce@mozilla.com>
Fri, 14 Nov 2014 21:26:24 +1300
changeset 232010 591742c4c713288981400ff91e18b373cf17b3a6
parent 231936 ea818fdbd81c5a06b063136f8ce7929d397e5e7a
child 232011 8078ba66f37cd1c9b6b6a63e358ad5bdc0db32bd
push id7326
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:58:42 +0000
treeherdermozilla-aurora@d3a3b2a0f2f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup, bsmedberg, glandium
bugs1088488
milestone36.0a1
Bug 1088488 - Add GMPLoader interface to encapsulate loading GMPs, pass that to XRE_InitChildProcess. r=jesup,r=bsmedberg,r=glandium
dom/media/gmp/GMPLoader.cpp
dom/media/gmp/GMPLoader.h
dom/media/gmp/GMPProcessChild.cpp
dom/media/gmp/GMPProcessChild.h
dom/media/gmp/moz.build
ipc/app/moz.build
ipc/contentproc/plugin-container.cpp
mozglue/android/APKOpen.cpp
toolkit/xre/nsEmbedFunctions.cpp
xpcom/build/nsXULAppAPI.h
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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 "GMPLoader.h"
+#include <stdio.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/NullPtr.h"
+#include "gmp-entrypoints.h"
+#include "prlink.h"
+
+#include <string>
+
+namespace mozilla {
+namespace gmp {
+
+class GMPLoaderImpl : public GMPLoader {
+public:
+  explicit GMPLoaderImpl(SandboxStarter* aStarter)
+    : mSandboxStarter(aStarter)
+  {}
+  virtual ~GMPLoaderImpl() {}
+
+  virtual bool Load(const char* aLibPath,
+                    uint32_t aLibPathLen,
+                    char* aOriginSalt,
+                    uint32_t aOriginSaltLen,
+                    const GMPPlatformAPI* aPlatformAPI) MOZ_OVERRIDE;
+
+  virtual GMPErr GetAPI(const char* aAPIName,
+                        void* aHostAPI,
+                        void** aPluginAPI) MOZ_OVERRIDE;
+
+  virtual void Shutdown() MOZ_OVERRIDE;
+
+#ifdef SANDBOX_NOT_STATICALLY_LINKED_INTO_PLUGIN_CONTAINER
+  virtual void SetStartSandboxStarter(SandboxStarter* aStarter) MOZ_OVERRIDE {
+    mSandboxStarter = aStarter;
+  }
+#endif
+
+private:
+  PRLibrary* mLib;
+  GMPGetAPIFunc mGetAPIFunc;
+  SandboxStarter* mSandboxStarter;
+};
+
+GMPLoader* CreateGMPLoader(SandboxStarter* aStarter) {
+  return static_cast<GMPLoader*>(new GMPLoaderImpl(aStarter));
+}
+
+bool
+GMPLoaderImpl::Load(const char* aLibPath,
+                    uint32_t aLibPathLen,
+                    char* aOriginSalt,
+                    uint32_t aOriginSaltLen,
+                    const GMPPlatformAPI* aPlatformAPI)
+{
+  std::string nodeId(aOriginSalt, aOriginSalt + aOriginSaltLen);
+
+  // TODO (subsequent patch): Hash node id with device id, send to GMP.
+
+#if defined(MOZ_GMP_SANDBOX)
+  // Start the sandbox now that we've generated the device bound node id.
+  // This must happen after the node id is bound to the device id, as
+  // generating the device id requires privileges.
+  if (mSandboxStarter) {
+    mSandboxStarter->Start();
+  }
+#endif
+
+  // Load the GMP.
+  PRLibSpec libSpec;
+  libSpec.value.pathname = aLibPath;
+  libSpec.type = PR_LibSpec_Pathname;
+  mLib = PR_LoadLibraryWithFlags(libSpec, 0);
+  if (!mLib) {
+    return false;
+  }
+
+  GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
+  if (!initFunc) {
+    return false;
+  }
+
+  if (initFunc(aPlatformAPI) != GMPNoErr) {
+    return false;
+  }
+
+  mGetAPIFunc = reinterpret_cast<GMPGetAPIFunc>(PR_FindFunctionSymbol(mLib, "GMPGetAPI"));
+  if (!mGetAPIFunc) {
+    return false;
+  }
+
+  return true;
+}
+
+GMPErr
+GMPLoaderImpl::GetAPI(const char* aAPIName,
+                      void* aHostAPI,
+                      void** aPluginAPI)
+{
+  return mGetAPIFunc ? mGetAPIFunc(aAPIName, aHostAPI, aPluginAPI)
+                     : GMPGenericErr;
+}
+
+void
+GMPLoaderImpl::Shutdown()
+{
+  if (mLib) {
+    GMPShutdownFunc shutdownFunc = reinterpret_cast<GMPShutdownFunc>(PR_FindFunctionSymbol(mLib, "GMPShutdown"));
+    if (shutdownFunc) {
+      shutdownFunc();
+    }
+    PR_UnloadLibrary(mLib);
+    mLib = nullptr;
+  }
+}
+
+} // namespace gmp
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/GMPLoader.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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/. */
+
+#ifndef GMP_LOADER_H__
+#define GMP_LOADER_H__
+
+#include <stdint.h>
+#include "gmp-entrypoints.h"
+
+namespace mozilla {
+namespace gmp {
+
+class SandboxStarter {
+public:
+  virtual ~SandboxStarter() {}
+  virtual void Start() = 0;
+};
+
+#if (defined(XP_LINUX) || defined(XP_MACOSX))
+#define SANDBOX_NOT_STATICALLY_LINKED_INTO_PLUGIN_CONTAINER 1
+#endif
+
+// Encapsulates generating the device-bound node id, activating the sandbox,
+// loading the GMP, and passing the node id to the GMP (in that order).
+//
+// In Desktop Gecko, the implementation of this lives in plugin-container,
+// and is passed into XUL code from on startup. The GMP IPC child protocol actor
+// uses this interface to load and retrieve interfaces from the GMPs.
+//
+// In Desktop Gecko the implementation lives in the plugin-container so that
+// it can be covered by DRM vendor's voucher.
+//
+// On Android the GMPLoader implementation lives in libxul (because for the time
+// being GMPLoader relies upon NSPR, which we can't use in plugin-container
+// on Android).
+//
+// There is exactly one GMPLoader per GMP child process, and only one GMP
+// per child process (so the GMPLoader only loads one GMP).
+class GMPLoader {
+public:
+  virtual ~GMPLoader() {}
+
+  // Calculates the device-bound node id, then activates the sandbox,
+  // then loads the GMP library and (if applicable) passes the bound node id
+  // to the GMP.
+  virtual bool Load(const char* aLibPath,
+                    uint32_t aLibPathLen,
+                    char* aOriginSalt,
+                    uint32_t aOriginSaltLen,
+                    const GMPPlatformAPI* aPlatformAPI) = 0;
+
+  // Retrieves an interface pointer from the GMP.
+  virtual GMPErr GetAPI(const char* aAPIName, void* aHostAPI, void** aPluginAPI) = 0;
+
+  // Calls the GMPShutdown function exported by the GMP lib, and unloads the
+  // plugin library.
+  virtual void Shutdown() = 0;
+
+#ifdef SANDBOX_NOT_STATICALLY_LINKED_INTO_PLUGIN_CONTAINER
+  // Encapsulates starting the sandbox on Linux and MacOSX.
+  // TODO: Remove this, and put sandbox in plugin-container on all platforms.
+  virtual void SetStartSandboxStarter(SandboxStarter* aStarter) = 0;
+#endif
+};
+
+// On Desktop, this function resides in plugin-container.
+// On Mobile, this function resides in XUL.
+GMPLoader* CreateGMPLoader(SandboxStarter* aStarter);
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // GMP_LOADER_H__
--- a/dom/media/gmp/GMPProcessChild.cpp
+++ b/dom/media/gmp/GMPProcessChild.cpp
@@ -54,10 +54,26 @@ GMPProcessChild::Init()
 }
 
 void
 GMPProcessChild::CleanUp()
 {
   BackgroundHangMonitor::Shutdown();
 }
 
+GMPLoader* GMPProcessChild::mLoader = nullptr;
+
+/* static */
+void
+GMPProcessChild::SetGMPLoader(GMPLoader* aLoader)
+{
+  mLoader = aLoader;
+}
+
+/* static */
+GMPLoader*
+GMPProcessChild::GetGMPLoader()
+{
+  return mLoader;
+}
+
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/GMPProcessChild.h
+++ b/dom/media/gmp/GMPProcessChild.h
@@ -7,29 +7,37 @@
 #define GMPProcessChild_h_
 
 #include "mozilla/ipc/ProcessChild.h"
 #include "GMPChild.h"
 
 namespace mozilla {
 namespace gmp {
 
+class GMPLoader;
+
 class GMPProcessChild MOZ_FINAL : public mozilla::ipc::ProcessChild {
 protected:
   typedef mozilla::ipc::ProcessChild ProcessChild;
 
 public:
   explicit GMPProcessChild(ProcessHandle parentHandle);
   ~GMPProcessChild();
 
   virtual bool Init() MOZ_OVERRIDE;
   virtual void CleanUp() MOZ_OVERRIDE;
 
+  // Set/get the GMPLoader singleton for this child process.
+  // Note: The GMPLoader is not deleted by this object, the caller of
+  // SetGMPLoader() must manage the GMPLoader's lifecycle.
+  static void SetGMPLoader(GMPLoader* aHost);
+  static GMPLoader* GetGMPLoader();
+
 private:
   GMPChild mPlugin;
-
+  static GMPLoader* mLoader;
   DISALLOW_COPY_AND_ASSIGN(GMPProcessChild);
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPProcessChild_h_
--- a/dom/media/gmp/moz.build
+++ b/dom/media/gmp/moz.build
@@ -34,16 +34,17 @@ EXPORTS += [
     'GMPAudioDecoderProxy.h',
     'GMPAudioHost.h',
     'GMPCallbackBase.h',
     'GMPChild.h',
     'GMPDecryptorChild.h',
     'GMPDecryptorParent.h',
     'GMPDecryptorProxy.h',
     'GMPEncryptedBufferDataImpl.h',
+    'GMPLoader.h',
     'GMPMessageUtils.h',
     'GMPParent.h',
     'GMPPlatform.h',
     'GMPProcessChild.h',
     'GMPProcessParent.h',
     'GMPService.h',
     'GMPSharedMemManager.h',
     'GMPStorageChild.h',
@@ -57,16 +58,23 @@ EXPORTS += [
     'GMPVideoEncoderChild.h',
     'GMPVideoEncoderParent.h',
     'GMPVideoEncoderProxy.h',
     'GMPVideoHost.h',
     'GMPVideoi420FrameImpl.h',
     'GMPVideoPlaneImpl.h',
 ]
 
+# We link GMPLoader into xul on B2G/Fennec as its code does not need to be
+# covered by a DRM vendor's voucher.
+if CONFIG['OS_TARGET'] == 'Android':
+    SOURCES += [
+      'GMPLoader.cpp',
+    ]
+
 UNIFIED_SOURCES += [
     'GMPAudioDecoderChild.cpp',
     'GMPAudioDecoderParent.cpp',
     'GMPAudioHost.cpp',
     'GMPChild.cpp',
     'GMPDecryptorChild.cpp',
     'GMPDecryptorParent.cpp',
     'GMPEncryptedBufferDataImpl.cpp',
--- a/ipc/app/moz.build
+++ b/ipc/app/moz.build
@@ -28,16 +28,23 @@ else:
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/toolkit/xre',
     '/xpcom/base',
 ]
 
+# We link GMPLoader into plugin-container on desktop so that its code is
+# covered by the desktop DRM vendor's voucher.
+if CONFIG['OS_TARGET'] != 'Android':
+    SOURCES += [
+        '../../dom/media/gmp/GMPLoader.cpp',
+    ]
+
 if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT':
     # For sandbox includes and the include dependencies those have
     LOCAL_INCLUDES += [
         '/security',
         '/security/sandbox',
         '/security/sandbox/chromium',
     ]
     USE_LIBS += [
--- a/ipc/contentproc/plugin-container.cpp
+++ b/ipc/contentproc/plugin-container.cpp
@@ -1,31 +1,34 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: sw=4 ts=4 et :
  * 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 "nsXPCOM.h"
 #include "nsXULAppAPI.h"
+#include "nsAutoPtr.h"
 
 // FIXME/cjones testing
 #if !defined(OS_WIN)
 #include <unistd.h>
 #endif
 
 #ifdef XP_WIN
 #include <windows.h>
 // we want a wmain entry point
 // but we don't want its DLL load protection, because we'll handle it here
 #define XRE_DONT_PROTECT_DLL_LOAD
 #include "nsWindowsWMain.cpp"
 #include "nsSetDllDirectory.h"
 #endif
 
+#include "GMPLoader.h"
+
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
 #include "sandbox/chromium/base/basictypes.h"
 #include "sandbox/win/src/sandbox.h"
 #include "sandbox/win/src/sandbox_factory.h"
 #include "mozilla/sandboxTarget.h"
 
 #if defined(MOZ_CONTENT_SANDBOX)
 #include "mozilla/warnonlysandbox/wosCallbacks.h"
@@ -77,18 +80,39 @@ static bool gIsSandboxEnabled = false;
 void StartSandboxCallback()
 {
     if (gIsSandboxEnabled) {
         sandbox::TargetServices* target_service =
             sandbox::SandboxFactory::GetTargetServices();
         target_service->LowerToken();
     }
 }
+
+class WinSandboxStarter : public mozilla::gmp::SandboxStarter {
+public:
+    virtual void Start() MOZ_OVERRIDE {
+        StartSandboxCallback();
+    }
+};
 #endif
 
+mozilla::gmp::SandboxStarter*
+MakeSandboxStarter()
+{
+    // Note: Linux and MacOSX create their SandboxStarters inside xul code,
+    // they need to change to statically link their sandbox code into
+    // plugin-container. Once they do that, we can create SandboxStarters for
+    // them here.
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+    return new WinSandboxStarter();
+#else
+    return nullptr;
+#endif
+}
+
 int
 content_process_main(int argc, char* argv[])
 {
     // Check for the absolute minimum number of args we need to move
     // forward here. We expect the last arg to be the child process type.
     if (argc < 1) {
       return 3;
     }
@@ -149,14 +173,22 @@ content_process_main(int argc, char* arg
         mozilla::SandboxTarget::Instance()->SetStartSandboxCallback(StartSandboxCallback);
 
 #if defined(MOZ_CONTENT_SANDBOX)
         mozilla::warnonlysandbox::PrepareForInit();
 #endif
     }
 #endif
 #endif
-
-    nsresult rv = XRE_InitChildProcess(argc, argv);
+    nsAutoPtr<mozilla::gmp::GMPLoader> loader;
+#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_GONK)
+    // On desktop, the GMPLoader lives in plugin-container, so that its
+    // code can be covered by an EME/GMP vendor's voucher.
+    nsAutoPtr<mozilla::gmp::SandboxStarter> starter(MakeSandboxStarter());
+    if (XRE_GetProcessType() == GeckoProcessType_GMPlugin) {
+        loader = mozilla::gmp::CreateGMPLoader(starter);
+    }
+#endif
+    nsresult rv = XRE_InitChildProcess(argc, argv, loader);
     NS_ENSURE_SUCCESS(rv, 1);
 
     return 0;
 }
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -440,16 +440,16 @@ ChildProcessInit(int argc, char* argv[])
   }
   if (loadGeckoLibs(argv[i]) != SUCCESS) {
     return FAILURE;
   }
 
   void (*fXRE_SetProcessType)(char*);
   xul_dlsym("XRE_SetProcessType", &fXRE_SetProcessType);
 
-  mozglueresult (*fXRE_InitChildProcess)(int, char**);
+  mozglueresult (*fXRE_InitChildProcess)(int, char**, void*);
   xul_dlsym("XRE_InitChildProcess", &fXRE_InitChildProcess);
 
   fXRE_SetProcessType(argv[--argc]);
 
-  return fXRE_InitChildProcess(argc, argv);
+  return fXRE_InitChildProcess(argc, argv, nullptr);
 }
 
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -66,16 +66,17 @@
 #include "mozilla/dom/ContentProcess.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/ipc/XPCShellEnvironment.h"
 
 #include "GMPProcessChild.h"
+#include "GMPLoader.h"
 
 #include "GeckoProfiler.h"
 
 #if defined(MOZ_CONTENT_SANDBOX) && defined(XP_WIN)
 #define TARGET_SANDBOX_EXPORTS
 #include "mozilla/warnonlysandbox/wosCallbacks.h"
 #endif
 
@@ -99,16 +100,20 @@ using mozilla::ipc::IOThreadChild;
 using mozilla::ipc::ProcessChild;
 using mozilla::ipc::ScopedXREEmbed;
 
 using mozilla::plugins::PluginProcessChild;
 using mozilla::dom::ContentProcess;
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
 
+using mozilla::gmp::GMPLoader;
+using mozilla::gmp::CreateGMPLoader;
+using mozilla::gmp::GMPProcessChild;
+
 using mozilla::ipc::TestShellParent;
 using mozilla::ipc::TestShellCommandParent;
 using mozilla::ipc::XPCShellEnvironment;
 
 using mozilla::startup::sChildProcessType;
 
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
@@ -283,22 +288,35 @@ SetTaskbarGroupId(const nsString& aId)
 
     if (hDLL)
         ::FreeLibrary(hDLL);
 }
 #endif
 
 nsresult
 XRE_InitChildProcess(int aArgc,
-                     char* aArgv[])
+                     char* aArgv[],
+                     GMPLoader* aGMPLoader)
 {
   NS_ENSURE_ARG_MIN(aArgc, 2);
   NS_ENSURE_ARG_POINTER(aArgv);
   NS_ENSURE_ARG_POINTER(aArgv[0]);
 
+#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_GONK)
+  // On non-Fennec Gecko, the GMPLoader code resides in plugin-container,
+  // and we must forward it through to the GMP code here.
+  GMPProcessChild::SetGMPLoader(aGMPLoader);
+#else
+  // On Fennec, the GMPLoader's code resides inside XUL (because for the time
+  // being GMPLoader relies upon NSPR, which we can't use in plugin-container
+  // on Android), so we create it here inside XUL and pass it to the GMP code.
+  nsAutoPtr<GMPLoader> loader(CreateGMPLoader(nullptr));
+  GMPProcessChild::SetGMPLoader(loader);
+#endif
+
 #if defined(XP_WIN)
   // From the --attach-console support in nsNativeAppSupportWin.cpp, but
   // here we are a content child process, so we always attempt to attach
   // to the parent's (ie, the browser's) console.
   // Try to attach console to the parent process.
   // It will succeed when the parent process is a command line,
   // so that stdio will be displayed in it.
   if (AttachConsole(ATTACH_PARENT_PROCESS)) {
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -388,19 +388,26 @@ XRE_API(bool,
         XRE_TakeMinidumpForChild, (uint32_t aChildPid, nsIFile** aDump,
                                    uint32_t* aSequence))
 
 // Used in child processes.
 XRE_API(bool,
         XRE_SetRemoteExceptionHandler, (const char* aPipe))
 #endif
 
+namespace mozilla {
+namespace gmp {
+class GMPLoader;
+} // namespace gmp
+} // namepsace mozilla
+
 XRE_API(nsresult,
         XRE_InitChildProcess, (int aArgc,
-                               char* aArgv[]))
+                               char* aArgv[],
+                               mozilla::gmp::GMPLoader* aGMPLoader))
 
 XRE_API(GeckoProcessType,
         XRE_GetProcessType, ())
 
 typedef void (*MainFunction)(void* aData);
 
 XRE_API(nsresult,
         XRE_InitParentProcess, (int aArgc,