diff options
Diffstat (limited to 'toolkit')
20 files changed, 435 insertions, 67 deletions
diff --git a/toolkit/components/glean/bindings/private/TimingDistribution.cpp b/toolkit/components/glean/bindings/private/TimingDistribution.cpp index 78aad6d4cd..5a1a6d3daf 100644 --- a/toolkit/components/glean/bindings/private/TimingDistribution.cpp +++ b/toolkit/components/glean/bindings/private/TimingDistribution.cpp @@ -25,7 +25,7 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionStart( auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId); if (mirrorId) { mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) { - auto tuple = std::make_tuple(aMetricId, aTimerId); + auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId}; // It should be all but impossible for anyone to have already inserted // this timer for this metric given the monotonicity of timer ids. (void)NS_WARN_IF(lock.ref()->Remove(tuple)); @@ -40,7 +40,8 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionStopAndAccumulate( auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId); if (mirrorId) { mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) { - auto optStart = lock.ref()->Extract(std::make_tuple(aMetricId, aTimerId)); + auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId}; + auto optStart = lock.ref()->Extract(tuple); // The timer might not be in the map to be removed if it's already been // cancelled or stop_and_accumulate'd. if (!NS_WARN_IF(!optStart)) { @@ -67,8 +68,8 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionCancel( mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) { // The timer might not be in the map to be removed if it's already been // cancelled or stop_and_accumulate'd. - (void)NS_WARN_IF( - !lock.ref()->Remove(std::make_tuple(aMetricId, aTimerId))); + auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId}; + (void)NS_WARN_IF(!lock.ref()->Remove(tuple)); }); } } diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja2 index 4a58ef5411..9d0c10cdf6 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja2 +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja2 @@ -35,7 +35,10 @@ using Telemetry::{{ probe_type }}ID; using MetricId = uint32_t; // Same type as in api/src/private/mod.rs using TimerId = uint64_t; // Same as in TimingDistribution.h. -using MetricTimerTuple = std::tuple<MetricId, TimerId>; +struct MetricTimerTuple { + MetricId mMetricId; + TimerId mTimerId; +}; class MetricTimerTupleHashKey : public PLDHashEntryHdr { public: using KeyType = const MetricTimerTuple&; @@ -49,15 +52,17 @@ class MetricTimerTupleHashKey : public PLDHashEntryHdr { KeyType GetKey() const { return mValue; } bool KeyEquals(KeyTypePointer aKey) const { - return std::get<0>(*aKey) == std::get<0>(mValue) && std::get<1>(*aKey) == std::get<1>(mValue); + return aKey->mMetricId == mValue.mMetricId && + aKey->mTimerId == mValue.mTimerId; } static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } static PLDHashNumber HashKey(KeyTypePointer aKey) { // Chosen because this is how nsIntegralHashKey does it. - return HashGeneric(std::get<0>(*aKey), std::get<1>(*aKey)); + return HashGeneric(aKey->mMetricId, aKey->mTimerId); } enum { ALLOW_MEMMOVE = true }; + static_assert(std::is_trivially_copyable_v<MetricTimerTuple>); private: const MetricTimerTuple mValue; @@ -170,6 +175,7 @@ class ScalarIDHashKey : public PLDHashEntryHdr { return static_cast<std::underlying_type<ScalarID>::type>(*aKey); } enum { ALLOW_MEMMOVE = true }; + static_assert(std::is_trivially_copyable_v<ScalarID>); private: const ScalarID mValue; diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Histogram b/toolkit/components/glean/tests/pytest/gifft_output_Histogram index b13df96594..f0e4c3d047 100644 --- a/toolkit/components/glean/tests/pytest/gifft_output_Histogram +++ b/toolkit/components/glean/tests/pytest/gifft_output_Histogram @@ -28,7 +28,10 @@ using Telemetry::HistogramID; using MetricId = uint32_t; // Same type as in api/src/private/mod.rs using TimerId = uint64_t; // Same as in TimingDistribution.h. -using MetricTimerTuple = std::tuple<MetricId, TimerId>; +struct MetricTimerTuple { + MetricId mMetricId; + TimerId mTimerId; +}; class MetricTimerTupleHashKey : public PLDHashEntryHdr { public: using KeyType = const MetricTimerTuple&; @@ -42,15 +45,17 @@ class MetricTimerTupleHashKey : public PLDHashEntryHdr { KeyType GetKey() const { return mValue; } bool KeyEquals(KeyTypePointer aKey) const { - return std::get<0>(*aKey) == std::get<0>(mValue) && std::get<1>(*aKey) == std::get<1>(mValue); + return aKey->mMetricId == mValue.mMetricId && + aKey->mTimerId == mValue.mTimerId; } static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } static PLDHashNumber HashKey(KeyTypePointer aKey) { // Chosen because this is how nsIntegralHashKey does it. - return HashGeneric(std::get<0>(*aKey), std::get<1>(*aKey)); + return HashGeneric(aKey->mMetricId, aKey->mTimerId); } enum { ALLOW_MEMMOVE = true }; + static_assert(std::is_trivially_copyable_v<MetricTimerTuple>); private: const MetricTimerTuple mValue; diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Scalar b/toolkit/components/glean/tests/pytest/gifft_output_Scalar index 66d25a2629..1ea7517407 100644 --- a/toolkit/components/glean/tests/pytest/gifft_output_Scalar +++ b/toolkit/components/glean/tests/pytest/gifft_output_Scalar @@ -92,6 +92,7 @@ class ScalarIDHashKey : public PLDHashEntryHdr { return static_cast<std::underlying_type<ScalarID>::type>(*aKey); } enum { ALLOW_MEMMOVE = true }; + static_assert(std::is_trivially_copyable_v<ScalarID>); private: const ScalarID mValue; diff --git a/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs b/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs index d571e46167..1f6be470aa 100644 --- a/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs +++ b/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs @@ -76,10 +76,7 @@ export class ShowHeartbeatAction extends BaseAction { learnMoreUrl, postAnswerUrl: await this.generatePostAnswerURL(recipe), flowId: lazy.NormandyUtils.generateUuid(), - // Recipes coming from Nimbus won't have a revision_id. - ...(Object.hasOwn(recipe, "revision_id") - ? { surveyVersion: recipe.revision_id } - : {}), + surveyVersion: recipe.revision_id, }); heartbeat.eventEmitter.once( diff --git a/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs b/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs index 501c9f70af..93c24faf5d 100644 --- a/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs +++ b/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs @@ -43,6 +43,7 @@ export const LegacyHeartbeat = { capabilities: ["action.show-heartbeat"], filter_expression: "true", use_only_baseline_capabilities: true, + revision_id: "1", // Required for the Heartbeat telemetry ping. }; }, }; diff --git a/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js b/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js index 465e5c1040..a40fa7350e 100644 --- a/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js +++ b/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js @@ -9,6 +9,9 @@ const { BaseAction } = ChromeUtils.importESModule( const { ClientEnvironment } = ChromeUtils.importESModule( "resource://normandy/lib/ClientEnvironment.sys.mjs" ); +const { EventEmitter } = ChromeUtils.importESModule( + "resource://normandy/lib/EventEmitter.sys.mjs" +); const { Heartbeat } = ChromeUtils.importESModule( "resource://normandy/lib/Heartbeat.sys.mjs" ); @@ -27,6 +30,9 @@ const { RecipeRunner } = ChromeUtils.importESModule( const { RemoteSettings } = ChromeUtils.importESModule( "resource://services-settings/remote-settings.sys.mjs" ); +const { JsonSchema } = ChromeUtils.importESModule( + "resource://gre/modules/JsonSchema.sys.mjs" +); const SURVEY = { surveyId: "a survey", @@ -39,9 +45,80 @@ const SURVEY = { repeatOption: "once", }; +// See properties.payload in +// https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/main/schemas/telemetry/heartbeat/heartbeat.4.schema.json + +const PAYLOAD_SCHEMA = { + additionalProperties: false, + anyOf: [ + { + required: ["closedTS"], + }, + { + required: ["windowClosedTS"], + }, + ], + properties: { + closedTS: { + minimum: 0, + type: "integer", + }, + engagedTS: { + minimum: 0, + type: "integer", + }, + expiredTS: { + minimum: 0, + type: "integer", + }, + flowId: { + pattern: + "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", + type: "string", + }, + learnMoreTS: { + minimum: 0, + type: "integer", + }, + offeredTS: { + minimum: 0, + type: "integer", + }, + score: { + minimum: 1, + type: "integer", + }, + surveyId: { + type: "string", + }, + surveyVersion: { + pattern: "^([0-9]+|[a-fA-F0-9]{64})$", + type: "string", + }, + testing: { + type: "boolean", + }, + version: { + maximum: 1, + minimum: 1, + type: "number", + }, + votedTS: { + minimum: 0, + type: "integer", + }, + windowClosedTS: { + minimum: 0, + type: "integer", + }, + }, + required: ["version", "flowId", "offeredTS", "surveyId", "surveyVersion"], + type: "object", +}; + function assertSurvey(actual, expected) { for (const key of Object.keys(actual)) { - if (["postAnswerUrl", "flowId"].includes(key)) { + if (["flowId", "postAnswerUrl", "surveyVersion"].includes(key)) { continue; } @@ -52,6 +129,7 @@ function assertSurvey(actual, expected) { ); } + Assert.equal(actual.surveyVersion, "1"); Assert.ok(actual.postAnswerUrl.startsWith(expected.postAnswerUrl)); } @@ -86,3 +164,62 @@ decorate_task( } } ); + +decorate_task( + withClearStorage(), + async function testLegacyHeartbeatPingPayload() { + const sandbox = sinon.createSandbox(); + + const cleanupEnrollment = await ExperimentFakes.enrollWithFeatureConfig({ + featureId: "legacyHeartbeat", + value: { + survey: SURVEY, + }, + }); + + const client = RemoteSettings("normandy-recipes-capabilities"); + sandbox.stub(client, "get").resolves([]); + + // Override Heartbeat so we can get the instance and manipulate it directly. + let resolveHeartbeatPromise; + const heartbeatPromise = new Promise(resolve => { + resolveHeartbeatPromise = resolve; + }); + class TestHeartbeat extends Heartbeat { + constructor(...args) { + super(...args); + resolveHeartbeatPromise(this); + } + } + ShowHeartbeatAction.overrideHeartbeatForTests(TestHeartbeat); + + try { + await RecipeRunner.run(); + const heartbeat = await heartbeatPromise; + // We are going to simulate the timer timing out, so we do not want it to + // *actually* time out. + heartbeat.endTimerIfPresent("surveyEndTimer"); + + const telemetrySentPromise = new Promise(resolve => { + heartbeat.eventEmitter.once("TelemetrySent", payload => + resolve(payload) + ); + }); + + // This method would be triggered when the timer timed out. This will + // trigger telemetry to be submitted. + heartbeat.close(); + + const payload = await telemetrySentPromise; + + const result = JsonSchema.validate(payload, PAYLOAD_SCHEMA); + Assert.ok(result.valid); + Assert.equal(payload.surveyVersion, "1"); + + await cleanupEnrollment(); + } finally { + ShowHeartbeatAction.overrideHeartbeatForTests(); + sandbox.restore(); + } + } +); diff --git a/toolkit/components/pdfjs/content/build/pdf.js b/toolkit/components/pdfjs/content/build/pdf.js index f59bc41651..017a45914e 100644 --- a/toolkit/components/pdfjs/content/build/pdf.js +++ b/toolkit/components/pdfjs/content/build/pdf.js @@ -9375,11 +9375,13 @@ exports.FreeTextEditor = void 0; var _util = __w_pdfjs_require__(1); var _tools = __w_pdfjs_require__(5); var _editor = __w_pdfjs_require__(4); +const EOL_PATTERN = /\r\n?|\n/g; class FreeTextEditor extends _editor.AnnotationEditor { #boundEditorDivBlur = this.editorDivBlur.bind(this); #boundEditorDivFocus = this.editorDivFocus.bind(this); #boundEditorDivInput = this.editorDivInput.bind(this); #boundEditorDivKeydown = this.editorDivKeydown.bind(this); + #boundEditorDivPaste = this.editorDivPaste.bind(this); #color; #content = ""; #editorDivId = `${this.id}-editor`; @@ -9495,6 +9497,7 @@ class FreeTextEditor extends _editor.AnnotationEditor { this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.addEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.addEventListener("input", this.#boundEditorDivInput); + this.editorDiv.addEventListener("paste", this.#boundEditorDivPaste); } disableEditMode() { if (!this.isInEditMode()) { @@ -9510,6 +9513,7 @@ class FreeTextEditor extends _editor.AnnotationEditor { this.editorDiv.removeEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.removeEventListener("input", this.#boundEditorDivInput); + this.editorDiv.removeEventListener("paste", this.#boundEditorDivPaste); this.div.focus({ preventScroll: true }); @@ -9539,13 +9543,10 @@ class FreeTextEditor extends _editor.AnnotationEditor { super.remove(); } #extractText() { - const divs = this.editorDiv.getElementsByTagName("div"); - if (divs.length === 0) { - return this.editorDiv.innerText; - } const buffer = []; - for (const div of divs) { - buffer.push(div.innerText.replace(/\r\n?|\n/, "")); + this.editorDiv.normalize(); + for (const child of this.editorDiv.childNodes) { + buffer.push(FreeTextEditor.#getNodeContent(child)); } return buffer.join("\n"); } @@ -9658,6 +9659,102 @@ class FreeTextEditor extends _editor.AnnotationEditor { } return this.div; } + static #getNodeContent(node) { + return (node.nodeType === Node.TEXT_NODE ? node.nodeValue : node.innerText).replaceAll(EOL_PATTERN, ""); + } + editorDivPaste(event) { + const clipboardData = event.clipboardData || window.clipboardData; + const { + types + } = clipboardData; + if (types.length === 1 && types[0] === "text/plain") { + return; + } + event.preventDefault(); + const paste = FreeTextEditor.#deserializeContent(clipboardData.getData("text") || "").replaceAll(EOL_PATTERN, "\n"); + if (!paste) { + return; + } + const selection = window.getSelection(); + if (!selection.rangeCount) { + return; + } + this.editorDiv.normalize(); + selection.deleteFromDocument(); + const range = selection.getRangeAt(0); + if (!paste.includes("\n")) { + range.insertNode(document.createTextNode(paste)); + this.editorDiv.normalize(); + selection.collapseToStart(); + return; + } + const { + startContainer, + startOffset + } = range; + const bufferBefore = []; + const bufferAfter = []; + if (startContainer.nodeType === Node.TEXT_NODE) { + const parent = startContainer.parentElement; + bufferAfter.push(startContainer.nodeValue.slice(startOffset).replaceAll(EOL_PATTERN, "")); + if (parent !== this.editorDiv) { + let buffer = bufferBefore; + for (const child of this.editorDiv.childNodes) { + if (child === parent) { + buffer = bufferAfter; + continue; + } + buffer.push(FreeTextEditor.#getNodeContent(child)); + } + } + bufferBefore.push(startContainer.nodeValue.slice(0, startOffset).replaceAll(EOL_PATTERN, "")); + } else if (startContainer === this.editorDiv) { + let buffer = bufferBefore; + let i = 0; + for (const child of this.editorDiv.childNodes) { + if (i++ === startOffset) { + buffer = bufferAfter; + } + buffer.push(FreeTextEditor.#getNodeContent(child)); + } + } + this.#content = `${bufferBefore.join("\n")}${paste}${bufferAfter.join("\n")}`; + this.#setContent(); + const newRange = new Range(); + let beforeLength = bufferBefore.reduce((acc, line) => acc + line.length, 0); + for (const { + firstChild + } of this.editorDiv.childNodes) { + if (firstChild.nodeType === Node.TEXT_NODE) { + const length = firstChild.nodeValue.length; + if (beforeLength <= length) { + newRange.setStart(firstChild, beforeLength); + newRange.setEnd(firstChild, beforeLength); + break; + } + beforeLength -= length; + } + } + selection.removeAllRanges(); + selection.addRange(newRange); + } + #setContent() { + this.editorDiv.replaceChildren(); + if (!this.#content) { + return; + } + for (const line of this.#content.split("\n")) { + const div = document.createElement("div"); + div.append(line ? document.createTextNode(line) : document.createElement("br")); + this.editorDiv.append(div); + } + } + #serializeContent() { + return this.#content.replaceAll("\xa0", " "); + } + static #deserializeContent(content) { + return content.replaceAll(" ", "\xa0"); + } get contentDiv() { return this.editorDiv; } diff --git a/toolkit/components/prompts/src/PromptUtils.sys.mjs b/toolkit/components/prompts/src/PromptUtils.sys.mjs index 7a15802b84..8c1b0ff992 100644 --- a/toolkit/components/prompts/src/PromptUtils.sys.mjs +++ b/toolkit/components/prompts/src/PromptUtils.sys.mjs @@ -76,6 +76,12 @@ export var EnableDelayHelper = function ({ this.focusTarget.addEventListener("keydown", this, true); this.focusTarget.document.addEventListener("unload", this); + // If we're not part of the active window, don't even start the timer yet. + let topWin = focusTarget.browsingContext.top.window; + if (topWin != Services.focus.activeWindow) { + return; + } + this.startOnFocusDelay(); }; diff --git a/toolkit/components/reputationservice/ApplicationReputation.cpp b/toolkit/components/reputationservice/ApplicationReputation.cpp index cc4045d5ad..8973fc85bb 100644 --- a/toolkit/components/reputationservice/ApplicationReputation.cpp +++ b/toolkit/components/reputationservice/ApplicationReputation.cpp @@ -519,20 +519,20 @@ const char* const ApplicationReputationService::kBinaryFileExtensions[] = { ".xlam", // MS Excel ".xldm", // MS Excel //".xll", exec // MS Excel - ".xlm", // MS Excel - ".xls", // MS Excel - ".xlsb", // MS Excel - ".xlsm", // MS Excel - ".xlsx", // MS Excel - ".xlt", // MS Excel - ".xltm", // MS Excel - ".xltx", // MS Excel - ".xlw", // MS Excel - ".xml", // MS Excel - ".xnk", // MS Exchange - ".xrm-ms", // Windows - ".xsd", // XML schema definition - ".xsl", // XML Stylesheet + ".xlm", // MS Excel + ".xls", // MS Excel + ".xlsb", // MS Excel + ".xlsm", // MS Excel + ".xlsx", // MS Excel + ".xlt", // MS Excel + ".xltm", // MS Excel + ".xltx", // MS Excel + ".xlw", // MS Excel + ".xml", // MS Excel + ".xnk", // MS Exchange + //".xrm-ms", exec // Windows + ".xsd", // XML schema definition + ".xsl", // XML Stylesheet //".xxe", ".xz", // Linux archive (xz) ".z", // InstallShield diff --git a/toolkit/components/reputationservice/ApplicationReputation.h b/toolkit/components/reputationservice/ApplicationReputation.h index f708c6eb71..5ea5b825f7 100644 --- a/toolkit/components/reputationservice/ApplicationReputation.h +++ b/toolkit/components/reputationservice/ApplicationReputation.h @@ -27,9 +27,9 @@ class ApplicationReputationService final public: static const char* const kNonBinaryExecutables[5]; #ifdef XP_WIN - static const char* const kBinaryFileExtensions[185]; -#else static const char* const kBinaryFileExtensions[184]; +#else + static const char* const kBinaryFileExtensions[183]; #endif static already_AddRefed<ApplicationReputationService> GetSingleton(); diff --git a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc index e13e4509b0..64c01bdce4 100644 --- a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc +++ b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc @@ -83,7 +83,7 @@ MinidumpGenerator::MinidumpGenerator() cpu_type_(DynamicImages::GetNativeCPUType()), dyldImageLoadAddress_(NULL), dyldSlide_(0), - dyldPath_(), + dyldPath_(nullptr), task_context_(NULL), dynamic_images_(NULL), memory_blocks_(&allocator_) { @@ -105,7 +105,7 @@ MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task, cpu_type_(DynamicImages::GetNativeCPUType()), dyldImageLoadAddress_(NULL), dyldSlide_(0), - dyldPath_(), + dyldPath_(nullptr), task_context_(NULL), dynamic_images_(NULL), memory_blocks_(&allocator_) { @@ -250,7 +250,7 @@ void MinidumpGenerator::GatherCurrentProcessDyldInformation() { return; } dyldImageLoadAddress_ = mh; - dyldPath_ = string(aii->dyldPath); + dyldPath_ = aii->dyldPath; dyldSlide_ = GetCurrentProcessModuleSlide(mh, aii->sharedCacheSlide); } @@ -1464,7 +1464,7 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index, if (index == INT_MAX) { dyld_or_in_dyld_shared_cache = true; slide = dyldSlide_; - name = dyldPath_.c_str(); + name = dyldPath_; } else { dyld_or_in_dyld_shared_cache = ((header->flags & MH_SHAREDCACHE) != 0); @@ -1993,7 +1993,7 @@ bool MinidumpGenerator::WriteCrashInfoStream( bool dyld_or_in_dyld_shared_cache; if (i == image_count - 1) { slide = dyldSlide_; - module_path = dyldPath_.c_str(); + module_path = dyldPath_; dyld_or_in_dyld_shared_cache = true; } else { slide = _dyld_get_image_vmaddr_slide(i); @@ -2048,7 +2048,10 @@ bool MinidumpGenerator::WriteBootargsStream( int rv = sysctlbyname("kern.bootargs", NULL, &size, NULL, 0); if ((rv != 0) || (size == 0)) size = 1; - vector<uint8_t> bootargs(size); + + wasteful_vector<uint8_t> bootargs(&this->allocator_, size); + bootargs.resize(size, 0); + bootargs[0] = 0; if (rv == 0) sysctlbyname("kern.bootargs", &bootargs[0], &size, NULL, 0); diff --git a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h index aba067cc04..77c250ccd5 100644 --- a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h +++ b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h @@ -266,7 +266,8 @@ class MinidumpGenerator { // process has crashed. breakpad_mach_header* dyldImageLoadAddress_; ptrdiff_t dyldSlide_; - string dyldPath_; + // We don't own this pointer, this is owned by macOS internal structures. + const char* dyldPath_; // Context of the task to dump. breakpad_ucontext_t *task_context_; diff --git a/toolkit/modules/GMPInstallManager.sys.mjs b/toolkit/modules/GMPInstallManager.sys.mjs index 9cb4802e58..421a843c71 100644 --- a/toolkit/modules/GMPInstallManager.sys.mjs +++ b/toolkit/modules/GMPInstallManager.sys.mjs @@ -43,6 +43,16 @@ const LOCAL_GMP_SOURCES = [ }, ]; +function getLocalSources() { + if (GMPPrefs.getBool(GMPPrefs.KEY_ALLOW_LOCAL_SOURCES, true)) { + return LOCAL_GMP_SOURCES; + } + + let log = getScopedLogger("GMPInstallManager.downloadLocalConfig"); + log.info("ignoring local sources"); + return []; +} + function downloadJSON(uri) { let log = getScopedLogger("GMPInstallManager.checkForAddons"); log.info("fetching config from: " + uri); @@ -70,7 +80,7 @@ function downloadJSON(uri) { function downloadLocalConfig() { let log = getScopedLogger("GMPInstallManager.downloadLocalConfig"); return Promise.all( - LOCAL_GMP_SOURCES.map(conf => { + getLocalSources().map(conf => { return downloadJSON(conf.src).then(addons => { let platforms = addons.vendors[conf.id].platforms; let target = Services.appinfo.OS + "_" + lazy.UpdateUtils.ABI; @@ -146,6 +156,36 @@ GMPInstallManager.prototype = { }, /** + * Determines the root to use for verifying content signatures. + * @param url + * The Balrog URL, i.e. the return value of _getURL(). + */ + _getContentSignatureRootForURL(url) { + // The prod and stage URLs of Balrog are documented at: + // https://mozilla-balrog.readthedocs.io/en/latest/infrastructure.html + // Note: we are matching by prefix without the full domain nor slash, to + // enable us to move to a different host name in the future if desired. + if (url.startsWith("https://aus")) { + return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot; + } + if (url.startsWith("https://stage.")) { + return Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot; + } + if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) { + return Ci.nsIX509CertDB.AppXPCShellRoot; + } + // When content signature verification for GMP was added (bug 1714621), a + // pref existed to configure an arbitrary root, which enabled local testing. + // This pref was removed later in bug 1769669, and replaced with hard-coded + // roots (prod and tests only). Support for testing against the stage server + // was restored in bug 1771992. + // Note: other verifiers ultimately fall back to ContentSignatureLocalRoot, + // to support local development. Here we use ContentSignatureProdRoot to + // minimize risk (and the unclear demand for "local" development). + return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot; + }, + + /** * Records telemetry results on if fetching update.xml from Balrog succeeded * when content signature was used to verify the response from Balrog. * @param didGetAddonList @@ -318,15 +358,17 @@ GMPInstallManager.prototype = { } let url = await this._getURL(); + let trustedContentSignatureRoot = this._getContentSignatureRootForURL(url); log.info( - `Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}` + `Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}, trustedContentSignatureRoot=${trustedContentSignatureRoot}` ); let addonPromise = ProductAddonChecker.getProductAddonList( url, allowNonBuiltIn, certs, - checkContentSignature + checkContentSignature, + trustedContentSignatureRoot ) .then(res => { if (checkContentSignature) { diff --git a/toolkit/modules/GMPUtils.sys.mjs b/toolkit/modules/GMPUtils.sys.mjs index 488a024d13..c21576e0a9 100644 --- a/toolkit/modules/GMPUtils.sys.mjs +++ b/toolkit/modules/GMPUtils.sys.mjs @@ -124,6 +124,7 @@ export var GMPPrefs = { KEY_PLUGIN_ABI: "media.{0}.abi", KEY_PLUGIN_FORCE_SUPPORTED: "media.{0}.forceSupported", KEY_PLUGIN_ALLOW_X64_ON_ARM64: "media.{0}.allow-x64-plugin-on-arm64", + KEY_ALLOW_LOCAL_SOURCES: "media.gmp-manager.allowLocalSources", KEY_URL: "media.gmp-manager.url", KEY_URL_OVERRIDE: "media.gmp-manager.url.override", KEY_CERT_CHECKATTRS: "media.gmp-manager.cert.checkAttributes", diff --git a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js index 87de57efaf..e755690e6c 100644 --- a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js +++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js @@ -802,6 +802,60 @@ add_task(async function test_checkForAddons_contentSignatureFailure() { }); /** + * Tests that the signature verification URL is as expected. + */ +add_task(async function test_checkForAddons_get_verifier_url() { + const previousUrlOverride = setupContentSigTestPrefs(); + + let installManager = new GMPInstallManager(); + // checkForAddons() calls _getContentSignatureRootForURL() with the return + // value of _getURL(), which is effectively KEY_URL_OVERRIDE or KEY_URL + // followed by some normalization. + const rootForUrl = async () => { + const url = await installManager._getURL(); + return installManager._getContentSignatureRootForURL(url); + }; + + Assert.equal( + await rootForUrl(), + Ci.nsIX509CertDB.AppXPCShellRoot, + "XPCShell root used by default in xpcshell test" + ); + + const defaultPrefs = Services.prefs.getDefaultBranch(""); + const defaultUrl = defaultPrefs.getStringPref(GMPPrefs.KEY_URL); + Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, defaultUrl); + Assert.equal( + await rootForUrl(), + Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot, + "Production cert should be used for the default Balrog URL: " + defaultUrl + ); + + // The current Balrog endpoint is at aus5.mozilla.org. Confirm that the prod + // cert is used even if we bump the version (e.g. aus6): + const potentialProdUrl = "https://aus1337.mozilla.org/potential/prod/URL"; + Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, potentialProdUrl); + Assert.equal( + await rootForUrl(), + Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot, + "Production cert should be used for: " + potentialProdUrl + ); + + // Stage URL documented at https://mozilla-balrog.readthedocs.io/en/latest/infrastructure.html + const stageUrl = "https://stage.balrog.nonprod.cloudops.mozgcp.net/etc."; + Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, stageUrl); + Assert.equal( + await rootForUrl(), + Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot, + "Stage cert should be used with the stage URL: " + stageUrl + ); + + installManager.uninit(); + + revertContentSigTestPrefs(previousUrlOverride); +}); + +/** * Tests that checkForAddons() works as expected when certificate pinning * checking is enabled. We plan to move away from cert pinning in favor of * content signature checks, but part of doing this is comparing the telemetry diff --git a/toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs b/toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs index 1615a551c8..f76ebf0d30 100644 --- a/toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs +++ b/toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs @@ -118,12 +118,18 @@ async function conservativeFetch(input) { * @param contentSignatureHeader * The contents of the 'content-signature' header received along with * `data`. + * @param trustedRoot + * The identifier of the trusted root to use for certificate validation. * @return A promise that will resolve to nothing if the signature verification * succeeds, or rejects on failure, with an Error that sets its * addonCheckerErr property disambiguate failure cases and a message * explaining the error. */ -async function verifyGmpContentSignature(data, contentSignatureHeader) { +async function verifyGmpContentSignature( + data, + contentSignatureHeader, + trustedRoot +) { if (!contentSignatureHeader) { logger.warn( "Unexpected missing content signature header during content signature validation" @@ -186,13 +192,6 @@ async function verifyGmpContentSignature(data, contentSignatureHeader) { "@mozilla.org/security/contentsignatureverifier;1" ].createInstance(Ci.nsIContentSignatureVerifier); - // See bug 1771992. In the future, this may need to handle staging and dev - // environments in addition to just production and testing. - let root = Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot; - if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) { - root = Ci.nsIX509CertDB.AppXPCShellRoot; - } - let valid; try { valid = await verifier.asyncVerifyContentSignature( @@ -200,7 +199,7 @@ async function verifyGmpContentSignature(data, contentSignatureHeader) { signature, certChain, "aus.content-signature.mozilla.org", - root + trustedRoot ); } catch (err) { logger.warn(`Unexpected error while validating content signature: ${err}`); @@ -329,6 +328,9 @@ function downloadXMLWithRequest( * @param verifyContentSignature * When true, will verify the content signature information from the * response header. Failure to verify will result in an error. + * @param trustedContentSignatureRoot + * The trusted root to use for certificate validation. + * Must be set if verifyContentSignature is true. * @return a promise that resolves to the DOM document downloaded or rejects * with a JS exception in case of error. */ @@ -336,7 +338,8 @@ async function downloadXML( url, allowNonBuiltIn = false, allowedCerts = null, - verifyContentSignature = false + verifyContentSignature = false, + trustedContentSignatureRoot = null ) { let request = await downloadXMLWithRequest( url, @@ -346,7 +349,8 @@ async function downloadXML( if (verifyContentSignature) { await verifyGmpContentSignature( request.response, - request.getResponseHeader("content-signature") + request.getResponseHeader("content-signature"), + trustedContentSignatureRoot ); } return request.responseXML; @@ -535,6 +539,9 @@ export const ProductAddonChecker = { * @param verifyContentSignature * When true, will verify the content signature information from the * response header. Failure to verify will result in an error. + * @param trustedContentSignatureRoot + * The trusted root to use for certificate validation. + * Must be set if verifyContentSignature is true. * @return a promise that resolves to an object containing the list of add-ons * and whether the local fallback was used, or rejects with a JS * exception in case of error. In the case of an error, a best effort @@ -545,13 +552,15 @@ export const ProductAddonChecker = { url, allowNonBuiltIn = false, allowedCerts = null, - verifyContentSignature = false + verifyContentSignature = false, + trustedContentSignatureRoot = null ) { return downloadXML( url, allowNonBuiltIn, allowedCerts, - verifyContentSignature + verifyContentSignature, + trustedContentSignatureRoot ).then(parseXML); }, diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js index 5ae61568ef..93602ffac9 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js @@ -98,7 +98,8 @@ add_task(async function test_valid_content_signature() { signedBaseUri + goodXmlPath + "?" + validSignatureQuery, /*allowNonBuiltIn*/ false, /*allowedCerts*/ false, - /*verifyContentSignature*/ true + /*verifyContentSignature*/ true, + /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot ); Assert.ok(true, "Should successfully get addon list"); @@ -122,7 +123,8 @@ add_task(async function test_invalid_content_signature() { signedBaseUri + goodXmlPath + "?" + invalidSignatureQuery, /*allowNonBuiltIn*/ false, /*allowedCerts*/ false, - /*verifyContentSignature*/ true + /*verifyContentSignature*/ true, + /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot ); Assert.ok(false, "Should fail to get addon list"); } catch (e) { @@ -143,7 +145,8 @@ add_task(async function test_missing_content_signature_header() { signedBaseUri + goodXmlPath + "?" + missingSignatureQuery, /*allowNonBuiltIn*/ false, /*allowedCerts*/ false, - /*verifyContentSignature*/ true + /*verifyContentSignature*/ true, + /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot ); Assert.ok(false, "Should fail to get addon list"); } catch (e) { @@ -165,7 +168,8 @@ add_task(async function test_incomplete_content_signature_header() { signedBaseUri + goodXmlPath + "?" + incompleteSignatureQuery, /*allowNonBuiltIn*/ false, /*allowedCerts*/ false, - /*verifyContentSignature*/ true + /*verifyContentSignature*/ true, + /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot ); Assert.ok(false, "Should fail to get addon list"); } catch (e) { @@ -187,7 +191,8 @@ add_task(async function test_bad_x5u_content_signature_header() { signedBaseUri + goodXmlPath + "?" + badX5uSignatureQuery, /*allowNonBuiltIn*/ false, /*allowedCerts*/ false, - /*verifyContentSignature*/ true + /*verifyContentSignature*/ true, + /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot ); Assert.ok(false, "Should fail to get addon list"); } catch (e) { diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 14b8c98dc8..ec23a4d39f 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -4206,7 +4206,9 @@ int XREMain::XRE_mainInit(bool* aExitFlag) { #if defined(MOZ_SANDBOX) && defined(XP_WIN) if (mAppData->sandboxBrokerServices) { - SandboxBroker::Initialize(mAppData->sandboxBrokerServices); + nsAutoString binDirPath; + MOZ_ALWAYS_SUCCEEDS(xreBinDirectory->GetPath(binDirPath)); + SandboxBroker::Initialize(mAppData->sandboxBrokerServices, binDirPath); } else { # if defined(MOZ_SANDBOX) // If we're sandboxing content and we fail to initialize, then crashing here diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index e3d975ca18..d2cc2506aa 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -529,7 +529,7 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], #if defined(XP_WIN) # if defined(MOZ_SANDBOX) if (aChildData->sandboxBrokerServices) { - SandboxBroker::Initialize(aChildData->sandboxBrokerServices); + SandboxBroker::Initialize(aChildData->sandboxBrokerServices, u""_ns); SandboxBroker::GeckoDependentInitialize(); } # endif // defined(MOZ_SANDBOX) |