Files
tubestation/toolkit/components/crashes/CrashService.js
Gabriele Svelto 30d88a559a Bug 1322611 - After a crash compute the SHA256 hash of a minidump and attach it to the crash ping; r=Ted, nchen
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
2017-02-16 07:36:57 +01:00

158 lines
4.9 KiB
JavaScript

/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
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);
return Task.spawn(function* () {
try {
let extraFile = cr.getExtraFileForID(id);
let decoder = new TextDecoder();
let extraData = yield OS.File.read(extraFile.path);
return parseKeyValuePairs(decoder.decode(extraData));
} catch (e) {
Cu.reportError(e);
return {};
}
});
}
/**
* This component makes crash data available throughout the application.
*
* It is a service because some background activity will eventually occur.
*/
this.CrashService = function() {};
CrashService.prototype = Object.freeze({
classID: Components.ID("{92668367-1b17-4190-86b2-1061b2179744}"),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsICrashService,
Ci.nsIObserver,
]),
addCrash(processType, crashType, id) {
switch (processType) {
case Ci.nsICrashService.PROCESS_TYPE_MAIN:
processType = Services.crashmanager.PROCESS_TYPE_MAIN;
break;
case Ci.nsICrashService.PROCESS_TYPE_CONTENT:
processType = Services.crashmanager.PROCESS_TYPE_CONTENT;
break;
case Ci.nsICrashService.PROCESS_TYPE_PLUGIN:
processType = Services.crashmanager.PROCESS_TYPE_PLUGIN;
break;
case Ci.nsICrashService.PROCESS_TYPE_GMPLUGIN:
processType = Services.crashmanager.PROCESS_TYPE_GMPLUGIN;
break;
case Ci.nsICrashService.PROCESS_TYPE_GPU:
processType = Services.crashmanager.PROCESS_TYPE_GPU;
break;
default:
throw new Error("Unrecognized PROCESS_TYPE: " + processType);
}
switch (crashType) {
case Ci.nsICrashService.CRASH_TYPE_CRASH:
crashType = Services.crashmanager.CRASH_TYPE_CRASH;
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", 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;
}
},
});
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CrashService]);