Bug 1647825 - Part 2: Report the XFO and CSP: frame-ancestors error through the telemetry event. r=ckerschb,chutten,nhnt11

After user ticks the checkbox of allowing error reporting, we will
report the error through the telemetry event. The event includes the
error type, XFO policy, CSP policy, the frame uri and the top-level uri.

Differential Revision: https://phabricator.services.mozilla.com/D82332
This commit is contained in:
Tim Huang
2020-07-22 15:12:38 +00:00
parent 84aff40d04
commit a75523fa49
6 changed files with 192 additions and 0 deletions

View File

@@ -29,6 +29,7 @@ class NetErrorChild extends RemotePageChild {
"RPMPrefIsLocked", "RPMPrefIsLocked",
"RPMAddToHistogram", "RPMAddToHistogram",
"RPMRecordTelemetryEvent", "RPMRecordTelemetryEvent",
"RPMGetHttpResponseHeader",
]; ];
this.exportFunctions(exportableFunctions); this.exportFunctions(exportableFunctions);
} }
@@ -82,4 +83,24 @@ class NetErrorChild extends RemotePageChild {
RPMRecordTelemetryEvent(category, event, object, value, extra) { RPMRecordTelemetryEvent(category, event, object, value, extra) {
Services.telemetry.recordEvent(category, event, object, value, extra); Services.telemetry.recordEvent(category, event, object, value, extra);
} }
// Get the header from the http response of the failed channel. This function
// is used in the 'about:neterror' page.
RPMGetHttpResponseHeader(responseHeader) {
let channel = this.contentWindow.docShell.failedChannel;
if (!channel) {
return "";
}
let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
if (!httpChannel) {
return "";
}
try {
return httpChannel.getResponseHeader(responseHeader);
} catch (e) {}
return "";
}
} }

View File

