Bug 927944 - Annotate crash reports with a list of DLLs that were blocked via the windows blocklist. Also fix the blocklist to recognize forward-slash as a path delimiter, r=ehsan
authorBenjamin Smedberg <benjamin@smedbergs.us>
Fri, 18 Oct 2013 14:24:50 -0400
changeset 166314 d04bbdfff591d7517f8f8cc0f1f51d806a21d4b6
parent 166313 74252b21fdacc74c1cd7c78fca225dba8cbdfb20
child 166315 e3d6e17ad7a3d8687bd05cc041ef1e80ee154478
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs927944
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 927944 - Annotate crash reports with a list of DLLs that were blocked via the windows blocklist. Also fix the blocklist to recognize forward-slash as a path delimiter, r=ehsan
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/crashreporter/nsExceptionHandler.h
toolkit/xre/nsWindowsDllBlocklist.cpp
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -217,16 +217,21 @@ static const int kAvailablePageFileParam
 static const char kAvailablePhysicalMemoryParameter[] = "AvailablePhysicalMemory=";
 static const int kAvailablePhysicalMemoryParameterLen =
   sizeof(kAvailablePhysicalMemoryParameter)-1;
 
 static const char kIsGarbageCollectingParameter[] = "IsGarbageCollecting=";
 static const int kIsGarbageCollectingParameterLen =
   sizeof(kIsGarbageCollectingParameter)-1;
 
+#ifdef XP_WIN
+static const char kBlockedDllsParameter[] = "BlockedDllList=";
+static const int kBlockedDllsParameterLen = sizeof(kBlockedDllsParameter) - 1;
+#endif
+
 // this holds additional data sent via the API
 static Mutex* crashReporterAPILock;
 static Mutex* notesFieldLock;
 static AnnotationTable* crashReporterAPIData_Hash;
 static nsCString* crashReporterAPIData = nullptr;
 static nsCString* notesField = nullptr;
 static bool isGarbageCollecting;
 
@@ -535,16 +540,19 @@ bool MinidumpCallback(
         WriteFile(hFile, "\n", 1, &nBytes, nullptr);
       }
       if (isGarbageCollecting) {
         WriteFile(hFile, kIsGarbageCollectingParameter, kIsGarbageCollectingParameterLen,
                   &nBytes, nullptr);
         WriteFile(hFile, isGarbageCollecting ? "1" : "0", 1, &nBytes, nullptr);
         WriteFile(hFile, "\n", 1, &nBytes, nullptr);
       }
+      WriteFile(hFile, kBlockedDllsParameter, kBlockedDllsParameterLen, &nBytes, nullptr);
+      WriteBlockedDlls(hFile);
+      WriteFile(hFile, "\n", 1, &nBytes, nullptr);
 
       // Try to get some information about memory.
       MEMORYSTATUSEX statex;
       statex.dwLength = sizeof(statex);
       if (GlobalMemoryStatusEx(&statex)) {
         char buffer[128];
         int bufferLen;
 
--- a/toolkit/crashreporter/nsExceptionHandler.h
+++ b/toolkit/crashreporter/nsExceptionHandler.h
@@ -39,16 +39,21 @@ nsresult SetMinidumpPath(const nsAString
 // AnnotateCrashReport and AppendAppNotesToCrashReport may be called from any
 // thread in a chrome process, but may only be called from the main thread in
 // a content process.
 nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data);
 nsresult AppendAppNotesToCrashReport(const nsACString& data);
 
 nsresult SetGarbageCollecting(bool collecting);
 
+#ifdef XP_WIN
+// Implemented by the blocklist, this method writes the blocklist annotation
+void WriteBlockedDlls(HANDLE file);
+#endif
+
 nsresult SetRestartArgs(int argc, char** argv);
 nsresult SetupExtraData(nsIFile* aAppDataDirectory,
                         const nsACString& aBuildID);
 bool GetLastRunCrashID(nsAString& id);
 
 // Registers an additional memory region to be included in the minidump
 nsresult RegisterAppMemory(void* ptr, size_t length);
 nsresult UnregisterAppMemory(void* ptr);
--- a/toolkit/xre/nsWindowsDllBlocklist.cpp
+++ b/toolkit/xre/nsWindowsDllBlocklist.cpp
@@ -18,17 +18,17 @@
 
 #include "prlog.h"
 
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/WindowsVersion.h"
 
 using namespace mozilla;
 
-#if defined(MOZ_CRASHREPORTER) && !defined(NO_BLOCKLIST_CRASHREPORTER)
+#ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 #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
@@ -219,16 +219,20 @@ CheckASLR(const wchar_t* path)
       ::CloseHandle(map);
     }
     ::CloseHandle(file);
   }
 
   return retval;
 }
 
