Bug 1422394: Add a mechanism to report dll loads to an observer; r=jimm
authorAaron Klotz <aklotz@mozilla.com>
Mon, 04 Dec 2017 18:08:17 -0700
changeset 708652 d9ec1dda6c57b28b460a3e5bc35451519854593b
parent 708651 4bedf3a1a515e04d74816267ad94c81fa5fd1f02
child 708685 99a3b09ac1898eb1db05430e2876d6643ba0d4a8
child 708850 1a9cbc25dd9dfe03dc8face38c2b7e1b597b925e
push id92403
push userVYV03354@nifty.ne.jp
push dateWed, 06 Dec 2017 22:18:41 +0000
reviewersjimm
bugs1422394
milestone59.0a1
Bug 1422394: Add a mechanism to report dll loads to an observer; r=jimm MozReview-Commit-ID: 1ocag6jTBVV
mozglue/build/WindowsDllBlocklist.cpp
mozglue/build/WindowsDllBlocklist.h
mozglue/build/WindowsDllServices.h
mozglue/build/moz.build
toolkit/xre/nsAppRunner.cpp
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -28,16 +28,17 @@
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/StackWalk_windows.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsWindowsHelpers.h"
 #include "WindowsDllBlocklist.h"
 #include "mozilla/AutoProfilerLabel.h"
+#include "mozilla/WindowsDllServices.h"
 
 using namespace mozilla;
 
 #define ALL_VERSIONS   ((unsigned long long)-1LL)
 
 // DLLs sometimes ship without a version number, particularly early
 // releases. Blocking "version <= 0" has the effect of blocking unversioned
 // DLLs (since the call to get version info fails), but not blocking
@@ -925,8 +926,145 @@ DllBlocklist_WriteNotes(HANDLE file)
 
 MFBT_API bool
 DllBlocklist_CheckStatus()
 {
   if (sBlocklistInitFailed || sUser32BeforeBlocklist)
     return false;
   return true;
 }
