Bug 1322611 - After a crash compute the SHA256 hash of a minidump and attach it to the crash ping; r?Ted, snorp draft
authorGabriele Svelto <gsvelto@mozilla.com>
Thu, 16 Feb 2017 07:36:57 +0100
changeset 496577 6cc29cd9353f20f57000d5d053e805b40a700e7a
parent 493721 8d026c60151005ad942e3d4389318fe28a0c8c54
child 548649 954b263e215f768d5ea37c786d658d96e5d491c3
push id48637
push usergsvelto@mozilla.com
push dateFri, 10 Mar 2017 10:33:22 +0000
reviewersTed, snorp
bugs1322611
milestone54.0a1
Bug 1322611 - After a crash compute the SHA256 hash of a minidump and attach it to the crash ping; r?Ted, snorp This patch changes the crashreporter client code as well as the crash service code to compute a SHA256 hash of a crash' minidump file and add it to the crash ping. The crash service code computes the hash on the fly before handing over the crash to the crash manager; the crash manager will then add it to the crash ping. The crashreporter client on the other hand sends the hash via the ping it generates but it also adds it to the event file so that the crash manager can pick it up and send it along with its own crash ping. On Fennec the crashreporter activity takes care of computing the hash. SHA256 hash computation uses nsICryptoHash in the crash service, the java.security.MessageDigest class in Fennec, the bundled NSS library in the crashreporter when running on Windows and Mac and the system-provided NSS library under Linux. The latter is required because the crashreporter client uses the system curl library which is linked to NSS and which would thus clash with the bundled one if used together. This patch introduces two new methods for the nsICrashService interface: |getMinidumpForID()| and |getExtraFileForID()|, these reliably retrieve the .dmp and .extra files associated with a crash and ensure the files exist before returning. These new methods are used in the CrashService for processing and will become the only way to reliably retrieve those files from a crash ID. MozReview-Commit-ID: 8BKvqj6URcO
dom/plugins/ipc/PluginModuleParent.cpp
mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
toolkit/components/crashes/CrashManager.jsm
toolkit/components/crashes/CrashService.js
toolkit/components/crashes/nsICrashService.idl
toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
toolkit/components/telemetry/docs/data/crash-ping.rst
toolkit/crashreporter/client/crashreporter.cpp
toolkit/crashreporter/client/crashreporter.h
toolkit/crashreporter/client/crashreporter_gtk_common.cpp
toolkit/crashreporter/client/crashreporter_osx.mm
toolkit/crashreporter/client/crashreporter_win.cpp
toolkit/crashreporter/client/moz.build
toolkit/crashreporter/client/ping.cpp
toolkit/crashreporter/docs/index.rst
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/xre/nsAppRunner.cpp
xpcom/system/nsICrashReporter.idl
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -1221,23 +1221,19 @@ PluginModuleChromeParent::TakeFullMinidu
     }
 
     bool reportsReady = false;
 
     // Check to see if we already have a browser dump id - with e10s plugin
     // hangs we take this earlier (see ProcessHangMonitor) from a background
     // thread. We do this before we message the main thread about the hang
     // since the posted message will trash our browser stack state.
-    bool exists;
     nsCOMPtr<nsIFile> browserDumpFile;