+// This lock protects both the reentrancy sentinel and the crash reporter
+// data structures.
+static CRITICAL_SECTION sLock;
+
 /**
  * Some versions of Windows call LoadLibraryEx to get the version information
  * for a DLL, which causes our patched LdrLoadDll implementation to re-enter
  * itself and cause infinite recursion and a stack-exhaustion crash. We protect
  * against reentrancy by allowing recursive loads of the same DLL.
  *
  * Note that we don't use __declspec(thread) because that doesn't work in DLLs
  * loaded via LoadLibrary and there can be a limited number of TLS slots, so
@@ -265,26 +269,108 @@ public:
     
   static void InitializeStatics()
   {
     InitializeCriticalSection(&sLock);
     sThreadMap = new std::map<DWORD, const char*>;
   }
 
 private:
-  static CRITICAL_SECTION sLock;
   static std::map<DWORD, const char*>* sThreadMap;
 
   const char* mPreviousDllName;
   bool mReentered;
 };
 
-CRITICAL_SECTION ReentrancySentinel::sLock;
 std::map<DWORD, const char*>* ReentrancySentinel::sThreadMap;
 
+/**
+ * This is a linked list of DLLs that have been blocked. It doesn't use
+ * mozilla::LinkedList because this is an append-only list and doesn't need
+ * to be doubly linked.
+ */
+class DllBlockSet
+{
+public:
+  static void Add(const char* name, unsigned long long version);
+
+  // Write the list of blocked DLLs to a file HANDLE. This method is run after
+  // a crash occurs and must therefore not use the heap, etc.
+  static void Write(HANDLE file);
+
+private:
+  DllBlockSet(const char* name, unsigned long long version)
+    : mName(name)
+    , mVersion(version)
+    , mNext(nullptr)
+  {
+  }
+
+  const char* mName; // points into the sWindowsDllBlocklist string
+  unsigned long long mVersion;
+  DllBlockSet* mNext;
+
+  static DllBlockSet* gFirst;
+};
+
+DllBlockSet* DllBlockSet::gFirst;
+
+void
+DllBlockSet::Add(const char* name, unsigned long long version)
+{
+  EnterCriticalSection(&sLock);
+  for (DllBlockSet* b = gFirst; b; b = b->mNext) {
+    if (0 == strcmp(b->mName, name) && b->mVersion == version) {
+      LeaveCriticalSection(&sLock);
+      return;
+    }
+  }
+  // Not already present
+  DllBlockSet* n = new DllBlockSet(name, version);
+  n->mNext = gFirst;
+  gFirst = n;
+  LeaveCriticalSection(&sLock);
+}
+
+void
+DllBlockSet::Write(HANDLE file)
+{
+  EnterCriticalSection(&sLock);
+  DWORD nBytes;
+
+  // Because this method is called after a crash occurs, and uses heap memory,
+  // protect this entire block with a structured exception handler.
+  __try {
+    for (DllBlockSet* b = gFirst; b; b = b->mNext) {
+      // write name[,v.v.v.v];
+      WriteFile(file, b->mName, strlen(b->mName), &nBytes, nullptr);
+      if (b->mVersion != -1) {
+        WriteFile(file, ",", 1, &nBytes, nullptr);
+        uint16_t parts[4];
+        parts[0] = b->mVersion >> 48;
+        parts[1] = (b->mVersion >> 32) & 0xFFFF;
+        parts[2] = (b->mVersion >> 16) & 0xFFFF;
+        parts[3] = b->mVersion & 0xFFFF;
+        for (int p = 0; p < 4; ++p) {
+          char buf[32];
+          ltoa(parts[p], buf, 10);
+          WriteFile(file, buf, strlen(buf), &nBytes, nullptr);
+          if (p != 3) {
+            WriteFile(file, ".", 1, &nBytes, nullptr);
+          }
+        }
+      }
+      WriteFile(file, ";", 1, &nBytes, nullptr);
+    }
+  }
+  __except (EXCEPTION_EXECUTE_HANDLER) { }
+
+  LeaveCriticalSection(&sLock);
+}
+
 static
 wchar_t* getFullPath (PWCHAR filePath, wchar_t* fname)
 {
   // In Windows 8, the first parameter seems to be used for more than just the
   // path name.  For example, its numerical value can be 1.  Passing a non-valid
   // pointer to SearchPathW will cause a crash, so we need to check to see if we
   // are handed a valid pointer, and otherwise just pass nullptr to SearchPathW.
   PWCHAR sanitizedFilePath = (intptr_t(filePath) < 1024) ? nullptr : filePath;
@@ -303,16 +389,27 @@ wchar_t* getFullPath (PWCHAR filePath, w
   }
 
   // now actually grab it
   SearchPathW(sanitizedFilePath, fname, L".dll", pathlen + 1, full_fname,
               nullptr);
   return full_fname;
 }
 