@@ -17,6 +17,10 @@ const { SessionStore } = ChromeUtils.import(
); );
const { HomePage } = ChromeUtils.import("resource:///modules/HomePage.jsm"); const { HomePage } = ChromeUtils.import("resource:///modules/HomePage.jsm");
const { TelemetryController } = ChromeUtils.import(
"resource://gre/modules/TelemetryController.jsm"
);
const PREF_SSL_IMPACT_ROOTS = [ const PREF_SSL_IMPACT_ROOTS = [
"security.tls.version.", "security.tls.version.",
"security.ssl3.", "security.ssl3.",
@@ -114,6 +118,40 @@ class NetErrorParent extends JSWindowActorParent {
errorReporter.reportTLSError(securityInfo, host, port); errorReporter.reportTLSError(securityInfo, host, port);
} }
async ReportBlockingError(bcID, scheme, host, port, path, xfoAndCspInfo) {
// For reporting X-Frame-Options error and CSP: frame-ancestors errors, We
// are collecting 4 pieces of information.
// 1. The X-Frame-Options in the response header.
// 2. The CSP: frame-ancestors in the response header.
// 3. The URI of the frame who triggers this error.
// 4. The top-level URI which loads the frame.
//
// We will exclude the query strings from the reporting URIs.
//
// More details about the data we send can be found in
// https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/xfocsp-error-report-ping.html
//
let topBC = BrowsingContext.get(bcID).top;
let topURI = topBC.currentWindowGlobal.documentURI;
// Get the URLs without query strings.
let frame_uri = `${scheme}//${host}${port == -1 ? "" : ":" + port}${path}`;
let top_uri = `${topURI.scheme}//${topURI.hostPort}${topURI.filePath}`;
TelemetryController.submitExternalPing(
"xfocsp-error-report",
{
...xfoAndCspInfo,
frame_hostname: host,
top_hostname: topURI.host,
frame_uri,
top_uri,
},
{ addClientId: false, addEnvironment: false }
);
}
/** /**
* Return the default start page for the cases when the user's own homepage is * Return the default start page for the cases when the user's own homepage is
* infected, so we can get them somewhere safe. * infected, so we can get them somewhere safe.
@@ -285,6 +323,16 @@ class NetErrorParent extends JSWindowActorParent {
message.data.port message.data.port
); );
break; break;
case "ReportBlockingError":
this.ReportBlockingError(
this.browsingContext.id,
message.data.scheme,
message.data.host,
message.data.port,
message.data.path,
message.data.xfoAndCspInfo
);
break;
case "Browser:CertExceptionError": case "Browser:CertExceptionError":
switch (message.data.elementId) { switch (message.data.elementId) {

View File

@@ -480,6 +480,52 @@ function setupBlockingReportingUI() {
} }
showBlockingErrorReporting(); showBlockingErrorReporting();
if (reportingAutomatic) {
reportBlockingError();
}
}
function reportBlockingError() {
// We only report if we are in a frame.
if (window === window.top) {
return;
}
let err = getErrorCode();
// Ensure we only deal with XFO and CSP here.
if (!["xfoBlocked", "cspBlocked"].includes(err)) {
return;
}
let xfo_header = RPMGetHttpResponseHeader("X-Frame-Options");
let csp_header = RPMGetHttpResponseHeader("Content-Security-Policy");
// Extract the 'CSP: frame-ancestors' from the CSP header.
let reg = /(?:^|\s)frame-ancestors\s([^;]*)[$]*/i;
let match = reg.exec(csp_header);
csp_header = match ? match[1] : "";
// If it's the csp error page without the CSP: frame-ancestors, this means
// this error page is not triggered by CSP: frame-ancestors. So, we bail out
// early.
if (err === "cspBlocked" && !csp_header) {
return;
}
let xfoAndCspInfo = {
error_type: err === "xfoBlocked" ? "xfo" : "csp",
xfo_header,
csp_header,
};
RPMSendAsyncMessage("ReportBlockingError", {
scheme: document.location.protocol,
host: document.location.host,
port: parseInt(document.location.port) || -1,
path: document.location.pathname,
xfoAndCspInfo,
});
} }
function onSetAutomatic(checked) { function onSetAutomatic(checked) {
@@ -501,6 +547,11 @@ function onSetAutomatic(checked) {
function onSetBlockingReportAutomatic(checked) { function onSetBlockingReportAutomatic(checked) {
RPMSetBoolPref("security.xfocsp.errorReporting.automatic", checked); RPMSetBoolPref("security.xfocsp.errorReporting.automatic", checked);
// If we're enabling reports, send a report for this failure.
if (checked) {
reportBlockingError();
}
} }
async function setNetErrorMessageFromCode() { async function setNetErrorMessageFromCode() {

View File

@@ -0,0 +1,69 @@
"xfocsp-error-report" ping
==========================
This opt-in ping is sent when an X-Frame-Options error or a CSP: frame-ancestors
happens to report the error. Users can opt-in this by checking the reporting
checkbox. After users opt-in, this ping will be sent every time the error
happens. Users can opt-out this by un-checking the reporting checkbox on the
error page. The client_id and environment are not sent with this ping.
Structure:
.. code-block:: js
{
"type": "xfocsp-error-report",
... common ping data
"payload": {
"error_type": <string>,
"xfo_header": <string>,
"csp_header": <string>,
"frame_hostname": <string>,
"top_hostname": <string>,
"frame_uri": <string>,
"top_uri": <string>,
}
}
info
----
error_type
~~~~~~~~~~
The type of what error triggers this ping. This could be either "xfo" or "csp".
xfo_header
~~~~~~~~~~
The X-Frame-Options value in the response HTTP header.
csp_header
~~~~~~~~~~
The CSP: frame-ancestors value in the response HTTP header.
frame_hostname
~~~~~~~~~~~~~~
The hostname of the frame which triggers the error.
top_hostname
~~~~~~~~~~~~
The hostname of the top-level page which loads the frame.
frame_uri
~~~~~~~~~
The uri of the frame which triggers the error. This excludes the query strings.
top_uri
~~~~~~~
The uri of the top-level page which loads the frame. This excludes the query
strings.
See also: :doc:`common ping fields <common-ping>`

View File

@@ -81,6 +81,7 @@ let RemotePageAccessManager = {
"Browser:SSLErrorGoBack", "Browser:SSLErrorGoBack",
"Browser:PrimeMitm", "Browser:PrimeMitm",
"Browser:ResetEnterpriseRootsPref", "Browser:ResetEnterpriseRootsPref",
"ReportBlockingError",
], ],
RPMAddMessageListener: ["*"], RPMAddMessageListener: ["*"],
RPMRemoveMessageListener: ["*"], RPMRemoveMessageListener: ["*"],
@@ -101,6 +102,7 @@ let RemotePageAccessManager = {
], ],
RPMPrefIsLocked: ["security.tls.version.min"], RPMPrefIsLocked: ["security.tls.version.min"],
RPMAddToHistogram: ["*"], RPMAddToHistogram: ["*"],
RPMGetHttpResponseHeader: ["*"],
}, },
"about:newinstall": { "about:newinstall": {
RPMGetUpdateChannel: ["*"], RPMGetUpdateChannel: ["*"],

View File

@@ -37,5 +37,6 @@ module.exports = {
RPMRecordTelemetryEvent: false, RPMRecordTelemetryEvent: false,
RPMAddToHistogram: false, RPMAddToHistogram: false,
RPMRemoveMessageListener: false, RPMRemoveMessageListener: false,
RPMGetHttpResponseHeader: false,
}, },
}; };