+
+// ============================================================================
+// This section is for DLL Services
+// ============================================================================
+
+
+static SRWLOCK gDllServicesLock = SRWLOCK_INIT;
+static mozilla::detail::DllServicesBase* gDllServices;
+
+class MOZ_RAII AutoSharedLock final
+{
+public:
+  explicit AutoSharedLock(SRWLOCK& aLock)
+    : mLock(aLock)
+  {
+    ::AcquireSRWLockShared(&aLock);
+  }
+
+  ~AutoSharedLock()
+  {
+    ::ReleaseSRWLockShared(&mLock);
+  }
+
+  AutoSharedLock(const AutoSharedLock&) = delete;
+  AutoSharedLock(AutoSharedLock&&) = delete;
+  AutoSharedLock& operator=(const AutoSharedLock&) = delete;
+  AutoSharedLock& operator=(AutoSharedLock&&) = delete;
+
+private:
+  SRWLOCK& mLock;
+};
+
+class MOZ_RAII AutoExclusiveLock final
+{
+public:
+  explicit AutoExclusiveLock(SRWLOCK& aLock)
+    : mLock(aLock)
+  {
+    ::AcquireSRWLockExclusive(&aLock);
+  }
+
+  ~AutoExclusiveLock()
+  {
+    ::ReleaseSRWLockExclusive(&mLock);
+  }
+
+  AutoExclusiveLock(const AutoExclusiveLock&) = delete;
+  AutoExclusiveLock(AutoExclusiveLock&&) = delete;
+  AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete;
+  AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete;
+
+private:
+  SRWLOCK& mLock;
+};
+
+// These types are documented on MSDN but not provided in any SDK headers
+
+enum DllNotificationReason
+{
+  LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
+  LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2
+};
+
+typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA {
+  ULONG Flags;                    //Reserved.
+  PCUNICODE_STRING FullDllName;   //The full path name of the DLL module.
+  PCUNICODE_STRING BaseDllName;   //The base file name of the DLL module.
+  PVOID DllBase;                  //A pointer to the base address for the DLL in memory.
+  ULONG SizeOfImage;              //The size of the DLL image, in bytes.
+} LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA;
+
+typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA {
+  ULONG Flags;                    //Reserved.
+  PCUNICODE_STRING FullDllName;   //The full path name of the DLL module.
+  PCUNICODE_STRING BaseDllName;   //The base file name of the DLL module.
+  PVOID DllBase;                  //A pointer to the base address for the DLL in memory.
+  ULONG SizeOfImage;              //The size of the DLL image, in bytes.
+} LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA;
+
+typedef union _LDR_DLL_NOTIFICATION_DATA {
+  LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
+  LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
+} LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA;
+
+typedef const LDR_DLL_NOTIFICATION_DATA* PCLDR_DLL_NOTIFICATION_DATA;
+
+typedef VOID (CALLBACK* PLDR_DLL_NOTIFICATION_FUNCTION)(
+          ULONG aReason,
+          PCLDR_DLL_NOTIFICATION_DATA aNotificationData,
+          PVOID aContext);
+
+NTSTATUS NTAPI
+LdrRegisterDllNotification(ULONG aFlags,
+                           PLDR_DLL_NOTIFICATION_FUNCTION aCallback,
+                           PVOID aContext, PVOID* aCookie);
+
+static PVOID gNotificationCookie;
+
+static VOID CALLBACK
+DllLoadNotification(ULONG aReason, PCLDR_DLL_NOTIFICATION_DATA aNotificationData,
+                    PVOID aContext)
+{
+  if (aReason != LDR_DLL_NOTIFICATION_REASON_LOADED) {
+    // We don't care about unloads
+    return;
+  }
+
+  AutoSharedLock lock(gDllServicesLock);
+  if (!gDllServices) {
+    return;
+  }
+
+  PCUNICODE_STRING fullDllName = aNotificationData->Loaded.FullDllName;
+  gDllServices->DispatchDllLoadNotification(fullDllName);
+}
+
+MFBT_API void
+DllBlocklist_SetDllServices(mozilla::detail::DllServicesBase* aSvc)
+{
+  AutoExclusiveLock lock(gDllServicesLock);
+
+  if (aSvc && !gNotificationCookie) {
+    auto pLdrRegisterDllNotification =
+      reinterpret_cast<decltype(&::LdrRegisterDllNotification)>(
+        ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"),
+                         "LdrRegisterDllNotification"));
+
+    MOZ_DIAGNOSTIC_ASSERT(pLdrRegisterDllNotification);
+
+    NTSTATUS ntStatus = pLdrRegisterDllNotification(0, &DllLoadNotification,
+                                                    nullptr, &gNotificationCookie);
+    MOZ_DIAGNOSTIC_ASSERT(NT_SUCCESS(ntStatus));
+  }
+
+  gDllServices = aSvc;
+}
+
--- a/mozglue/build/WindowsDllBlocklist.h
+++ b/mozglue/build/WindowsDllBlocklist.h
@@ -19,10 +19,19 @@ enum DllBlocklistInitFlags
   eDllBlocklistInitFlagDefault = 0,
   eDllBlocklistInitFlagIsChildProcess = 1
 };
 
 MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags = eDllBlocklistInitFlagDefault);
 MFBT_API void DllBlocklist_WriteNotes(HANDLE file);
 MFBT_API bool DllBlocklist_CheckStatus();
 
+// Forward declaration
+namespace mozilla {
+namespace detail {
+class DllServicesBase;
+} // namespace detail
+} // namespace mozilla
+
+MFBT_API void DllBlocklist_SetDllServices(mozilla::detail::DllServicesBase* aSvc);
+
 #endif // defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
 #endif // mozilla_windowsdllblocklist_h
