Bug 1293656 - Store all crash annotations in the crash manager's database and filter out privacy-sensitive ones when sending a crash ping r=bsmedberg

This commit is contained in:
Gabriele Svelto 2016-10-19 12:48:19 +02:00
Родитель 6cc37ab472
Коммит d55bb9094c
4 изменённых файлов: 151 добавлений и 99 удалений

Просмотреть файл

@ -39,6 +39,36 @@ 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.
*
* @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
*/
function parseAndRemoveField(obj, field, parseAsJson = true) {
let value = null;
if (field in obj) {
if (!parseAsJson) {
value = obj[field];
} else {
try {
value = JSON.parse(obj[field]);
} catch (e) {
Cu.reportError(e);
}
}
delete obj[field];
}
return value;
}
/**
* A gateway to crash-related data.
@ -174,6 +204,41 @@ this.CrashManager.prototype = Object.freeze({
// The type of event is unknown.
EVENT_FILE_ERROR_UNKNOWN_EVENT: "unknown-event",
// A whitelist of crash annotations which do not contain sensitive data
// and are saved in the crash record and sent with Firefox Health Report.
ANNOTATION_WHITELIST: [
"AsyncShutdownTimeout",
"BuildID",
"ProductID",
"ProductName",
"ReleaseChannel",
"SecondsSinceLastCrash",
"ShutdownProgress",
"StartupCrash",
"TelemetryEnvironment",
"Version",
// The following entries are not normal annotations that can be found in
// the .extra file but are included in the crash record/FHR:
"AvailablePageFile",
"AvailablePhysicalMemory",
"AvailableVirtualMemory",
"BlockedDllList",
"BlocklistInitFailed",
"ContainsMemoryReport",
"CrashTime",
"EventLoopNestingLevel",
"IsGarbageCollecting",
"MozCrashReason",
"OOMAllocationSize",
"SystemMemoryUsePercentage",
"TextureUsage",
"TotalPageFile",
"TotalPhysicalMemory",
"TotalVirtualMemory",
"UptimeTS",
"User32BeforeBlocklist",
],
/**
* Obtain a list of all dumps pending upload.
*
@ -544,6 +609,51 @@ this.CrashManager.prototype = Object.freeze({
}.bind(this));
},
_filterAnnotations: function (annotations) {
let filteredAnnotations = {};
for (let line in annotations) {
if (this.ANNOTATION_WHITELIST.includes(line)) {
filteredAnnotations[line] = annotations[line];
}
}
return filteredAnnotations;
},
_sendCrashPing: function (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 stackTraces = parseAndRemoveField(reportMeta, "StackTraces");
// Filter the remaining annotations to remove privacy-sensitive ones
reportMeta = this._filterAnnotations(reportMeta);
TelemetryController.submitExternalPing("crash",
{
version: 1,
crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
sessionId: sessionId,
crashId: crashId,
processType: type,
stackTraces: stackTraces,
metadata: reportMeta,
hasCrashEnvironment: (crashEnvironment !== null),
},
{
retentionDays: 180,
addClientId: true,
addEnvironment: true,
overrideEnvironment: crashEnvironment,
}
);
},
_handleEventFilePayload: function(store, entry, type, date, payload) {
// The payload types and formats are documented in docs/crash-events.rst.
// Do not change the format of an existing type. Instead, invent a new
@ -565,48 +675,7 @@ this.CrashManager.prototype = Object.freeze({
store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
crashID, date, metadata);
// If we have a saved environment, use it. Otherwise report
// the current environment.
let crashEnvironment = null;
let sessionId = null;
let stackTraces = null;
let reportMeta = Cu.cloneInto(metadata, myScope);
if ('TelemetryEnvironment' in reportMeta) {
try {
crashEnvironment = JSON.parse(reportMeta.TelemetryEnvironment);
} catch (e) {
Cu.reportError(e);
}
delete reportMeta.TelemetryEnvironment;
}
if ('TelemetrySessionId' in reportMeta) {
sessionId = reportMeta.TelemetrySessionId;
delete reportMeta.TelemetrySessionId;
}
if ('StackTraces' in reportMeta) {
try {
stackTraces = JSON.parse(reportMeta.StackTraces);
} catch (e) {
Cu.reportError(e);
}
delete reportMeta.StackTraces;
}
TelemetryController.submitExternalPing("crash",
{
version: 1,
crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
sessionId: sessionId,
crashId: entry.id,
stackTraces: stackTraces,
metadata: reportMeta,
hasCrashEnvironment: (crashEnvironment !== null),
},
{
retentionDays: 180,
addClientId: true,
addEnvironment: true,
overrideEnvironment: crashEnvironment,
});
this._sendCrashPing(crashID, this.PROCESS_TYPE_MAIN, date, metadata);
break;
case "crash.submission.1":

Просмотреть файл

@ -15,6 +15,12 @@ From JavaScript, the service can be accessed via::
That will give you an instance of ``CrashManager`` from ``CrashManager.jsm``.
From there, you can access and manipulate crash data.
The crash manager stores statistical information about crashes as well as
detailed information for both browser and content crashes. The crash manager
automatically detects new browser crashes at startup by scanning for
:ref:`crash-events`. Content process crash information on the other hand is
provided externally.
Other Documents
===============

Просмотреть файл

@ -209,11 +209,15 @@ add_task(function* test_schedule_maintenance() {
Assert.equal(crashes[0].id, "id1");
});
const crashId = "3cb67eba-0dc7-6f78-6a569a0e-172287ec";
const productName = "Firefox";
const productId = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
add_task(function* test_main_crash_event_file() {
let ac = new TelemetryArchiveTesting.Checker();
yield ac.promiseInit();
let theEnvironment = TelemetryEnvironment.currentEnvironment;
let sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
const sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
let stackTraces = { status: "OK" };
// To test proper escaping, add data to the environment with an embedded
@ -221,36 +225,43 @@ add_task(function* test_main_crash_event_file() {
theEnvironment.testValue = "MyValue\"";
let m = yield getManager();
const fileContent = "id1\nk1=v1\nk2=v2\n" +
const fileContent = crashId + "\n" +
"ProductName=" + productName + "\n" +
"ProductID=" + productId + "\n" +
"TelemetryEnvironment=" + JSON.stringify(theEnvironment) + "\n" +
"TelemetrySessionId=" + sessionId + "\n" +
"StackTraces=" + JSON.stringify(stackTraces) + "\n";
"StackTraces=" + JSON.stringify(stackTraces) + "\n" +
"ThisShouldNot=end-up-in-the-ping\n";
yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, fileContent);
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, "id1");
Assert.equal(crashes[0].id, crashId);
Assert.equal(crashes[0].type, "main-crash");
Assert.equal(crashes[0].metadata.k1, "v1");
Assert.equal(crashes[0].metadata.k2, "v2");
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, 5);
Assert.equal(Object.getOwnPropertyNames(crashes[0].metadata).length, 6);
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", "k1"], "v1"],
[["payload", "crashId"], "1"],
[["payload", "metadata", "ProductName"], productName],
[["payload", "metadata", "ProductID"], productId],
[["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.deepEqual(found.environment, theEnvironment,
"The saved environment should be present");
Assert.equal(found.payload.metadata.ThisShouldNot, undefined,
"Non-whitelisted fields should be filtered out");
count = yield m.aggregateEventsFiles();
Assert.equal(count, 0);
@ -259,22 +270,29 @@ add_task(function* test_main_crash_event_file() {
add_task(function* test_main_crash_event_file_noenv() {
let ac = new TelemetryArchiveTesting.Checker();
yield ac.promiseInit();
const fileContent = crashId + "\n" +
"ProductName=" + productName + "\n" +
"ProductID=" + productId + "\n";
let m = yield getManager();
yield m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "id1\nk1=v3\nk2=v2");
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, "id1");
Assert.equal(crashes[0].id, crashId);
Assert.equal(crashes[0].type, "main-crash");
Assert.deepEqual(crashes[0].metadata, { k1: "v3", k2: "v2"});
Assert.deepEqual(crashes[0].metadata, {
ProductName: productName,
ProductID: productId
});
Assert.deepEqual(crashes[0].crashDate, DUMMY_DATE);
let found = yield ac.promiseFindPing("crash", [
[["payload", "hasCrashEnvironment"], false],
[["payload", "metadata", "k1"], "v3"],
[["payload", "metadata", "ProductName"], productName],
[["payload", "metadata", "ProductID"], productId],
]);
Assert.ok(found, "Telemetry ping submitted for found crash");
Assert.ok(found.environment, "There is an environment");

Просмотреть файл

@ -183,34 +183,6 @@ static const XP_CHAR extraFileExtension[] = XP_TEXT(".extra");
static const XP_CHAR memoryReportExtension[] = XP_TEXT(".memory.json.gz");
static xpstring *defaultMemoryReportPath = nullptr;
// A whitelist of crash annotations which do not contain sensitive data
// and are saved in the crash record and sent with Firefox Health Report.
static char const * const kCrashEventAnnotations[] = {
"AsyncShutdownTimeout",
"BuildID",
"ProductID",
"ProductName",
"ReleaseChannel",
"SecondsSinceLastCrash",
"ShutdownProgress",
"StartupCrash",
"TelemetryEnvironment",
"Version",
// The following entries are not normal annotations but are included
// in the crash record/FHR:
// "ContainsMemoryReport"
// "EventLoopNestingLevel"
// "IsGarbageCollecting"
// "AvailablePageFile"
// "AvailableVirtualMemory"
// "SystemMemoryUsePercentage"
// "OOMAllocationSize"
// "TotalPageFile"
// "TotalPhysicalMemory"
// "TotalVirtualMemory"
// "MozCrashReason"
};
static const char kCrashMainID[] = "crash.main.2\n";
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
@ -2163,17 +2135,6 @@ static void ReplaceChar(nsCString& str, const nsACString& character,
}
}
static bool
IsInWhitelist(const nsACString& key)
{
for (size_t i = 0; i < ArrayLength(kCrashEventAnnotations); ++i) {
if (key.EqualsASCII(kCrashEventAnnotations[i])) {
return true;
}
}
return false;
}
// This function is miscompiled with MSVC 2005/2008 when PGO is on.
#ifdef _MSC_VER
#pragma optimize("", off)
@ -2310,11 +2271,9 @@ nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data)
nsAutoCString line = key + kEquals + entry + kNewline;
crashReporterAPIData->Append(line);
if (IsInWhitelist(key)) {
crashEventAPIData->Append(line);
}
}
}
return NS_OK;
}