diff options
Diffstat (limited to 'toolkit/components/telemetry/tests/unit/test_ThirdPartyModulesPing.js')
-rw-r--r-- | toolkit/components/telemetry/tests/unit/test_ThirdPartyModulesPing.js | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/unit/test_ThirdPartyModulesPing.js b/toolkit/components/telemetry/tests/unit/test_ThirdPartyModulesPing.js new file mode 100644 index 0000000000..3b25e749bc --- /dev/null +++ b/toolkit/components/telemetry/tests/unit/test_ThirdPartyModulesPing.js @@ -0,0 +1,269 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { Preferences } = ChromeUtils.import( + "resource://gre/modules/Preferences.jsm" +); +const { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm"); +const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); + +const kDllName = "modules-test.dll"; + +let gCurrentPidStr; + +async function load_and_free(name) { + // Dynamically load a DLL which we have hard-coded as untrusted; this should + // appear in the payload. + let dllHandle = ctypes.open(do_get_file(name).path); + if (dllHandle) { + dllHandle.close(); + dllHandle = null; + } + // Give the thread some cycles to process a loading event. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 50)); +} + +add_task(async function setup() { + do_get_profile(); + + // Dynamically load a DLL which we have hard-coded as untrusted; this should + // appear in the payload. + await load_and_free(kDllName); + + // Force the timer to fire (using a small interval). + Cc["@mozilla.org/updates/timer-manager;1"] + .getService(Ci.nsIObserver) + .observe(null, "utm-test-init", ""); + Preferences.set("toolkit.telemetry.untrustedModulesPing.frequency", 0); + Preferences.set("app.update.url", "http://localhost"); + + let currentPid = Services.appinfo.processID; + gCurrentPidStr = "browser.0x" + currentPid.toString(16); + + // Start the local ping server and setup Telemetry to use it during the tests. + PingServer.start(); + Preferences.set( + TelemetryUtils.Preferences.Server, + "http://localhost:" + PingServer.port + ); + + return TelemetryController.testSetup(); +}); + +registerCleanupFunction(function() { + return PingServer.stop(); +}); + +// This tests basic end-to-end functionality of the untrusted modules +// telemetry ping. We force the ping to fire, capture the result, and test for: +// - Basic payload structure validity. +// - Expected results for a few specific DLLs +add_task(async function test_send_ping() { + let expectedModules = [ + // This checks that a DLL loaded during runtime is evaluated properly. + // This is hard-coded as untrusted in toolkit/xre/UntrustedModules.cpp for + // testing purposes. + { + nameMatch: new RegExp(kDllName, "i"), + expectedTrusted: false, + wasFound: false, + }, + { + nameMatch: /kernelbase.dll/i, + expectedTrusted: true, + wasFound: false, + }, + ]; + + // There is a tiny chance some other ping is being sent legitimately before + // the one we care about. Spin until we find the correct ping type. + let found; + while (true) { + found = await PingServer.promiseNextPing(); + if (found.type == "third-party-modules") { + break; + } + } + + // Test the ping payload's validity. + Assert.ok(found, "Untrusted modules ping submitted"); + Assert.ok(found.environment, "Ping has an environment"); + Assert.ok(typeof found.clientId != "undefined", "Ping has a client ID"); + + Assert.equal(found.payload.structVersion, 1, "Version is correct"); + Assert.ok(found.payload.modules, "'modules' object exists"); + Assert.ok(Array.isArray(found.payload.modules), "'modules' is an array"); + Assert.ok(found.payload.processes, "'processes' object exists"); + Assert.ok( + gCurrentPidStr in found.payload.processes, + `Current process "${gCurrentPidStr}" is included in payload` + ); + + let ourProcInfo = found.payload.processes[gCurrentPidStr]; + Assert.equal(ourProcInfo.processType, "browser", "'processType' is correct"); + Assert.ok(typeof ourProcInfo.elapsed == "number", "'elapsed' exists"); + Assert.equal( + ourProcInfo.sanitizationFailures, + 0, + "'sanitizationFailures' is 0" + ); + Assert.equal(ourProcInfo.trustTestFailures, 0, "'trustTestFailures' is 0"); + + Assert.equal( + ourProcInfo.combinedStacks.stacks.length, + ourProcInfo.events.length, + "combinedStacks.stacks.length == events.length" + ); + + for (let event of ourProcInfo.events) { + Assert.ok( + typeof event.processUptimeMS == "number", + "'processUptimeMS' exists" + ); + Assert.ok(typeof event.threadID == "number", "'threadID' exists"); + Assert.ok(typeof event.baseAddress == "string", "'baseAddress' exists"); + + Assert.ok(typeof event.moduleIndex == "number", "'moduleIndex' exists"); + Assert.ok(event.moduleIndex >= 0, "'moduleIndex' is non-negative"); + + Assert.ok(typeof event.isDependent == "boolean", "'isDependent' exists"); + Assert.ok(!event.isDependent, "'isDependent' is false"); + + Assert.ok(typeof event.loadStatus == "number", "'loadStatus' exists"); + Assert.ok(event.loadStatus == 0, "'loadStatus' is 0 (Loaded)"); + + let modRecord = found.payload.modules[event.moduleIndex]; + Assert.ok(modRecord, "module record for this event exists"); + Assert.ok( + typeof modRecord.resolvedDllName == "string", + "'resolvedDllName' exists" + ); + Assert.ok(typeof modRecord.trustFlags == "number", "'trustFlags' exists"); + + let mod = expectedModules.find(function(elem) { + return elem.nameMatch.test(modRecord.resolvedDllName); + }); + + if (mod) { + mod.wasFound = true; + } + } + + for (let x of expectedModules) { + Assert.equal( + !x.wasFound, + x.expectedTrusted, + `Trustworthiness == expected for module: ${x.nameMatch.source}` + ); + } +}); + +// This tests the flags INCLUDE_OLD_LOADEVENTS and KEEP_LOADEVENTS_NEW +// controls the method's return value and the internal storages +// "Staging" and "Settled" correctly. +add_task(async function test_new_old_instances() { + const kIncludeOld = Telemetry.INCLUDE_OLD_LOADEVENTS; + const kKeepNew = Telemetry.KEEP_LOADEVENTS_NEW; + const get_events_count = data => data.processes[gCurrentPidStr].events.length; + + // Make sure |baseline| has at least one instance. + await load_and_free(kDllName); + + // Make sure all instances are "old" + const baseline = await Telemetry.getUntrustedModuleLoadEvents(kIncludeOld); + const baseline_count = get_events_count(baseline); + print("baseline_count = " + baseline_count); + print("baseline = " + JSON.stringify(baseline)); + + await Assert.rejects( + Telemetry.getUntrustedModuleLoadEvents(), + e => e.result == Cr.NS_ERROR_NOT_AVAILABLE, + "New instances should not exist!" + ); + + await load_and_free(kDllName); // A + + // Passing kIncludeOld and kKeepNew is unsupported. A is kept new. + await Assert.rejects( + Telemetry.getUntrustedModuleLoadEvents(kIncludeOld | kKeepNew), + e => e.result == Cr.NS_ERROR_INVALID_ARG, + "Passing unsupported flag combination should throw an exception!" + ); + + await load_and_free(kDllName); // B + + // After newly loading B, the new instances we have is {A, B} + // Both A and B are still kept new. + let payload = await Telemetry.getUntrustedModuleLoadEvents(kKeepNew); + print("payload = " + JSON.stringify(payload)); + Assert.equal(get_events_count(payload), 2); + + await load_and_free(kDllName); // C + + // After newly loading C, the new instances we have is {A, B, C} + // All of A, B, and C are now marked as old. + payload = await Telemetry.getUntrustedModuleLoadEvents(); + Assert.equal(get_events_count(payload), 3); + + payload = await Telemetry.getUntrustedModuleLoadEvents(kIncludeOld); + // payload is {baseline, A, B, C} + Assert.equal(get_events_count(payload), baseline_count + 3); +}); + +// This tests the flag INCLUDE_PRIVATE_FIELDS_IN_LOADEVENTS returns +// data including private fields. +add_task(async function test_private_fields() { + await load_and_free(kDllName); + const data = await Telemetry.getUntrustedModuleLoadEvents( + Telemetry.KEEP_LOADEVENTS_NEW | + Telemetry.INCLUDE_PRIVATE_FIELDS_IN_LOADEVENTS + ); + + for (const module of data.modules) { + Assert.ok(!("resolvedDllName" in module)); + Assert.ok("dllFile" in module); + Assert.ok(module.dllFile.QueryInterface); + Assert.ok(module.dllFile.QueryInterface(Ci.nsIFile)); + } +}); + +// This tests the flag EXCLUDE_STACKINFO_FROM_LOADEVENTS correctly +// merges "Staging" and "Settled" on a JS object correctly, and +// the "combinedStacks" field is really excluded. +add_task(async function test_exclude_stack() { + const baseline = await Telemetry.getUntrustedModuleLoadEvents( + Telemetry.EXCLUDE_STACKINFO_FROM_LOADEVENTS | + Telemetry.INCLUDE_OLD_LOADEVENTS + ); + Assert.ok(!("combinedStacks" in baseline.processes[gCurrentPidStr])); + const baseSet = baseline.processes[gCurrentPidStr].events.map( + x => x.processUptimeMS + ); + + await load_and_free(kDllName); + await load_and_free(kDllName); + const newLoadsWithStack = await Telemetry.getUntrustedModuleLoadEvents( + Telemetry.KEEP_LOADEVENTS_NEW + ); + Assert.ok("combinedStacks" in newLoadsWithStack.processes[gCurrentPidStr]); + const newSet = newLoadsWithStack.processes[gCurrentPidStr].events.map( + x => x.processUptimeMS + ); + + const merged = baseSet.concat(newSet); + + const allData = await Telemetry.getUntrustedModuleLoadEvents( + Telemetry.KEEP_LOADEVENTS_NEW | + Telemetry.EXCLUDE_STACKINFO_FROM_LOADEVENTS | + Telemetry.INCLUDE_OLD_LOADEVENTS + ); + Assert.ok(!("combinedStacks" in allData.processes[gCurrentPidStr])); + const allSet = allData.processes[gCurrentPidStr].events.map( + x => x.processUptimeMS + ); + + Assert.deepEqual(allSet.sort(), merged.sort()); +}); |