new file mode 100644
--- /dev/null
+++ b/mozglue/build/WindowsDllServices.h
@@ -0,0 +1,91 @@
+/* -*- 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/. */
+
+#ifndef mozilla_WindowsDllServices_h
+#define mozilla_WindowsDllServices_h
+
+#include "mozilla/WindowsDllBlocklist.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+
+#include "mozilla/SystemGroup.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+// For PCUNICODE_STRING
+#include <winternl.h>
+
+namespace mozilla {
+namespace detail {
+
+class DllServicesBase
+{
+public:
+  /**
+   * WARNING: This method is called from within an unsafe context that holds
+   *          multiple locks inside the Windows loader. The only thing that
+   *          this function should be used for is dispatching the event to our
+   *          event loop so that it may be handled in a safe context.
+   */
+  virtual void DispatchDllLoadNotification(PCUNICODE_STRING aDllName) = 0;
+
+  void Disable()
+  {
+    DllBlocklist_SetDllServices(nullptr);
+  }
+
+  DllServicesBase(const DllServicesBase&) = delete;
+  DllServicesBase(DllServicesBase&&) = delete;
+  DllServicesBase& operator=(const DllServicesBase&) = delete;
+  DllServicesBase& operator=(DllServicesBase&&) = delete;
+
+protected:
+  DllServicesBase() = default;
+  virtual ~DllServicesBase() = default;
+
+  void Enable()
+  {
+    DllBlocklist_SetDllServices(this);
+  }
+};
+
+} // namespace detail
+
+#if defined(MOZILLA_INTERNAL_API)
+
+class DllServices : public detail::DllServicesBase
+{
+public:
+  virtual void DispatchDllLoadNotification(PCUNICODE_STRING aDllName) override final
+  {
+    nsDependentString strDllName(aDllName->Buffer,
+                                 aDllName->Length / sizeof(wchar_t));
+
+    nsCOMPtr<nsIRunnable> runnable(
+      NewRunnableMethod<bool, nsString>("DllServices::NotifyDllLoad",
+                                        this, &DllServices::NotifyDllLoad,
+                                        NS_IsMainThread(), strDllName));
+
+    SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
+  }
+
+  NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(DllServices)
+
+protected:
+  DllServices() = default;
+  ~DllServices() = default;
+
+  virtual void NotifyDllLoad(const bool aIsMainThread, const nsString& aDllName) = 0;
+};
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsDllServices_h
--- a/mozglue/build/moz.build
+++ b/mozglue/build/moz.build
@@ -52,16 +52,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT']:
             'version',
         ]
 
     EXPORTS.mozilla += [
         'arm.h',
         'mips.h',
         'SSE.h',
         'WindowsDllBlocklist.h',
+        'WindowsDllServices.h',
     ]
 
     if CONFIG['CPU_ARCH'].startswith('x86'):
         SOURCES += [
             'SSE.cpp',
         ]
 
     if CONFIG['CPU_ARCH'] == 'arm':
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -154,16 +154,17 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <pwd.h>
 #endif
 
 #ifdef XP_WIN
 #include <process.h>
 #include <shlobj.h>
+#include "mozilla/WindowsDllServices.h"
 #include "nsThreadUtils.h"
 #include <comdef.h>
 #include <wbemidl.h>
 #include "WinUtils.h"
 #endif
 
 #ifdef XP_MACOSX
 #include "nsILocalFileMac.h"
@@ -1649,16 +1650,50 @@ ScopedXPCOMStartup::CreateAppSupport(nsI
   if (!gNativeAppSupport)
     return NS_ERROR_NOT_INITIALIZED;
 
   return gNativeAppSupport->QueryInterface(aIID, aResult);
 }
 
 nsINativeAppSupport* ScopedXPCOMStartup::gNativeAppSupport;
 
+#if defined(XP_WIN)
+
+class DllNotifications : public mozilla::DllServices
+{
+public:
+  DllNotifications()
+  {
+    Enable();
+  }
+
+private:
+  ~DllNotifications() = default;
+
+  void NotifyDllLoad(const bool aIsMainThread, const nsString& aDllName) override;
+};
+
+void
+DllNotifications::NotifyDllLoad(const bool aIsMainThread,
+                                const nsString& aDllName)
+{
+  const char* topic;
+
+  if (aIsMainThread) {
+    topic = "dll-loaded-main-thread";
+  } else {
+    topic = "dll-loaded-non-main-thread";
+  }
+
+  nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
+  obsServ->NotifyObservers(nullptr, topic, aDllName.get());
+}
+
+#endif // defined(XP_WIN)
+
 static void DumpArbitraryHelp()
 {
   nsresult rv;
 
   ScopedLogging log;
 
   {
     ScopedXPCOMStartup xpcom;
@@ -4297,16 +4332,23 @@ void AddSandboxAnnotations()
  * the calling of appStartup->Run().
  */
 nsresult
 XREMain::XRE_mainRun()
 {
   nsresult rv = NS_OK;
   NS_ASSERTION(mScopedXPCOM, "Scoped xpcom not initialized.");
 
+#if defined(XP_WIN)
+  RefPtr<DllNotifications> dllNotifications(new DllNotifications());
+  auto dllNotificationsDisable = MakeScopeExit([&dllNotifications]() {
+    dllNotifications->Disable();
+  });
+#endif // defined(XP_WIN)
+
 #ifdef NS_FUNCTION_TIMER
   // initialize some common services, so we don't pay the cost for these at odd times later on;
   // SetWindowCreator -> ChromeRegistry -> IOService -> SocketTransportService -> (nspr wspm init), Prefs
   {
     nsCOMPtr<nsISupports> comp;
 
     comp = do_GetService("@mozilla.org/preferences-service;1");