+// No builtin function to find the last character matching a set
+static wchar_t* lastslash(wchar_t* s, int len)
+{
+  for (wchar_t* c = s + len - 1; c >= s; --c) {
+    if (*c == L'\\' || *c == L'/') {
+      return c;
+    }
+  }
+  return nullptr;
+}
+
 static NTSTATUS NTAPI
 patched_LdrLoadDll (PWCHAR filePath, PULONG flags, PUNICODE_STRING moduleFileName, PHANDLE handle)
 {
   // We have UCS2 (UTF16?), we want ASCII, but we also just want the filename portion
 #define DLLNAME_MAX 128
   char dllName[DLLNAME_MAX+1];
   wchar_t *dll_part;
   DllBlockInfo *info;
@@ -329,17 +426,17 @@ patched_LdrLoadDll (PWCHAR filePath, PUL
       fname[len] != 0)
   {
 #ifdef DEBUG
     printf_stderr("LdrLoadDll: non-null terminated string found!\n");
 #endif
     goto continue_loading;
   }
 
-  dll_part = wcsrchr(fname, L'\\');
+  dll_part = lastslash(fname, len);
   if (dll_part) {
     dll_part = dll_part + 1;
     len -= dll_part - fname;
   } else {
     dll_part = fname;
   }
 
 #ifdef DEBUG_very_verbose
@@ -395,16 +492,18 @@ patched_LdrLoadDll (PWCHAR filePath, PUL
     printf_stderr("LdrLoadDll: info->name: '%s'\n", info->name);
 #endif
 
     if ((info->flags == DllBlockInfo::BLOCK_WIN8PLUS_ONLY) &&
         !IsWin8OrLater()) {
       goto continue_loading;
     }
 
+    unsigned long long fVersion = ALL_VERSIONS;
+
     if (info->maxVersion != ALL_VERSIONS) {
       ReentrancySentinel sentinel(dllName);
       if (sentinel.BailOut()) {
         goto continue_loading;
       }
 
       full_fname = getFullPath(filePath, fname);
       if (!full_fname) {
@@ -421,30 +520,31 @@ patched_LdrLoadDll (PWCHAR filePath, PUL
       if (infoSize != 0) {
         nsAutoArrayPtr<unsigned char> infoData(new unsigned char[infoSize]);
         VS_FIXEDFILEINFO *vInfo;
         UINT vInfoLen;
 
         if (GetFileVersionInfoW(full_fname, 0, infoSize, infoData) &&
             VerQueryValueW(infoData, L"\\", (LPVOID*) &vInfo, &vInfoLen))
         {
-          unsigned long long fVersion =
+          fVersion =
             ((unsigned long long)vInfo->dwFileVersionMS) << 32 |
             ((unsigned long long)vInfo->dwFileVersionLS);
 
           // finally do the version check, and if it's greater than our block
           // version, keep loading
           if (fVersion > info->maxVersion)
             load_ok = true;
         }
       }
     }
 
     if (!load_ok) {
       printf_stderr("LdrLoadDll: Blocking load of '%s' -- see http://www.mozilla.com/en-US/blocklist/\n", dllName);
+      DllBlockSet::Add(info->name, fVersion);
       return STATUS_DLL_NOT_FOUND;
     }
   }
 
 continue_loading:
 #ifdef DEBUG_very_verbose
   printf_stderr("LdrLoadDll: continuing load... ('%S')\n", moduleFileName->Buffer);
 #endif
@@ -480,14 +580,22 @@ XRE_SetupDllBlocklist()
 
   bool ok = NtDllIntercept.AddHook("LdrLoadDll", reinterpret_cast<intptr_t>(patched_LdrLoadDll), (void**) &stub_LdrLoadDll);
 
 #ifdef DEBUG
   if (!ok)
     printf_stderr ("LdrLoadDll hook failed, no dll blocklisting active\n");
 #endif
 
-#if defined(MOZ_CRASHREPORTER) && !defined(NO_BLOCKLIST_CRASHREPORTER)
+#ifdef MOZ_CRASHREPORTER
   if (!ok) {
     CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DllBlockList Failed\n"));
   }
 #endif
 }
+
+#ifdef MOZ_CRASHREPORTER
+void
+CrashReporter::WriteBlockedDlls(HANDLE file)
+{
+  DllBlockSet::Write(file);
+}
+#endif