-    if (!aBrowserDumpId.IsEmpty() &&
-        CrashReporter::GetMinidumpForID(aBrowserDumpId, getter_AddRefs(browserDumpFile)) &&
-        browserDumpFile &&
-        NS_SUCCEEDED(browserDumpFile->Exists(&exists)) && exists)
-    {
+    if (CrashReporter::GetMinidumpForID(aBrowserDumpId,
+                                        getter_AddRefs(browserDumpFile))) {
         // We have a single browser report, generate a new plugin process parent
         // report and pair it up with the browser report handed in.
         reportsReady = mCrashReporter->GenerateMinidumpAndPair(
           this,
           browserDumpFile,
           NS_LITERAL_CSTRING("browser"));
 
         if (!reportsReady) {
@@ -1259,18 +1255,17 @@ PluginModuleChromeParent::TakeFullMinidu
 
     if (reportsReady) {
         aDumpId = mCrashReporter->MinidumpID();
         PLUGIN_LOG_DEBUG(
                 ("generated paired browser/plugin minidumps: %s)",
                  NS_ConvertUTF16toUTF8(aDumpId).get()));
         nsAutoCString additionalDumps("browser");
         nsCOMPtr<nsIFile> pluginDumpFile;
-        if (GetMinidumpForID(aDumpId, getter_AddRefs(pluginDumpFile)) &&
-            pluginDumpFile) {
+        if (GetMinidumpForID(aDumpId, getter_AddRefs(pluginDumpFile))) {
 #ifdef MOZ_CRASHREPORTER_INJECTOR
             // If we have handles to the flash sandbox processes on Windows,
             // include those minidumps as well.
             if (CreatePluginMinidump(mFlashProcess1, 0, pluginDumpFile,
                                      NS_LITERAL_CSTRING("flash1"))) {
                 additionalDumps.AppendLiteral(",flash1");
             }
             if (CreatePluginMinidump(mFlashProcess2, 0, pluginDumpFile,
--- a/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
@@ -7,23 +7,25 @@ package org.mozilla.gecko;
 
 import java.util.HashMap;
 import java.util.Map;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.FileReader;
+import java.io.FileWriter;
 import java.io.InputStreamReader;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
 import java.util.zip.GZIPOutputStream;
 
 import org.mozilla.gecko.AppConstants.Versions;
 
 import android.annotation.SuppressLint;
 import android.app.AlertDialog;
 import android.app.ProgressDialog;
 import android.content.DialogInterface;
@@ -133,16 +135,17 @@ public class CrashReporter extends AppCo
         pendingDir.mkdirs();
         mPendingMinidumpFile = new File(pendingDir, passedMinidumpFile.getName());
         moveFile(passedMinidumpFile, mPendingMinidumpFile);
 
         File extrasFile = new File(passedMinidumpPath.replaceAll("\\.dmp", ".extra"));
         mPendingExtrasFile = new File(pendingDir, extrasFile.getName());
         moveFile(extrasFile, mPendingExtrasFile);
 
+        computeMinidumpHash(mPendingExtrasFile, mPendingMinidumpFile);
         mExtrasStringMap = new HashMap<String, String>();
         readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
 
         // Notify GeckoApp that we've crashed, so it can react appropriately during the next start.
         try {
             File crashFlag = new File(GeckoProfileDirectories.getMozillaDirectory(this), "CRASHED");
             crashFlag.createNewFile();
         } catch (GeckoProfileDirectories.NoMozillaDirectoryException | IOException e) {
@@ -262,16 +265,56 @@ public class CrashReporter extends AppCo
         backgroundSendReport();
     }
 
     public void onRestartClick(View v) {  // bound via crash_reporter.xml
         doRestart();
         backgroundSendReport();
     }
 
+    private void computeMinidumpHash(File extraFile, File minidump) {
+        try {
+            FileInputStream stream = new FileInputStream(minidump);
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+
+            try {
+                byte[] buffer = new byte[4096];
+                int readBytes;
+
+                while ((readBytes = stream.read(buffer)) != -1) {
+                    md.update(buffer, 0, readBytes);
+                }
+            } finally {
+              stream.close();
+            }
+
+            byte[] digest = md.digest();
+            StringBuilder hash = new StringBuilder(84);
+
+            hash.append("MinidumpSha256Hash=");
+
+            for (int i = 0; i < digest.length; i++) {
+              hash.append(Integer.toHexString((digest[i] & 0xf0) >> 4));
+              hash.append(Integer.toHexString(digest[i] & 0x0f));
+            }
+
+            hash.append('\n');
+
+            FileWriter writer = new FileWriter(extraFile, /* append */ true);
+
+            try {
+                writer.write(hash.toString());
+            } finally {
+                writer.close();
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "exception while computing the minidump hash: ", e);
+        }
+    }
+
     private boolean readStringsFromFile(String filePath, Map<String, String> stringMap) {
         try {
             BufferedReader reader = new BufferedReader(new FileReader(filePath));
             return readStringsFromReader(reader, stringMap);
         } catch (Exception e) {
             Log.e(LOGTAG, "exception while reading strings: ", e);
             return false;
         }
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -35,40 +35,54 @@ const MILLISECONDS_IN_DAY = 24 * 60 * 60
 // Converts Date to days since UNIX epoch.
 // This was copied from /services/metrics.storage.jsm. The implementation
 // does not account for leap seconds.
 function dateToDays(date) {
   return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
 }
 
 /**
- * Parse the string stored in the specified field as JSON and then remove the
- * field from the object. The string might also be returned without parsing.
+ * Get a field from the specified object and remove it.
  *
  * @param obj {Object} The object holding the field
  * @param field {String} The name of the field to be parsed and removed
- * @param [parseAsJson=true] {Boolean} If true parse the field's contents as if
- *        it were JSON code, otherwise return the rew string.
  *
- * @returns {Object|String} the parsed object or the raw string
+ * @returns {String} the field contents as a string, null if none was found
  */
-function parseAndRemoveField(obj, field, parseAsJson = true) {
+function getAndRemoveField(obj, field) {
   let value = null;
 
   if (field in obj) {
-    if (!parseAsJson) {
-      // We split extra files on LF characters but Windows-generated ones might
-      // contain trailing CR characters so trim them here.
-      value = obj[field].trim();
-    } else {
-      try {
-        value = JSON.parse(obj[field]);
-      } catch (e) {
-        Cu.reportError(e);
-      }
+    // We split extra files on LF characters but Windows-generated ones might
+    // contain trailing CR characters so trim them here.
+    value = obj[field].trim();
+
+    delete obj[field];
+  }
+
+  return value;
+}
+
+/**
+ * Parse the string stored in the specified field as JSON and then remove the
+ * field from the object.
+ *
+ * @param obj {Object} The object holding the field
+ * @param field {String} The name of the field to be parsed and removed
+ *
+ * @returns {Object} the parsed object, null if none was found
+ */
+function parseAndRemoveField(obj, field) {
+  let value = null;
+
+  if (field in obj) {
+    try {
+      value = JSON.parse(obj[field]);
+    } catch (e) {
+      Cu.reportError(e);
     }
 
     delete obj[field];
   }
 
   return value;
 }
 
@@ -454,17 +468,17 @@ this.CrashManager.prototype = Object.fre
         this._crashPromises.delete(id);
         deferred.resolve();
       }
 
       // Send a telemetry ping for each content process crash
       if (processType === this.PROCESS_TYPE_CONTENT) {
         this._sendCrashPing(id, processType, date, metadata);
       }
-   }.bind(this));
+    }.bind(this));
 
     return promise;
   },
 
   /**
    * Returns a promise that is resolved only the crash with the specified id
    * has been fully recorded.
    *
@@ -632,32 +646,33 @@ this.CrashManager.prototype = Object.fre
   },
 
   _sendCrashPing(crashId, type, date, metadata = {}) {
     // If we have a saved environment, use it. Otherwise report
     // the current environment.
     let reportMeta = Cu.cloneInto(metadata, myScope);
     let crashEnvironment = parseAndRemoveField(reportMeta,
                                                "TelemetryEnvironment");
-    let sessionId = parseAndRemoveField(reportMeta, "TelemetrySessionId",
-                                        /* parseAsJson */ false);
+    let sessionId = getAndRemoveField(reportMeta, "TelemetrySessionId");
     let stackTraces = parseAndRemoveField(reportMeta, "StackTraces");
+    let minidumpSha256Hash = getAndRemoveField(reportMeta,
+                                               "MinidumpSha256Hash");
     // If CrashPingUUID is not present then Telemetry will generate a new UUID
-    let pingId = parseAndRemoveField(reportMeta, "CrashPingUUID",
-                                     /* parseAsJson */ false);
+    let pingId = getAndRemoveField(reportMeta, "CrashPingUUID");
 
     // Filter the remaining annotations to remove privacy-sensitive ones
     reportMeta = this._filterAnnotations(reportMeta);
 
     this._pingPromise = TelemetryController.submitExternalPing("crash",
       {
         version: 1,
         crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
         sessionId,
         crashId,
+        minidumpSha256Hash,
         processType: type,
         stackTraces,
         metadata: reportMeta,
         hasCrashEnvironment: (crashEnvironment !== null),
       },
       {
         addClientId: true,
         addEnvironment: true,
--- a/toolkit/components/crashes/CrashService.js
+++ b/toolkit/components/crashes/CrashService.js
@@ -10,41 +10,77 @@ Cu.import("resource://gre/modules/AsyncS
 Cu.import("resource://gre/modules/KeyValueParser.jsm");
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 
 /**
+ * Computes the SHA256 hash of the minidump file associated with a crash
+ *
+ * @param crashID {string} Crash ID. Likely a UUID.
+ *
+ * @returns {Promise} A promise that resolves to the hash value of the
+ *          minidump. If the hash could not be computed then null is returned
+ *          instead.
+ */
+function computeMinidumpHash(id) {
+  let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
+             .getService(Components.interfaces.nsICrashReporter);
+
+  return Task.spawn(function* () {
+    try {
+      let minidumpFile = cr.getMinidumpForID(id);
+      let minidumpData = yield OS.File.read(minidumpFile.path);
+      let hasher = Cc["@mozilla.org/security/hash;1"]
+                     .createInstance(Ci.nsICryptoHash);
+      hasher.init(hasher.SHA256);
+      hasher.update(minidumpData, minidumpData.length);
+
+      let hashBin = hasher.finish(false);
+      let hash = "";
+
+      for (let i = 0; i < hashBin.length; i++) {
+        // Every character in the hash string contains a byte of the hash data
+        hash += ("0" + hashBin.charCodeAt(i).toString(16)).slice(-2);
+      }
+
+      return hash;
+    } catch (e) {
+      Cu.reportError(e);
+      return null;
+    }
+  });
+}
+
+/**
  * Process the .extra file associated with the crash id and return the
  * annotations it contains in an object.
  *
  * @param crashID {string} Crash ID. Likely a UUID.
  *
  * @return {Promise} A promise that resolves to an object holding the crash
  *         annotations, this object may be empty if no annotations were found.
  */
 function processExtraFile(id) {
   let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
              .getService(Components.interfaces.nsICrashReporter);
-  let extraPath = OS.Path.join(cr.minidumpPath.path, id + ".extra");
 
   return Task.spawn(function* () {
     try {
+      let extraFile = cr.getExtraFileForID(id);
       let decoder = new TextDecoder();
-      let extraFile = yield OS.File.read(extraPath);
-      let extraData = decoder.decode(extraFile);
+      let extraData = yield OS.File.read(extraFile.path);
 
-      return parseKeyValuePairs(extraData);
+      return parseKeyValuePairs(decoder.decode(extraData));
     } catch (e) {
       Cu.reportError(e);
+      return {};
     }
-
-    return {};
   });
 }
 
 /**
  * This component makes crash data available throughout the application.
  *
  * It is a service because some background activity will eventually occur.
  */
@@ -84,23 +120,33 @@ CrashService.prototype = Object.freeze({
       break;
     case Ci.nsICrashService.CRASH_TYPE_HANG:
       crashType = Services.crashmanager.CRASH_TYPE_HANG;
       break;
     default:
       throw new Error("Unrecognized CRASH_TYPE: " + crashType);
     }
 
+    let blocker = Task.spawn(function* () {
+      let metadata = yield processExtraFile(id);
+      let hash = yield computeMinidumpHash(id);
+
+      if (hash) {
+        metadata.MinidumpSha256Hash = hash;
+      }
+
+      yield Services.crashmanager.addCrash(processType, crashType, id,
+                                           new Date(), metadata);
+    });
+
     AsyncShutdown.profileBeforeChange.addBlocker(
-      "CrashService waiting for content crash ping to be sent",
-      processExtraFile(id).then(metadata => {
-        return Services.crashmanager.addCrash(processType, crashType, id,
-                                              new Date(), metadata)
-      })
+      "CrashService waiting for content crash ping to be sent", blocker
     );
+
+    blocker.then(AsyncShutdown.profileBeforeChange.removeBlocker(blocker));
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case "profile-after-change":
         // Side-effect is the singleton is instantiated.
         Services.crashmanager;
         break;
--- a/toolkit/components/crashes/nsICrashService.idl
+++ b/toolkit/components/crashes/nsICrashService.idl
@@ -11,16 +11,18 @@ interface nsICrashService : nsISupports
    * Records the occurrence of a crash.
    *
    * @param processType
    *        One of the PROCESS_TYPE constants defined below.
    * @param crashType
    *        One of the CRASH_TYPE constants defined below.
    * @param id
    *        Crash ID. Likely a UUID.
+   *
+   * @return {Promise} A promise that resolves after the crash has been stored
    */
   void addCrash(in long processType, in long crashType, in AString id);
 
   const long PROCESS_TYPE_MAIN = 0;
   const long PROCESS_TYPE_CONTENT = 1;
   const long PROCESS_TYPE_PLUGIN = 2;
   const long PROCESS_TYPE_GMPLUGIN = 3;
   const long PROCESS_TYPE_GPU = 4;
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
@@ -205,18 +205,21 @@ add_task(function* test_schedule_mainten
 
   yield m.scheduleMaintenance(25);
   let crashes = yield m.getCrashes();
   Assert.equal(crashes.length, 1);
   Assert.equal(crashes[0].id, "id1");
 });
 
 const crashId = "3cb67eba-0dc7-6f78-6a569a0e-172287ec";
+const crashPingUuid = "103dbdf2-339b-4b9c-a7cc-5f9506ea9d08";
 const productName = "Firefox";
 const productId = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+const sha256Hash =
+  "f8410c3ac4496cfa9191a1240f0e365101aef40c7bf34fc5bcb8ec511832ed79";
 const stackTraces = "{\"status\":\"OK\"}";
 
 add_task(function* test_main_crash_event_file() {
   let ac = new TelemetryArchiveTesting.Checker();
   yield ac.promiseInit();
   let theEnvironment = TelemetryEnvironment.currentEnvironment;
   const sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
 
@@ -225,39 +228,41 @@ add_task(function* test_main_crash_event
   theEnvironment.testValue = "MyValue\"";
 
   let m = yield getManager();
   const fileContent = crashId + "\n" +
     "ProductName=" + productName + "\n" +
     "ProductID=" + productId + "\n" +
     "TelemetryEnvironment=" + JSON.stringify(theEnvironment) + "\n" +
     "TelemetrySessionId=" + sessionId + "\n" +
+    "MinidumpSha256Hash=" + sha256Hash + "\n" +
     "StackTraces=" + stackTraces + "\n" +
     "ThisShouldNot=end-up-in-the-ping\n";
 
   yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
   let count = yield m.aggregateEventsFiles();
   Assert.equal(count, 1);
 
   let crashes = yield m.getCrashes();
   Assert.equal(crashes.length, 1);
   Assert.equal(crashes[0].id, crashId);
   Assert.equal(crashes[0].type, "main-crash");
   Assert.equal(crashes[0].metadata.ProductName, productName);
   Assert.equal(crashes[0].metadata.ProductID, productId);
   Assert.ok(crashes[0].metadata.TelemetryEnvironment);
-  Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 6);
+  Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 7);
   Assert.equal(crashes[0].metadata.TelemetrySessionId, sessionId);
   Assert.ok(crashes[0].metadata.StackTraces);
   Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
 
   let found = yield ac.promiseFindPing("crash", [
     [["payload", "hasCrashEnvironment"], true],
     [["payload", "metadata", "ProductName"], productName],
     [["payload", "metadata", "ProductID"], productId],
+    [["payload", "minidumpSha256Hash"], sha256Hash],
     [["payload", "crashId"], crashId],
     [["payload", "stackTraces", "status"], "OK"],
     [["payload", "sessionId"], sessionId],
   ]);
   Assert.ok(found, "Telemetry ping submitted for found crash");
   Assert.deepEqual(found.environment, theEnvironment,
                    "The saved environment should be present");
   Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
@@ -296,16 +301,55 @@ add_task(function* test_main_crash_event
   ]);
   Assert.ok(found, "Telemetry ping submitted for found crash");
   Assert.ok(found.environment, "There is an environment");
 
   count = yield m.aggregateEventsFiles();
   Assert.equal(count, 0);
 });
 
+add_task(function* test_main_crash_event_file_override_ping_uuid() {
+  let ac = new TelemetryArchiveTesting.Checker();
+  yield ac.promiseInit();
+
+  let m = yield getManager();
+  const fileContent = crashId + "\n" +
+    "ProductName=" + productName + "\n" +
+    "ProductID=" + productId + "\n" +
+    "CrashPingUUID=" + crashPingUuid + "\n";
+
+  yield m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
+  let count = yield m.aggregateEventsFiles();
+  Assert.equal(count, 1);
+
+  let crashes = yield m.getCrashes();
+  Assert.equal(crashes.length, 1);
+  Assert.equal(crashes[0].id, crashId);
+  Assert.equal(crashes[0].type, "main-crash");
+  Assert.deepEqual(crashes[0].metadata, {
+    CrashPingUUID: crashPingUuid,
+    ProductName: productName,
+    ProductID: productId
+  });
+  Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
+
+  let found = yield ac.promiseFindPing("crash", [
+    [["id"], crashPingUuid],
+    [["payload", "hasCrashEnvironment"], false],
+    [["payload", "metadata", "ProductName"], productName],
+    [["payload", "metadata", "ProductID"], productId],
+  ]);
+  Assert.ok(found, "Telemetry ping submitted for found crash");
+  Assert.equal(found.payload.metadata.CrashPingUUID, undefined,
+               "Crash ping UUID should be filtered out");
+
+  count = yield m.aggregateEventsFiles();
+  Assert.equal(count, 0);
+});
+
 add_task(function* test_crash_submission_event_file() {
   let m = yield getManager();
   yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "crash1");
   yield m.createEventsFile("1-submission", "crash.submission.1", DUMMY_DATE_2,
                            "crash1\nfalse\n");
 
   // The line below has been intentionally commented out to make sure that
   // the crash record is created when one does not exist.
@@ -461,22 +505,24 @@ add_task(function* test_addCrash() {
 add_task(function* test_content_crash_ping() {
   let ac = new TelemetryArchiveTesting.Checker();
   yield ac.promiseInit();
 
   let m = yield getManager();
   let id = yield m.createDummyDump();
   yield m.addCrash(m.PROCESS_TYPE_CONTENT, m.CRASH_TYPE_CRASH, id, DUMMY_DATE, {
     StackTraces: stackTraces,
+    MinidumpSha256Hash: sha256Hash,
     ThisShouldNot: "end-up-in-the-ping"
   });
   yield m._pingPromise;
 
   let found = yield ac.promiseFindPing("crash", [
     [["payload", "crashId"], id],
+    [["payload", "minidumpSha256Hash"], sha256Hash],
     [["payload", "processType"], m.PROCESS_TYPE_CONTENT],
     [["payload", "stackTraces", "status"], "OK"],
   ]);
   Assert.ok(found, "Telemetry ping submitted for content crash");
   Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
                "Non-whitelisted fields should be filtered out");
 });
 
--- a/toolkit/components/telemetry/docs/data/crash-ping.rst
+++ b/toolkit/components/telemetry/docs/data/crash-ping.rst
@@ -31,16 +31,17 @@ Structure:
       processType: <type>, // Type of process that crashed, see below for a list of types
       payload: {
         crashDate: "YYYY-MM-DD",
         version: 1,
         sessionId: <UUID>, // may be missing for crashes that happen early
                            // in startup. Added in Firefox 48 with the
                            // intention of uplifting to Firefox 46
         crashId: <UUID>, // Optional, ID of the associated crash
+        minidumpSha256Hash: <hash>, // SHA256 hash of the minidump file
         stackTraces: { ... }, // Optional, see below
         metadata: { // Annotations saved while Firefox was running. See nsExceptionHandler.cpp for more information
           ProductID: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
           ProductName: "Firefox",
           ReleaseChannel: <channel>,
           Version: <version number>,
           BuildID: "YYYYMMDDHHMMSS",
           AvailablePageFile: <size>, // Windows-only, available paging file
--- a/toolkit/crashreporter/client/crashreporter.cpp
+++ b/toolkit/crashreporter/client/crashreporter.cpp
@@ -6,23 +6,31 @@
 #include "crashreporter.h"
 
 #ifdef _MSC_VER
 // Disable exception handler warnings.
 # pragma warning( disable : 4530 )
 #endif
 
 #include <fstream>
+#include <iomanip>
 #include <sstream>
 #include <memory>
 #include <ctime>
 #include <cstdlib>
 #include <cstring>
 #include <string>
 
+#ifdef XP_LINUX
+#include <dlfcn.h>
+#endif
+
+#include "nss.h"
+#include "sechash.h"
+
 using std::string;
 using std::istream;
 using std::ifstream;
 using std::istringstream;
 using std::ostringstream;
 using std::ostream;
 using std::ofstream;
 using std::vector;
@@ -431,16 +439,127 @@ void SendCompleted(bool success, const s
 }
 
 bool ShouldEnableSending()
 {
   srand(time(0));
   return ((rand() % 100) < MOZ_CRASHREPORTER_ENABLE_PERCENT);
 }
 
+static string ComputeDumpHash() {
+#ifdef XP_LINUX
+  // On Linux we rely on the system-provided libcurl which uses nss so we have
+  // to also use the system-provided nss instead of the ones we have bundled.
+  const char* libnssNames[] = {
+    "libnss3.so",
+#ifndef HAVE_64BIT_BUILD
+    // 32-bit versions on 64-bit hosts
+    "/usr/lib32/libnss3.so",
+#endif
+  };
+  void* lib = nullptr;
+
+  for (const char* libname : libnssNames) {
+    lib = dlopen(libname, RTLD_NOW);
+
+    if (lib) {
+      break;
+    }
+  }
+
+  if (!lib) {
+    return "";
+  }
+
+  SECStatus (*NSS_Initialize)(const char*, const char*, const char*,
+                              const char*, PRUint32);
+  HASHContext* (*HASH_Create)(HASH_HashType);
+  void (*HASH_Destroy)(HASHContext*);
+  void (*HASH_Begin)(HASHContext*);
+  void (*HASH_Update)(HASHContext*, const unsigned char*, unsigned int);
+  void (*HASH_End)(HASHContext*, unsigned char*, unsigned int*, unsigned int);
+
+  *(void**) (&NSS_Initialize) = dlsym(lib, "NSS_Initialize");
+  *(void**) (&HASH_Create) = dlsym(lib, "HASH_Create");
+  *(void**) (&HASH_Destroy) = dlsym(lib, "HASH_Destroy");
+  *(void**) (&HASH_Begin) = dlsym(lib, "HASH_Begin");
+  *(void**) (&HASH_Update) = dlsym(lib, "HASH_Update");
+  *(void**) (&HASH_End) = dlsym(lib, "HASH_End");
+
+  if (!HASH_Create || !HASH_Destroy || !HASH_Begin || !HASH_Update ||
+      !HASH_End) {
+    return "";
+  }
+#endif
+  // Minimal NSS initialization so we can use the hash functions
+  const PRUint32 kNssFlags = NSS_INIT_READONLY | NSS_INIT_NOROOTINIT |
+                             NSS_INIT_NOMODDB | NSS_INIT_NOCERTDB;
+  if (NSS_Initialize(nullptr, "", "", "", kNssFlags) != SECSuccess) {
+    return "";
+  }
+
+  HASHContext* hashContext = HASH_Create(HASH_AlgSHA256);
+
+  if (!hashContext) {
+    return "";
+  }
+
+  HASH_Begin(hashContext);
+
+  ifstream* f = UIOpenRead(gReporterDumpFile, /* binary */ true);
+  bool error = false;
+
+  // Read the minidump contents
+  if (f->is_open()) {
+    uint8_t buff[4096];
+
+    do {
+      f->read((char*) buff, sizeof(buff));
+
+      if (f->bad()) {
+        error = true;
+        break;
+      }
+
+      HASH_Update(hashContext, buff, f->gcount());
+    } while (!f->eof());
+
+    f->close();
+  } else {
+    error = true;
+  }
+
+  delete f;
+
+  // Finalize the hash computation
+  uint8_t result[SHA256_LENGTH];
+  uint32_t resultLen = 0;
+
+  HASH_End(hashContext, result, &resultLen, SHA256_LENGTH);
+
+  if (resultLen != SHA256_LENGTH) {
+    error = true;
+  }
+
+  HASH_Destroy(hashContext);
+
+  if (!error) {
+    ostringstream hash;
+
+    for (size_t i = 0; i < SHA256_LENGTH; i++) {
+      hash << std::setw(2) << std::setfill('0') << std::hex
+           << static_cast<unsigned int>(result[i]);
+    }
+
+    return hash.str();
+  } else {
+    return ""; // If we encountered an error, return an empty hash
+  }
+}
+
 } // namespace CrashReporter
 
 using namespace CrashReporter;
 
 void RewriteStrings(StringTable& queryParameters)
 {
   // rewrite some UI strings with the values from the query parameters
   string product = queryParameters["ProductName"];
@@ -636,19 +755,23 @@ int main(int argc, char** argv)
       gEventsPath = eventsPath;
     }
 #endif
     else {
       gEventsPath.clear();
     }
 
     // Assemble and send the crash ping
+    string hash;
     string pingUuid;
 
-    if (SendCrashPing(queryParameters, pingUuid)) {
+    hash = ComputeDumpHash();
+    AppendToEventFile("MinidumpSha256Hash", hash);
+
+    if (SendCrashPing(queryParameters, hash, pingUuid)) {
       AppendToEventFile("CrashPingUUID", pingUuid);
     }
 
     // Update the crash event with stacks if they are present
     auto stackTracesItr = queryParameters.find("StackTraces");
     if (stackTracesItr != queryParameters.end()) {
       AppendToEventFile(stackTracesItr->first, stackTracesItr->second);
     }
--- a/toolkit/crashreporter/client/crashreporter.h
+++ b/toolkit/crashreporter/client/crashreporter.h
@@ -113,17 +113,18 @@ namespace CrashReporter {
                           const std::string& header,
                           StringTable& strings,
                           bool escape);
   void LogMessage(const std::string& message);
   void DeleteDump();
   bool ShouldEnableSending();
 
   // Telemetry ping
-  bool SendCrashPing(StringTable& strings, std::string& pingUuid);
+  bool SendCrashPing(StringTable& strings, const std::string& hash,
+                     std::string& pingUuid);
 
   static const unsigned int kSaveCount = 10;
 }
 
 //=============================================================================
 // implemented in the platform-specific files
 //=============================================================================
 
@@ -146,17 +147,17 @@ void UIError_impl(const std::string& mes
 bool UIGetIniPath(std::string& path);
 bool UIGetSettingsPath(const std::string& vendor,
                        const std::string& product,
                        std::string& settingsPath);
 bool UIEnsurePathExists(const std::string& path);
 bool UIFileExists(const std::string& path);
 bool UIMoveFile(const std::string& oldfile, const std::string& newfile);
 bool UIDeleteFile(const std::string& oldfile);
-std::ifstream* UIOpenRead(const std::string& filename);
+std::ifstream* UIOpenRead(const std::string& filename, bool binary = false);
 std::ofstream* UIOpenWrite(const std::string& filename,
                            bool append=false,
                            bool binary=false);
 void UIPruneSavedDumps(const std::string& directory);
 
 // Run the program specified by exename, passing it the parameter in arg and
 // then sending it the contents of data (if any) via a pipe tied to its stdin.
 // If wait is true, wait for the program to terminate execution before
--- a/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
+++ b/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
@@ -425,19 +425,25 @@ bool UIMoveFile(const string& file, cons
   return UIFileExists(newfile);
 }
 
 bool UIDeleteFile(const string& file)
 {
   return (unlink(file.c_str()) != -1);
 }
 
-std::ifstream* UIOpenRead(const string& filename)
+std::ifstream* UIOpenRead(const string& filename, bool binary)
 {
-  return new std::ifstream(filename.c_str(), std::ios::in);
+  std::ios_base::openmode mode = std::ios::in;
+
+  if (binary) {
+    mode = mode | std::ios::binary;
+  }
+
+  return new std::ifstream(filename.c_str(), mode);
 }
 
 std::ofstream* UIOpenWrite(const string& filename,
                            bool append, // append=false
                            bool binary) // binary=false
 {
   std::ios_base::openmode mode = std::ios::out;
 
--- a/toolkit/crashreporter/client/crashreporter_osx.mm
+++ b/toolkit/crashreporter/client/crashreporter_osx.mm
@@ -894,19 +894,25 @@ bool UIMoveFile(const string& file, cons
   return UIFileExists(newfile);
 }
 
 bool UIDeleteFile(const string& file)
 {
   return (unlink(file.c_str()) != -1);
 }
 
-std::ifstream* UIOpenRead(const string& filename)
+std::ifstream* UIOpenRead(const string& filename, bool binary)
 {
-  return new std::ifstream(filename.c_str(), std::ios::in);
+  std::ios_base::openmode mode = std::ios::in;
+
+  if (binary) {
+    mode = mode | std::ios::binary;
+  }
+
+  return new std::ifstream(filename.c_str(), mode);
 }
 
 std::ofstream* UIOpenWrite(const string& filename,
                            bool append, // append=false
                            bool binary) // binary=false
 {
   std::ios_base::openmode mode = std::ios::out;
 
--- a/toolkit/crashreporter/client/crashreporter_win.cpp
+++ b/toolkit/crashreporter/client/crashreporter_win.cpp
@@ -1458,26 +1458,31 @@ bool UIMoveFile(const string& oldfile, c
     == TRUE;
 }
 
 bool UIDeleteFile(const string& oldfile)
 {
   return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE;
 }
 
-ifstream* UIOpenRead(const string& filename)
+ifstream* UIOpenRead(const string& filename, bool binary)
 {
   // adapted from breakpad's src/common/windows/http_upload.cc
+  std::ios_base::openmode mode = ios::in;
+
+  if (binary) {
+    mode = mode | ios::binary;
+  }
 
 #if defined(_MSC_VER)
   ifstream* file = new ifstream();
-  file->open(UTF8ToWide(filename).c_str(), ios::in);
+  file->open(UTF8ToWide(filename).c_str(), mode);
 #else   // GCC
   ifstream* file = new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(),
-                                ios::in);
+                                mode);
 #endif  // _MSC_VER
 
   return file;
 }
 
 ofstream* UIOpenWrite(const string& filename,
                       bool append, // append=false
                       bool binary) // binary=false
--- a/toolkit/crashreporter/client/moz.build
+++ b/toolkit/crashreporter/client/moz.build
@@ -23,16 +23,17 @@ if CONFIG['OS_TARGET'] != 'Android':
 if CONFIG['OS_ARCH'] == 'WINNT':
     UNIFIED_SOURCES += [
         'crashreporter_win.cpp',
     ]
     DEFINES['UNICODE'] = True
     DEFINES['_UNICODE'] = True
     USE_LIBS += [
         'google_breakpad_libxul_s',
+        'nss',
     ]
     OS_LIBS += [
         'comctl32',
         'ole32',
         'shell32',
         'wininet',
         'shlwapi',
     ]
@@ -43,16 +44,17 @@ elif CONFIG['OS_ARCH'] == 'Darwin':
     ]
     LOCAL_INCLUDES += [
         '../google-breakpad/src/common/mac',
     ]
     OS_LIBS += ['-framework Cocoa']
     USE_LIBS += [
         'breakpad_common_s',
         'breakpad_mac_common_s',
+        'nss',
     ]
 elif CONFIG['OS_ARCH'] == 'SunOS':
     SOURCES += [
         'crashreporter_linux.cpp',
         'crashreporter_unix.cpp',
     ]
     USE_LIBS += [
         'breakpad_solaris_common_s',
--- a/toolkit/crashreporter/client/ping.cpp
+++ b/toolkit/crashreporter/client/ping.cpp
@@ -158,25 +158,27 @@ CreateMetadataNode(StringTable& strings)
     }
   }
 
   return node;
 }
 
 // Create the payload node of the crash ping
 static Json::Value
-CreatePayloadNode(StringTable& strings, const string& aSessionId)
+CreatePayloadNode(StringTable& strings, const string& aHash,
+                  const string& aSessionId)
 {
   Json::Value payload;
 
   payload["sessionId"] = aSessionId;
   payload["version"] = 1;
   payload["crashDate"] = CurrentDate(kISO8601Date);
   payload["hasCrashEnvironment"] = true;
   payload["crashId"] = GetDumpLocalID();
+  payload["minidumpSha256Hash"] = aHash;
   payload["processType"] = "main"; // This is always a main crash
 
   // Parse the stack traces
   Json::Value stackTracesValue;
   Json::Reader reader;
 
   if (reader.parse(strings["StackTraces"], stackTracesValue,
                    /* collectComments */ false)) {
@@ -212,17 +214,17 @@ CreateApplicationNode(const string& aVen
     application["xpcomAbi"] = aXpcomAbi;
   }
 
   return application;
 }
 
 // Create the root node of the crash ping
 static Json::Value
-CreateRootNode(StringTable& strings, const string& aUuid,
+CreateRootNode(StringTable& strings, const string& aUuid, const string& aHash,
                const string& aClientId, const string& aSessionId,
                const string& aName, const string& aVersion,
                const string& aChannel, const string& aBuildId)
 {
   Json::Value root;
   root["type"] = "crash"; // This is a crash ping
   root["id"] = aUuid;
   root["version"] = kTelemetryVersion;
@@ -245,17 +247,17 @@ CreateRootNode(StringTable& strings, con
       if (build.isMember("xpcomAbi") && build["xpcomAbi"].isString()) {
         xpcomAbi = build["xpcomAbi"].asString();
       }
     }
 
     root["environment"] = environment;
   }
 
-  root["payload"] = CreatePayloadNode(strings, aSessionId);
+  root["payload"] = CreatePayloadNode(strings, aHash, aSessionId);
   root["application"] = CreateApplicationNode(strings["Vendor"], aName,
                                               aVersion, aChannel, aBuildId,
                                               architecture, xpcomAbi);
 
   return root;
 }
 
 // Generates the URL used to submit the crash ping, see TelemetrySend.jsm
@@ -275,17 +277,17 @@ GenerateSubmissionUrl(const string& aUrl
 // together with the crash report.
 //
 // Note that the crash ping sender is invoked in a fire-and-forget way so this
 // won't block waiting for the ping to be delivered.
 //
 // Returns true if the ping was assembled and handed over to the pingsender
 // correctly, false otherwise and populates the aUUID field with the ping UUID.
 bool
-SendCrashPing(StringTable& strings, string& pingUuid)
+SendCrashPing(StringTable& strings, const string& aHash, string& pingUuid)
 {
   string clientId    = strings[kTelemetryClientId];
   string serverUrl   = strings[kTelemetryUrl];
   string sessionId   = strings[kTelemetrySessionId];
 
   // Remove the telemetry-related data from the crash annotations
   strings.erase(kTelemetryClientId);
   strings.erase(kTelemetryUrl);
@@ -298,17 +300,17 @@ SendCrashPing(StringTable& strings, stri
   string uuid        = GenerateUUID();
   string url         = GenerateSubmissionUrl(serverUrl, uuid, name, version,
                                              channel, buildId);
 
   if (serverUrl.empty() || uuid.empty()) {
     return false;
   }
 
-  Json::Value root = CreateRootNode(strings, uuid, clientId, sessionId,
+  Json::Value root = CreateRootNode(strings, uuid, aHash, clientId, sessionId,
                                     name, version, channel, buildId);
 
   // Write out the result
   Json::FastWriter writer;
   string ping = writer.write(root);
 
   // Hand over the ping to the sender
   if (UIRunProgram(GetProgramPath(UI_PING_SENDER_FILENAME), url, ping)) {
--- a/toolkit/crashreporter/docs/index.rst
+++ b/toolkit/crashreporter/docs/index.rst
@@ -91,17 +91,18 @@ the time of the crash, memory statistics
 If the *crash reporter client* is enabled, ``MinidumpCallback()`` invokes
 it. It simply tries to create a new *crash reporter client* process (e.g.
 *crashreporter.exe*) with the path to the written minidump file as an
 argument.
 
 The *crash reporter client* performs a number of roles. There's a lot going
 on, so you may want to look at ``main()`` in ``crashreporter.cpp``. First,
 stack traces are extracted from the dump via the *minidump analyzer* tool.
-The resulting traces are appended to the .extra file of the crash. Once this
+The resulting traces are appended to the .extra file of the crash together with
+the SHA256 hash of the minidump file. Once this
 is done a crash ping is assembled holding the same information as the one
 generated by the ```CrashManager``` and it's sent to the telemetry servers via
 the *ping sender* program. The UUID of the ping is then stored in the extra
 file; the ```CrashManager``` will later pick it up and generate a new ping
 with the same UUID so that the telemetry server can deduplicate both pings.
 Then, the
 *crash reporter client* verifies that the dump data is sane. If it isn't
 (e.g. required metadata is missing), the dump data is ignored. If dump data
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -182,16 +182,19 @@ static const char kCrashMainID[] = "cras
 
 static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
 
 static XP_CHAR* pendingDirectory;
 static XP_CHAR* crashReporterPath;
 static XP_CHAR* memoryReportPath;
 #if !defined(MOZ_WIDGET_ANDROID)
 static XP_CHAR* minidumpAnalyzerPath;
+#ifdef XP_MACOSX
+static XP_CHAR* libraryPath; // Path where the NSS library is
+#endif // XP_MACOSX
 #endif // !defined(MOZ_WIDGET_ANDROID)
 
 // Where crash events should go.
 static XP_CHAR* eventsDirectory;
 static char* eventsEnv = nullptr;
 
 // The current telemetry session ID to write to the event file
 static char* currentSessionId = nullptr;
@@ -387,30 +390,16 @@ JitExceptionHandler(void *exceptionRecor
  *
  * This size is bigger than xul.dll plus some extra for MinidumpWriteDump
  * allocations.
  */
 static const SIZE_T kReserveSize = 0x4000000; // 64 MB
 static void* gBreakpadReservedVM;
 #endif
 
-#ifdef XP_MACOSX
-static cpu_type_t pref_cpu_types[2] = {
-#if defined(__i386__)
-                                 CPU_TYPE_X86,
-#elif defined(__x86_64__)
-                                 CPU_TYPE_X86_64,
-#elif defined(__ppc__)
-                                 CPU_TYPE_POWERPC,
-#endif
-                                 CPU_TYPE_ANY };
-
-static posix_spawnattr_t spawnattr;
-#endif
-
 #if defined(MOZ_WIDGET_ANDROID)
 // Android builds use a custom library loader,
 // so the embedding will provide a list of shared
 // libraries that are mapped into anonymous mappings.
 typedef struct {
   std::string name;
   uintptr_t   start_address;
   size_t      length;
@@ -836,55 +825,37 @@ LaunchProgram(const XP_CHAR* aProgramPat
     if (aWait) {
       WaitForSingleObject(pi.hProcess, INFINITE);
     }
 
     CloseHandle(pi.hProcess);
     CloseHandle(pi.hThread);
   }
 #elif defined(XP_UNIX)
-#ifdef XP_MACOSX
-  pid_t pid = 0;
-  char* const my_argv[] = {
-    const_cast<char*>(aProgramPath),
-    const_cast<char*>(aMinidumpPath),
-    nullptr
-  };
-
-  char **env = nullptr;
-  char ***nsEnv = _NSGetEnviron();
-  if (nsEnv)
-    env = *nsEnv;
-
-  int rv = posix_spawnp(&pid, my_argv[0], nullptr, &spawnattr, my_argv, env);
-
-  if (rv != 0) {
-    return false;
-  } else if (aWait) {
-    waitpid(pid, nullptr, 0);
-  }
-
-#else // !XP_MACOSX
   pid_t pid = sys_fork();
 
   if (pid == -1) {
     return false;
   } else if (pid == 0) {
+#ifdef XP_LINUX
     // need to clobber this, as libcurl might load NSS,
     // and we want it to load the system NSS.
     unsetenv("LD_LIBRARY_PATH");
+#else // XP_MACOSX
+    // Needed to locate NSS and its dependencies
+    setenv("DYLD_LIBRARY_PATH", libraryPath, /* overwrite */ 1);
+#endif
     Unused << execl(aProgramPath,
                     aProgramPath, aMinidumpPath, (char*)0);
     _exit(1);
   } else {
     if (aWait) {
-      sys_waitpid(pid, nullptr, __WALL);
+      waitpid(pid, nullptr, 0);
     }
   }
-#endif // XP_MACOSX
 #endif // XP_UNIX
 
   return true;
 }
 
 #else
 
 /**
@@ -1644,32 +1615,49 @@ nsresult SetExceptionHandler(nsIFile* aX
     nsAutoString crashReporterPath_temp;
     nsresult rv = LocateExecutable(aXREDirectory,
                                    NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME),
                                    crashReporterPath_temp);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+#ifdef XP_MACOSX
+    nsCOMPtr<nsIFile> libPath;
+    rv = aXREDirectory->Clone(getter_AddRefs(libPath));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+
+    nsAutoString libraryPath_temp;
+    rv = libPath->GetPath(libraryPath_temp);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+#endif // XP_MACOSX
+
     nsAutoString minidumpAnalyzerPath_temp;
     rv = LocateExecutable(aXREDirectory,
                           NS_LITERAL_CSTRING(MINIDUMP_ANALYZER_FILENAME),
                           minidumpAnalyzerPath_temp);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
 #ifdef XP_WIN32
   crashReporterPath =
     reinterpret_cast<wchar_t*>(ToNewUnicode(crashReporterPath_temp));
   minidumpAnalyzerPath =
     reinterpret_cast<wchar_t*>(ToNewUnicode(minidumpAnalyzerPath_temp));
 #else
   crashReporterPath = ToNewCString(crashReporterPath_temp);
   minidumpAnalyzerPath = ToNewCString(minidumpAnalyzerPath_temp);
+#ifdef XP_MACOSX
+  libraryPath = ToNewCString(libraryPath_temp);
+#endif
 #endif // XP_WIN32
 #else
     // On Android, we launch using the application package name instead of a
     // filename, so use the dynamically set MOZ_ANDROID_PACKAGE_NAME, or fall
     // back to the static ANDROID_PACKAGE_NAME.
     const char* androidPackageName = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME");
     if (androidPackageName != nullptr) {
       nsCString package(androidPackageName);
@@ -1687,35 +1675,16 @@ nsresult SetExceptionHandler(nsIFile* aX
   nsString tempPath;
 #else
   nsCString tempPath;
 #endif
   if (!BuildTempPath(tempPath)) {
     return NS_ERROR_FAILURE;
   }
 
-#ifdef XP_MACOSX
-  // Initialize spawn attributes, since this calls malloc.
-  if (posix_spawnattr_init(&spawnattr) != 0) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Set spawn attributes.
-  size_t attr_count = ArrayLength(pref_cpu_types);
-  size_t attr_ocount = 0;
-  if (posix_spawnattr_setbinpref_np(&spawnattr,
-                                    attr_count,
-                                    pref_cpu_types,
-                                    &attr_ocount) != 0 ||
-      attr_ocount != attr_count) {
-    posix_spawnattr_destroy(&spawnattr);
-    return NS_ERROR_FAILURE;
-  }
-#endif
-
 #ifdef XP_WIN32
   ReserveBreakpadVM();
 #endif // XP_WIN32
 
 #ifdef MOZ_WIDGET_ANDROID
   androidUserSerial = getenv("MOZ_ANDROID_USER_SERIAL_NUMBER");
 #endif
 
@@ -2136,35 +2105,44 @@ nsresult UnsetExceptionHandler()
     pendingDirectory = nullptr;
   }
 
   if (crashReporterPath) {
     free(crashReporterPath);
     crashReporterPath = nullptr;
   }
 
+#if !defined(MOZ_WIDGET_ANDROID)
+  if (minidumpAnalyzerPath) {
+    free(minidumpAnalyzerPath);
+    minidumpAnalyzerPath = nullptr;
+  }
+#ifdef XP_MACOSX
+  if (libraryPath) {
+    free(libraryPath);
+    libraryPath = nullptr;
+  }
+#endif // XP_MACOSX
+#endif // !defined(MOZ_WIDGET_ANDROID)
+
   if (eventsDirectory) {
     free(eventsDirectory);
     eventsDirectory = nullptr;
   }
 
   if (currentSessionId) {
     free(currentSessionId);
     currentSessionId = nullptr;
   }
 
   if (memoryReportPath) {
     free(memoryReportPath);
     memoryReportPath = nullptr;
   }
 
-#ifdef XP_MACOSX
-  posix_spawnattr_destroy(&spawnattr);
-#endif
-
   if (!gExceptionHandler)
     return NS_ERROR_NOT_INITIALIZED;
 
   gExceptionHandler = nullptr;
 
   OOPDeinit();
 
   delete dumpSafetyLock;
@@ -2950,53 +2928,67 @@ GetMinidumpLimboDir(nsIFile** dir)
     return nullptr != *dir;
   }
 }
 
 void
 DeleteMinidumpFilesForID(const nsAString& id)
 {
   nsCOMPtr<nsIFile> minidumpFile;
-  GetMinidumpForID(id, getter_AddRefs(minidumpFile));
-  bool exists = false;
-  if (minidumpFile && NS_SUCCEEDED(minidumpFile->Exists(&exists)) && exists) {
+  if (GetMinidumpForID(id, getter_AddRefs(minidumpFile))) {
     nsCOMPtr<nsIFile> childExtraFile;
     GetExtraFileForMinidump(minidumpFile, getter_AddRefs(childExtraFile));
     if (childExtraFile) {
       childExtraFile->Remove(false);
     }
     minidumpFile->Remove(false);
   }
 }
 
 bool
 GetMinidumpForID(const nsAString& id, nsIFile** minidump)
 {
-  if (!GetMinidumpLimboDir(minidump))
+  if (!GetMinidumpLimboDir(minidump)) {
     return false;
+  }
+
   (*minidump)->Append(id + NS_LITERAL_STRING(".dmp"));
+
+  bool exists;
+  if (NS_FAILED((*minidump)->Exists(&exists)) || !exists) {
+    return false;
+  }
+
   return true;
 }
 
 bool
 GetIDFromMinidump(nsIFile* minidump, nsAString& id)
 {
   if (minidump && NS_SUCCEEDED(minidump->GetLeafName(id))) {
     id.Replace(id.Length() - 4, 4, NS_LITERAL_STRING(""));
     return true;
   }
   return false;
 }
 
 bool
 GetExtraFileForID(const nsAString& id, nsIFile** extraFile)
 {
-  if (!GetMinidumpLimboDir(extraFile))
+  if (!GetMinidumpLimboDir(extraFile)) {
     return false;
+  }
+
   (*extraFile)->Append(id + NS_LITERAL_STRING(".extra"));
+
+  bool exists;
+  if (NS_FAILED((*extraFile)->Exists(&exists)) || !exists) {
+    return false;
+  }
+
   return true;
 }
 
 bool
 GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile)
 {
   nsAutoString leafName;
   nsresult rv = minidump->GetLeafName(leafName);
@@ -3037,17 +3029,17 @@ AppendExtraData(const nsAString& id, con
  * do not call it from the main thread!
  */
 void
 RunMinidumpAnalyzer(const nsAString& id)
 {
 #if !defined(MOZ_WIDGET_ANDROID)
   nsCOMPtr<nsIFile> file;
 
-  if (CrashReporter::GetMinidumpForID(id, getter_AddRefs(file)) && file) {
+  if (CrashReporter::GetMinidumpForID(id, getter_AddRefs(file))) {
 #ifdef XP_WIN
     nsAutoString path;
     file->GetPath(path);
 #else
     nsAutoCString path;
     file->GetNativePath(path);
 #endif
 
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -1235,16 +1235,36 @@ nsXULAppInfo::SetMinidumpPath(nsIFile* a
 {
   nsAutoString path;
   nsresult rv = aMinidumpPath->GetPath(path);
   NS_ENSURE_SUCCESS(rv, rv);
   return CrashReporter::SetMinidumpPath(path);
 }
 
 NS_IMETHODIMP
+nsXULAppInfo::GetMinidumpForID(const nsAString& aId, nsIFile** aMinidump)
+{
+  if (!CrashReporter::GetMinidumpForID(aId, aMinidump)) {
+    return NS_ERROR_FILE_NOT_FOUND;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::GetExtraFileForID(const nsAString& aId, nsIFile** aExtraFile)
+{
+  if (!CrashReporter::GetExtraFileForID(aId, aExtraFile)) {
+    return NS_ERROR_FILE_NOT_FOUND;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsXULAppInfo::AnnotateCrashReport(const nsACString& key,
                                   const nsACString& data)
 {
   return CrashReporter::AnnotateCrashReport(key, data);
 }
 
 NS_IMETHODIMP
 nsXULAppInfo::AppendAppNotesToCrashReport(const nsACString& data)
--- a/xpcom/system/nsICrashReporter.idl
+++ b/xpcom/system/nsICrashReporter.idl
@@ -44,16 +44,40 @@ interface nsICrashReporter : nsISupports
    * Get or set the path on the local system to which minidumps will be
    * written when a crash happens.
    *
    * @throw NS_ERROR_NOT_INITIALIZED if crash reporting is not initialized
    */
   attribute nsIFile minidumpPath;
 
   /**
+   * Get the minidump file corresponding to the specified ID.
+   *
+   * @param id
+   *        ID of the crash. Likely a UUID.
+   *
+   * @return The minidump file associated with the ID.
+   *
+   * @throw NS_ERROR_FILE_NOT_FOUND if the minidump could not be found
+   */
+  nsIFile getMinidumpForID(in AString id);
+
+  /**
+   * Get the extra file corresponding to the specified ID.
+   *
+   * @param id
+   *        ID of the crash. Likely a UUID.
+   *
+   * @return The extra file associated with the ID.
+   *
+   * @throw NS_ERROR_FILE_NOT_FOUND if the extra file could not be found
+   */
+  nsIFile getExtraFileForID(in AString id);
+
+  /**
    * Add some extra data to be submitted with a crash report.
    *
    * @param key
    *        Name of the data to be added.
    * @param data
    *        Data to be added.
    *
    * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized