--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -85,16 +85,39 @@ IsEligibleForHSTSPriming(nsIURI* aConten
nsAutoCString hostname;
rv = aContentLocation->GetHost(hostname);
NS_ENSURE_SUCCESS(rv, false);
PRNetAddr hostAddr;
return (PR_StringToNetAddr(hostname.get(), &hostAddr) != PR_SUCCESS);
}
+enum MixedContentHSTSState {
+ MCB_HSTS_PASSIVE_NO_HSTS = 0,
+ MCB_HSTS_PASSIVE_WITH_HSTS = 1,
+ MCB_HSTS_ACTIVE_NO_HSTS = 2,
+ MCB_HSTS_ACTIVE_WITH_HSTS = 3
+};
+
+// Similar to the existing mixed-content HSTS, except MCB_HSTS_*_NO_HSTS is
+// broken into two distinct states, indicating whether we plan to send a priming
+// request or not. If we decided not go send a priming request, it could be
+// because it is a type we do not support, or because we cached a previous
+// negative response.
+enum MixedContentHSTSPrimingState {
+ eMCB_HSTS_PASSIVE_WITH_HSTS = 0,
+ eMCB_HSTS_ACTIVE_WITH_HSTS = 1,
+ eMCB_HSTS_PASSIVE_NO_PRIMING = 2,
+ eMCB_HSTS_PASSIVE_DO_PRIMING = 3,
+ eMCB_HSTS_ACTIVE_NO_PRIMING = 4,
+ eMCB_HSTS_ACTIVE_DO_PRIMING = 5,
+ eMCB_HSTS_PASSIVE_UPGRADE = 6,
+ eMCB_HSTS_ACTIVE_UPGRADE = 7,
+};
+
// Fired at the document that attempted to load mixed content. The UI could
// handle this event, for example, by displaying an info bar that offers the
// choice to reload the page with mixed content permitted.
class nsMixedContentEvent : public Runnable
{
public:
nsMixedContentEvent(nsISupports *aContext, MixedContentTypes aType, bool aRootHasSecureConnection)
: mContext(aContext), mType(aType), mRootHasSecureConnection(aRootHasSecureConnection)
@@ -887,29 +910,33 @@ nsMixedContentBlocker::ShouldLoad(bool a
OriginAttributes originAttributes;
if (principal) {
originAttributes = principal->OriginAttributesRef();
} else if (aRequestPrincipal) {
originAttributes = aRequestPrincipal->OriginAttributesRef();
}
+ bool active = (classification == eMixedScript);
bool doHSTSPriming = false;
if (IsEligibleForHSTSPriming(aContentLocation)) {
bool hsts = false;
bool cached = false;
nsCOMPtr<nsISiteSecurityService> sss =
do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aContentLocation,
0, originAttributes, &cached, &hsts);
NS_ENSURE_SUCCESS(rv, rv);
if (hsts && sUseHSTS) {
// assume we will be upgraded later
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
+ (active) ? MixedContentHSTSPrimingState::eMCB_HSTS_ACTIVE_UPGRADE
+ : MixedContentHSTSPrimingState::eMCB_HSTS_PASSIVE_UPGRADE);
*aDecision = ACCEPT;
return NS_OK;
}
// Send a priming request if the result is not already cached and priming
// requests are allowed
if (!cached && sSendHSTSPriming) {
// add this URI as a priming location
@@ -927,17 +954,16 @@ nsMixedContentBlocker::ShouldLoad(bool a
//
// Note that we count this for redirects as well as primary requests. This
// will cause some degree of double-counting, especially when mixed content
// is not blocked (e.g., for images). For more detail, see:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1198572#c19
//
// We do not count requests aHadInsecureImageRedirect=true, since these are
// just an artifact of the image caching system.
- bool active = (classification == eMixedScript);
if (!aHadInsecureImageRedirect) {
if (XRE_IsParentProcess()) {
AccumulateMixedContentHSTS(innerContentLocation, active, doHSTSPriming,
originAttributes);
} else {
// Ask the parent process to do the same call
mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
if (cc) {
@@ -1098,37 +1124,16 @@ nsMixedContentBlocker::ShouldProcess(uin
}
}
return ShouldLoad(aContentType, aContentLocation, aRequestingLocation,
aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal,
aDecision);
}
-enum MixedContentHSTSState {
- MCB_HSTS_PASSIVE_NO_HSTS = 0,
- MCB_HSTS_PASSIVE_WITH_HSTS = 1,
- MCB_HSTS_ACTIVE_NO_HSTS = 2,
- MCB_HSTS_ACTIVE_WITH_HSTS = 3
-};
-
-// Similar to the existing mixed-content HSTS, except MCB_HSTS_*_NO_HSTS is
-// broken into two distinct states, indicating whether we plan to send a priming
-// request or not. If we decided not go send a priming request, it could be
-// because it is a type we do not support, or because we cached a previous
-// negative response.
-enum MixedContentHSTSPrimingState {
- eMCB_HSTS_PASSIVE_WITH_HSTS = 0,
- eMCB_HSTS_ACTIVE_WITH_HSTS = 1,
- eMCB_HSTS_PASSIVE_NO_PRIMING = 2,
- eMCB_HSTS_PASSIVE_DO_PRIMING = 3,
- eMCB_HSTS_ACTIVE_NO_PRIMING = 4,
- eMCB_HSTS_ACTIVE_DO_PRIMING = 5
-};
-
// Record information on when HSTS would have made mixed content not mixed
// content (regardless of whether it was actually blocked)
void
nsMixedContentBlocker::AccumulateMixedContentHSTS(
nsIURI* aURI, bool aActive, bool aHasHSTSPriming,
const OriginAttributes& aOriginAttributes)
{
// This method must only be called in the parent, because
@@ -1153,45 +1158,45 @@ nsMixedContentBlocker::AccumulateMixedCo
// states: would upgrade, would prime, hsts info cached
// active, passive
//
if (!aActive) {
if (!hsts) {
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
MCB_HSTS_PASSIVE_NO_HSTS);
if (aHasHSTSPriming) {
- Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
eMCB_HSTS_PASSIVE_DO_PRIMING);
} else {
- Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
eMCB_HSTS_PASSIVE_NO_PRIMING);
}
}
else {
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
MCB_HSTS_PASSIVE_WITH_HSTS);
- Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
eMCB_HSTS_PASSIVE_WITH_HSTS);
}
} else {
if (!hsts) {
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
MCB_HSTS_ACTIVE_NO_HSTS);
if (aHasHSTSPriming) {
- Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
eMCB_HSTS_ACTIVE_DO_PRIMING);
} else {
- Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
eMCB_HSTS_ACTIVE_NO_PRIMING);
}
}
else {
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
MCB_HSTS_ACTIVE_WITH_HSTS);
- Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
eMCB_HSTS_ACTIVE_WITH_HSTS);
}
}
}
//static
nsresult
nsMixedContentBlocker::MarkLoadInfoForPriming(nsIURI* aURI,
--- a/dom/security/test/hsts/browser_hsts-priming_allow_active.js
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_active.js
@@ -1,24 +1,40 @@
/*
* Description of the test:
* Check that HSTS priming occurs correctly with mixed content when active
* content is allowed.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Services.obs.addObserver(Observer, "console-api-log-event");
Services.obs.addObserver(Observer, "http-on-examine-response");
registerCleanupFunction(do_cleanup);
let which = "allow_active";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_allow_display.js
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_display.js
@@ -1,24 +1,40 @@
/*
* Description of the test:
* Check that HSTS priming occurs correctly with mixed content when display
* content is allowed.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Services.obs.addObserver(Observer, "console-api-log-event");
Services.obs.addObserver(Observer, "http-on-examine-response");
registerCleanupFunction(do_cleanup);
let which = "allow_display";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_block_active.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active.js
@@ -1,24 +1,40 @@
/*
* Description of the test:
* Check that HSTS priming occurs correctly with mixed content when active
* content is blocked.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Services.obs.addObserver(Observer, "console-api-log-event");
Services.obs.addObserver(Observer, "http-on-examine-response");
registerCleanupFunction(do_cleanup);
let which = "block_active";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
@@ -1,24 +1,40 @@
/*
* Description of the test:
* Check that HSTS priming occurs correctly with mixed content when active
* content is blocked for css.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Services.obs.addObserver(Observer, "console-api-log-event");
Services.obs.addObserver(Observer, "http-on-examine-response");
registerCleanupFunction(do_cleanup);
let which = "block_active_css";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
@@ -1,24 +1,40 @@
/*
* Description of the test:
* Check that HSTS priming occurs correctly with mixed content when active
* content is blocked and redirect to the same host should still upgrade.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Services.obs.addObserver(Observer, "console-api-log-event");
Services.obs.addObserver(Observer, "http-on-examine-response");
registerCleanupFunction(do_cleanup);
let which = "block_active_with_redir_same";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_block_display.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_display.js
@@ -1,24 +1,40 @@
/*
* Description of the test:
* Check that HSTS priming occurs correctly with mixed content when display
* content is blocked.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Services.obs.addObserver(Observer, "console-api-log-event");
Services.obs.addObserver(Observer, "http-on-examine-response");
registerCleanupFunction(do_cleanup);
let which = "block_display";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
+++ b/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
@@ -1,24 +1,37 @@
/*
* Description of the test:
* Test that the network.hsts_priming.cache_timeout preferene causes the cache
* to timeout
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 2,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 4,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Observer.add_observers(Services);
registerCleanupFunction(do_cleanup);
let which = "block_display";
SetupPrefTestEnvironment(which, [["security.mixed_content.hsts_priming_cache_timeout", 1]]);
+ clear_hists(expected_telemetry);
yield execute_test("no-ssl", test_settings[which].mimetype);
let pre_promise = performance.now();
while ((performance.now() - pre_promise) < 2000) {
yield new Promise(function (resolve) {
setTimeout(resolve, 2000);
@@ -27,10 +40,12 @@ add_task(function*() {
// clear the fact that we saw a priming request
test_settings[which].priming = {};
yield execute_test("no-ssl", test_settings[which].mimetype);
is(test_settings[which].priming["no-ssl"], true,
"Correctly send a priming request after expiration.");
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
+++ b/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
@@ -1,24 +1,40 @@
/*
* Description of the test:
* Check that HSTS priming occurs correctly with mixed content when the
* mixed-content blocks before HSTS.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Services.obs.addObserver(Observer, "console-api-log-event");
Services.obs.addObserver(Observer, "http-on-examine-response");
registerCleanupFunction(do_cleanup);
let which = "hsts_after_mixed";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
+++ b/dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
@@ -5,16 +5,28 @@
/*
* Description of the test:
* If the top-level domain sends the STS header but does not have
* includeSubdomains, HSTS priming requests should still be sent to
* subdomains.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 2,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 4,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Observer.add_observers(Services);
registerCleanupFunction(do_cleanup);
// add the top-level server
test_servers['top-level'] = {
@@ -22,18 +34,21 @@ add_task(function*() {
response: true,
id: 'top-level',
};
test_settings.block_active.result['top-level'] = 'secure';
let which = "block_active";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
yield execute_test("top-level", test_settings[which].mimetype);
yield execute_test("prime-hsts", test_settings[which].mimetype);
ok("prime-hsts" in test_settings[which].priming,
"HSTS priming on a subdomain when top-level does not includeSubDomains");
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
@@ -1,28 +1,44 @@
/*
* Description of the test:
* Only one request should be sent per host, even if we run the test more
* than once.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 8,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Observer.add_observers(Services);
registerCleanupFunction(do_cleanup);
let which = "block_display";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
// run the tests twice to validate the cache is being used
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js
@@ -1,16 +1,25 @@
/*
* Description of the test:
* If the top-level domain sends the STS header but does not have
* includeSubdomains, HSTS priming requests should still be sent to
* subdomains.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 0,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 1,
+ },
+ "keyed-histograms": {
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Observer.add_observers(Services);
registerCleanupFunction(do_cleanup);
// add the top-level server
test_servers['localhost-ip'] = {
@@ -18,13 +27,16 @@ add_task(function*() {
response: true,
id: 'localhost-ip',
};
test_settings.block_active.result['localhost-ip'] = 'blocked';
let which = "block_active";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
yield execute_test("localhost-ip", test_settings[which].mimetype);
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
@@ -1,34 +1,49 @@
/*
* Description of the test:
* If the top-level domain sends the STS header but does not have
* includeSubdomains, HSTS priming requests should still be sent to
* subdomains.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 1,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 3,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Observer.add_observers(Services);
registerCleanupFunction(do_cleanup);
// add the top-level server
test_servers['non-standard-port'] = {
- host: 'example.com:1234',
+ host: 'test1.example.com:1234',
response: true,
id: 'non-standard-port',
};
test_settings.block_active.result['non-standard-port'] = 'blocked';
let which = "block_active";
SetupPrefTestEnvironment(which);
+ clear_hists(expected_telemetry);
yield execute_test("non-standard-port", test_settings[which].mimetype);
yield execute_test("prime-hsts", test_settings[which].mimetype);
ok("prime-hsts" in test_settings[which_test].priming, "Sent priming request on standard port after non-standard was not primed");
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/browser_hsts-priming_timeout.js
+++ b/dom/security/test/hsts/browser_hsts-priming_timeout.js
@@ -1,24 +1,39 @@
/*
* Description of the test:
* Only one request should be sent per host, even if we run the test more
* than once.
*/
'use strict';
+var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 3,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "failure": 3,
+ },
+ }
+};
+
//jscs:disable
add_task(function*() {
//jscs:enable
Observer.add_observers(Services);
registerCleanupFunction(do_cleanup);
let which = "timeout";
SetupPrefTestEnvironment(which, [["security.mixed_content.hsts_priming_request_timeout",
1000]]);
+ clear_hists(expected_telemetry);
for (let server of Object.keys(test_servers)) {
yield execute_test(server, test_settings[which].mimetype);
}
+ test_telemetry(expected_telemetry);
+
SpecialPowers.popPrefEnv();
});
--- a/dom/security/test/hsts/head.js
+++ b/dom/security/test/hsts/head.js
@@ -372,22 +372,26 @@ function openTab(uri) {
}
function clear_sts_data() {
for (let test in test_servers) {
SpecialPowers.cleanUpSTSData('http://'+test_servers[test].host);
}
}
+var oldCanRecord = Services.telemetry.canRecordExtended;
+
function do_cleanup() {
clear_sts_data();
Services.obs.removeObserver(Observer, "console-api-log-event");
Services.obs.removeObserver(Observer, "http-on-examine-response");
+ Services.telemetry.canRecordExtended = oldCanRecord;
+
Observer.cleanup();
}
function SetupPrefTestEnvironment(which, additional_prefs) {
which_test = which;
clear_sts_data();
var settings = test_settings[which];
@@ -399,24 +403,27 @@ function SetupPrefTestEnvironment(which,
var prefs = [["security.mixed_content.block_active_content",
settings.block_active],
["security.mixed_content.block_display_content",
settings.block_display],
["security.mixed_content.use_hsts",
settings.use_hsts],
["security.mixed_content.send_hsts_priming",
settings.send_hsts_priming],
+ ["toolkit.telemetry.enabled", true],
];
if (additional_prefs) {
for (let idx in additional_prefs) {
prefs.push(additional_prefs[idx]);
}
}
+ Services.telemetry.canRecordExtended = true;
+
SpecialPowers.pushPrefEnv({'set': prefs});
}
// make the top-level test uri
function build_test_uri(base_uri, host, test_id, type, timeout) {
return base_uri +
"?host=" + escape(host) +
"&id=" + escape(test_id) +
@@ -428,8 +435,93 @@ function build_test_uri(base_uri, host,
// open a new tab, load the test, and wait for it to finish
async function execute_test(test, mimetype) {
var src = build_test_uri(TOP_URI, test_servers[test].host,
test, test_settings[which_test].type,
test_settings[which_test].timeout);
await BrowserTestUtils.withNewTab(src, () => {});
}
+
+/* Expected should look something like this:
+ * The numbers are the sum of all telemetry values.
+ var expected_telemetry = {
+ "histograms": {
+ "MIXED_CONTENT_HSTS_PRIMING_RESULT": 6,
+ "HSTS_PRIMING_REQUESTS": 10,
+ },
+ "keyed-histograms": {
+ "HSTS_PRIMING_REQUEST_DURATION": {
+ "success": 1,
+ "failure": 2,
+ },
+ }
+ };
+ */
+function test_telemetry(expected) {
+ for (let key in expected['histograms']) {
+ let hs = undefined;
+ try {
+ let hist = Services.telemetry.getHistogramById(key);
+ hs = hist.snapshot();
+ hist.clear();
+ } catch(e) {
+ ok(false, "Caught exception trying to get histogram for key " + key + ":" + e);
+ continue;
+ }
+
+ if (!hs) {
+ ok(false, "No histogram found for key " + key);
+ continue;
+ }
+
+ // there may have been other background requests processed
+ ok(hs.counts.reduce(sum) >= expected['histograms'][key], "Histogram counts match expected, got " + hs.counts.reduce(sum) + ", expected at least " + expected['histograms'][key]);
+ }
+
+ for (let key in expected['keyed-histograms']) {
+ let hs = undefined;
+ try {
+ let hist = Services.telemetry.getKeyedHistogramById(key);
+ hs = hist.snapshot();
+ hist.clear();
+ } catch(e) {
+ ok(false, "Caught exception trying to get histogram for key " + key + " :" + e);
+ continue;
+ }
+
+ if (!hs) {
+ ok(false, "No keyed histogram found for key " + key);
+ continue;
+ }
+
+ for (let hist_key in expected['keyed-histograms'][key]) {
+ ok(hist_key in hs, "Keyed histogram exists with key");
+ if (hist_key in hs) {
+ ok(hs[hist_key].counts.reduce(sum) >= expected['keyed-histograms'][key][hist_key], "Keyed histogram counts match expected got " + hs[hist_key].counts.reduce(sum) + ", expected at least " + expected['keyed-histograms'][key][hist_key])
+ }
+ }
+ }
+}
+
+function sum(a, b) {
+ return a+b;
+}
+
+function clear_hists(hists) {
+ for (let key in hists['histograms']) {
+ try {
+ let hist = Services.telemetry.getHistogramById(key);
+ hist.clear();
+ } catch(e) {
+ continue;
+ }
+ }
+
+ for (let key in hists['keyed-histograms']) {
+ try {
+ let hist = Services.telemetry.getKeyedHistogramById(key);
+ hist.clear();
+ } catch(e) {
+ continue;
+ }
+ }
+}
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -384,17 +384,20 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
aLoadInfo->GetIsInThirdPartyContext(),
aLoadInfo->GetOriginAttributes(),
redirectChainIncludingInternalRedirects,
redirectChain,
aLoadInfo->CorsUnsafeHeaders(),
aLoadInfo->GetForcePreflight(),
aLoadInfo->GetIsPreflight(),
aLoadInfo->GetForceHSTSPriming(),
- aLoadInfo->GetMixedContentWouldBlock());
+ aLoadInfo->GetMixedContentWouldBlock(),
+ aLoadInfo->GetIsHSTSPriming(),
+ aLoadInfo->GetIsHSTSPrimingUpgrade()
+ );
return NS_OK;
}
nsresult
LoadInfoArgsToLoadInfo(const OptionalLoadInfoArgs& aOptionalLoadInfoArgs,
nsILoadInfo** outLoadInfo)
{
@@ -469,17 +472,19 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
loadInfoArgs.isInThirdPartyContext(),
loadInfoArgs.originAttributes(),
redirectChainIncludingInternalRedirects,
redirectChain,
loadInfoArgs.corsUnsafeHeaders(),
loadInfoArgs.forcePreflight(),
loadInfoArgs.isPreflight(),
loadInfoArgs.forceHSTSPriming(),
- loadInfoArgs.mixedContentWouldBlock()
+ loadInfoArgs.mixedContentWouldBlock(),
+ loadInfoArgs.isHSTSPriming(),
+ loadInfoArgs.isHSTSPrimingUpgrade()
);
loadInfo.forget(outLoadInfo);
return NS_OK;
}
} // namespace ipc
} // namespace mozilla
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -53,16 +53,18 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
, mFrameOuterWindowID(0)
, mEnforceSecurity(false)
, mInitialSecurityCheckDone(false)
, mIsThirdPartyContext(false)
, mForcePreflight(false)
, mIsPreflight(false)
, mForceHSTSPriming(false)
, mMixedContentWouldBlock(false)
+ , mIsHSTSPriming(false)
+ , mIsHSTSPrimingUpgrade(false)
{
MOZ_ASSERT(mLoadingPrincipal);
MOZ_ASSERT(mTriggeringPrincipal);
#ifdef DEBUG
// TYPE_DOCUMENT loads initiated by javascript tests will go through
// nsIOService and use the wrong constructor. Don't enforce the
// !TYPE_DOCUMENT check in those cases
@@ -226,16 +228,18 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
, mFrameOuterWindowID(0)
, mEnforceSecurity(false)
, mInitialSecurityCheckDone(false)
, mIsThirdPartyContext(false) // NB: TYPE_DOCUMENT implies not third-party.
, mForcePreflight(false)
, mIsPreflight(false)
, mForceHSTSPriming(false)
, mMixedContentWouldBlock(false)
+ , mIsHSTSPriming(false)
+ , mIsHSTSPrimingUpgrade(false)
{
// Top-level loads are never third-party
// Grab the information we can out of the window.
MOZ_ASSERT(aOuterWindow);
MOZ_ASSERT(mTriggeringPrincipal);
// if the load is sandboxed, we can not also inherit the principal
if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
@@ -289,16 +293,18 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
, mRedirectChainIncludingInternalRedirects(
rhs.mRedirectChainIncludingInternalRedirects)
, mRedirectChain(rhs.mRedirectChain)
, mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders)
, mForcePreflight(rhs.mForcePreflight)
, mIsPreflight(rhs.mIsPreflight)
, mForceHSTSPriming(rhs.mForceHSTSPriming)
, mMixedContentWouldBlock(rhs.mMixedContentWouldBlock)
+ , mIsHSTSPriming(rhs.mIsHSTSPriming)
+ , mIsHSTSPrimingUpgrade(rhs.mIsHSTSPrimingUpgrade)
{
}
LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aTriggeringPrincipal,
nsIPrincipal* aPrincipalToInherit,
nsIPrincipal* aSandboxedLoadingPrincipal,
nsSecurityFlags aSecurityFlags,
@@ -317,17 +323,19 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
bool aIsThirdPartyContext,
const OriginAttributes& aOriginAttributes,
RedirectHistoryArray& aRedirectChainIncludingInternalRedirects,
RedirectHistoryArray& aRedirectChain,
const nsTArray<nsCString>& aCorsUnsafeHeaders,
bool aForcePreflight,
bool aIsPreflight,
bool aForceHSTSPriming,
- bool aMixedContentWouldBlock)
+ bool aMixedContentWouldBlock,
+ bool aIsHSTSPriming,
+ bool aIsHSTSPrimingUpgrade)
: mLoadingPrincipal(aLoadingPrincipal)
, mTriggeringPrincipal(aTriggeringPrincipal)
, mPrincipalToInherit(aPrincipalToInherit)
, mSecurityFlags(aSecurityFlags)
, mInternalContentPolicyType(aContentPolicyType)
, mTainting(aTainting)
, mUpgradeInsecureRequests(aUpgradeInsecureRequests)
, mVerifySignedContent(aVerifySignedContent)
@@ -341,16 +349,18 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
, mInitialSecurityCheckDone(aInitialSecurityCheckDone)
, mIsThirdPartyContext(aIsThirdPartyContext)
, mOriginAttributes(aOriginAttributes)
, mCorsUnsafeHeaders(aCorsUnsafeHeaders)
, mForcePreflight(aForcePreflight)
, mIsPreflight(aIsPreflight)
, mForceHSTSPriming (aForceHSTSPriming)
, mMixedContentWouldBlock(aMixedContentWouldBlock)
+ , mIsHSTSPriming(aIsHSTSPriming)
+ , mIsHSTSPrimingUpgrade(aIsHSTSPrimingUpgrade)
{
// Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
MOZ_ASSERT(mLoadingPrincipal || aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
MOZ_ASSERT(mTriggeringPrincipal);
mRedirectChainIncludingInternalRedirects.SwapElements(
aRedirectChainIncludingInternalRedirects);
@@ -929,16 +939,48 @@ LoadInfo::SetHSTSPriming(bool aMixedCont
void
LoadInfo::ClearHSTSPriming()
{
mForceHSTSPriming = false;
mMixedContentWouldBlock = false;
}
NS_IMETHODIMP
+LoadInfo::SetIsHSTSPriming(bool aIsHSTSPriming)
+{
+ MOZ_ASSERT(aIsHSTSPriming);
+ mIsHSTSPriming = aIsHSTSPriming;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsHSTSPriming(bool* aIsHSTSPriming)
+{
+ MOZ_ASSERT(aIsHSTSPriming);
+ *aIsHSTSPriming = mIsHSTSPriming;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsHSTSPrimingUpgrade(bool aIsHSTSPrimingUpgrade)
+{
+ MOZ_ASSERT(aIsHSTSPrimingUpgrade);
+ mIsHSTSPrimingUpgrade = aIsHSTSPrimingUpgrade;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetIsHSTSPrimingUpgrade(bool* aIsHSTSPrimingUpgrade)
+{
+ MOZ_ASSERT(aIsHSTSPrimingUpgrade);
+ *aIsHSTSPrimingUpgrade = mIsHSTSPrimingUpgrade;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
LoadInfo::GetTainting(uint32_t* aTaintingOut)
{
MOZ_ASSERT(aTaintingOut);
*aTaintingOut = static_cast<uint32_t>(mTainting);
return NS_OK;
}
NS_IMETHODIMP
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -110,17 +110,19 @@ private:
bool aIsThirdPartyRequest,
const OriginAttributes& aOriginAttributes,
RedirectHistoryArray& aRedirectChainIncludingInternalRedirects,
RedirectHistoryArray& aRedirectChain,
const nsTArray<nsCString>& aUnsafeHeaders,
bool aForcePreflight,
bool aIsPreflight,
bool aForceHSTSPriming,
- bool aMixedContentWouldBlock);
+ bool aMixedContentWouldBlock,
+ bool aIsHSTSPriming,
+ bool aIsHSTSPrimingUpgrade);
LoadInfo(const LoadInfo& rhs);
NS_IMETHOD GetRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aRedirects,
const RedirectHistoryArray& aArra);
friend nsresult
mozilla::ipc::LoadInfoArgsToLoadInfo(
const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs,
@@ -160,15 +162,17 @@ private:
RedirectHistoryArray mRedirectChainIncludingInternalRedirects;
RedirectHistoryArray mRedirectChain;
nsTArray<nsCString> mCorsUnsafeHeaders;
bool mForcePreflight;
bool mIsPreflight;
bool mForceHSTSPriming : 1;
bool mMixedContentWouldBlock : 1;
+ bool mIsHSTSPriming: 1;
+ bool mIsHSTSPrimingUpgrade: 1;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_LoadInfo_h
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -671,16 +671,26 @@ interface nsILoadInfo : nsISupports
[noscript, infallible] readonly attribute boolean forceHSTSPriming;
/**
* Carry the decision whether this load would be blocked by mixed content so
* that if HSTS priming fails, the correct decision can be made.
*/
[noscript, infallible] readonly attribute boolean mixedContentWouldBlock;
/**
+ * True if this load is an HSTS priming request.
+ */
+ [noscript, infallible] attribute boolean isHSTSPriming;
+
+ /**
+ * True if this load was upgraded from HSTS priming
+ */
+ [noscript, infallible] attribute boolean isHSTSPrimingUpgrade;
+
+ /**
* Mark this LoadInfo as needing HSTS Priming
*
* @param wouldBlock Carry the decision of Mixed Content Blocking to be
* applied when HSTS priming is complete.
*/
[noscript, notxpcom, nostdcall]
void setHSTSPriming(in boolean mixeContentWouldBlock);
[noscript, notxpcom, nostdcall]
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -2564,16 +2564,24 @@ NS_ShouldSecureUpgrade(nsIURI* aURI,
0, // aColumnNumber
nsIScriptError::warningFlag, "CSP",
innerWindowId);
Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 4);
aShouldUpgrade = true;
return NS_OK;
}
+
+ if (aLoadInfo->GetForceHSTSPriming()) {
+ // don't log requests which might be upgraded due to HSTS Priming
+ // they get logged in nsHttpChannel::OnHSTSPrimingSucceeded or
+ // nsHttpChannel::OnHSTSPrimingFailed if the load is allowed to proceed.
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
}
// enforce Strict-Transport-Security
nsISiteSecurityService* sss = gHttpHandler->GetSSService();
NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
bool isStsHost = false;
uint32_t flags = aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
@@ -2593,16 +2601,31 @@ NS_ShouldSecureUpgrade(nsIURI* aURI,
return NS_OK;
} else {
Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2);
}
} else {
Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1);
}
} else {
+ if (aLoadInfo) {
+ if (aLoadInfo->GetIsHSTSPriming()) {
+ // don't log HSTS priming requests
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
+
+ if (aLoadInfo->GetIsHSTSPrimingUpgrade()) {
+ // if the upgrade occured due to HSTS priming, it was logged in
+ // nsHttpChannel::OnHSTSPrimingSucceeded before redirect
+ aShouldUpgrade = false;
+ return NS_OK;
+ }
+ }
+
Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0);
}
aShouldUpgrade = false;
return NS_OK;
}
nsresult
NS_GetSecureUpgradedURI(nsIURI* aURI, nsIURI** aUpgradedURI)
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -58,16 +58,18 @@ struct LoadInfoArgs
OriginAttributes originAttributes;
RedirectHistoryEntryInfo[] redirectChainIncludingInternalRedirects;
RedirectHistoryEntryInfo[] redirectChain;
nsCString[] corsUnsafeHeaders;
bool forcePreflight;
bool isPreflight;
bool forceHSTSPriming;
bool mixedContentWouldBlock;
+ bool isHSTSPriming;
+ bool isHSTSPrimingUpgrade;
};
/**
* Not every channel necessarily has a loadInfo attached.
*/
union OptionalLoadInfoArgs
{
void_t;
--- a/netwerk/protocol/http/HSTSPrimerListener.cpp
+++ b/netwerk/protocol/http/HSTSPrimerListener.cpp
@@ -46,31 +46,34 @@ HSTSPrimingListener::HSTSPrimingListener
NS_IMETHODIMP
HSTSPrimingListener::GetInterface(const nsIID & aIID, void **aResult)
{
return QueryInterface(aIID, aResult);
}
void
-HSTSPrimingListener::ReportTiming(nsresult aResult)
+HSTSPrimingListener::ReportTiming(nsIHstsPrimingCallback* aCallback, nsresult aResult)
{
nsCOMPtr<nsITimedChannel> timingChannel =
- do_QueryInterface(mCallback);
- if (timingChannel) {
- TimeStamp channelCreationTime;
- nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime);
- if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
- PRUint32 interval =
- (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds();
- Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION,
- (NS_SUCCEEDED(aResult)) ? NS_LITERAL_CSTRING("success")
- : NS_LITERAL_CSTRING("failure"),
- interval);
- }
+ do_QueryInterface(aCallback);
+ if (!timingChannel) {
+ LOG(("HSTS priming: mCallback is not an nsITimedChannel!"));
+ return;
+ }
+
+ TimeStamp channelCreationTime;
+ nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime);
+ if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
+ PRUint32 interval =
+ (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION,
+ (NS_SUCCEEDED(aResult)) ? NS_LITERAL_CSTRING("success")
+ : NS_LITERAL_CSTRING("failure"),
+ interval);
}
}
NS_IMETHODIMP
HSTSPrimingListener::OnStartRequest(nsIRequest *aRequest,
nsISupports *aContext)
{
nsCOMPtr<nsIHstsPrimingCallback> callback;
@@ -83,17 +86,17 @@ HSTSPrimingListener::OnStartRequest(nsIR
// if callback is null, we have already canceled this request and reported
// the failure
if (!callback) {
return NS_OK;
}
nsresult primingResult = CheckHSTSPrimingRequestStatus(aRequest);
- ReportTiming(primingResult);
+ ReportTiming(callback, primingResult);
if (NS_FAILED(primingResult)) {
LOG(("HSTS Priming Failed (request was not approved)"));
return callback->OnHSTSPrimingFailed(primingResult, false);
}
LOG(("HSTS Priming Succeeded (request was approved)"));
return callback->OnHSTSPrimingSucceeded(false);
@@ -187,17 +190,17 @@ HSTSPrimingListener::Notify(nsITimer* ti
nsresult rv;
nsCOMPtr<nsIHstsPrimingCallback> callback;
callback.swap(mCallback);
if (!callback) {
// we already processed this channel
return NS_OK;
}
- ReportTiming(NS_ERROR_HSTS_PRIMING_TIMEOUT);
+ ReportTiming(callback, NS_ERROR_HSTS_PRIMING_TIMEOUT);
if (mPrimingChannel) {
rv = mPrimingChannel->Cancel(NS_ERROR_HSTS_PRIMING_TIMEOUT);
if (NS_FAILED(rv)) {
NS_ERROR("HSTS Priming timed out, and we got an error canceling the priming channel.");
}
}
@@ -232,36 +235,41 @@ HSTSPrimingListener::StartHSTSPriming(ns
NS_GetOriginAttributes(aRequestChannel, originAttributes);
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0,
originAttributes, &cached, &hsts);
NS_ENSURE_SUCCESS(rv, rv);
if (hsts) {
// already saw this host and will upgrade if allowed by preferences
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+ HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_HSTS);
return aCallback->OnHSTSPrimingSucceeded(true);
}
if (cached) {
// there is a non-expired entry in the cache that doesn't allow us to
// upgrade, so go ahead and fail early.
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+ HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_NO_HSTS);
return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
}
// Either it wasn't cached or the cached result has expired. Build a
// channel for the HEAD request.
nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo();
MOZ_ASSERT(originalLoadInfo, "can not perform HSTS priming without a loadInfo");
if (!originalLoadInfo) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsILoadInfo> loadInfo = static_cast<mozilla::LoadInfo*>
(originalLoadInfo.get())->CloneForNewRequest();
+ loadInfo->SetIsHSTSPriming(true);
// the LoadInfo must have a security flag set in order to pass through priming
// if none of these security flags are set, go ahead and fail now instead of
// crashing in nsContentSecurityManager::ValidateSecurityFlags
nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
@@ -365,13 +373,16 @@ HSTSPrimingListener::StartHSTSPriming(ns
sHSTSPrimingTimeout,
nsITimer::TYPE_ONE_SHOT);
if (NS_FAILED(rv)) {
NS_ERROR("HSTS Priming failed to initialize channel cancellation timer");
}
listener->mHSTSPrimingTimer.swap(timer);
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+ HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_SENT);
+
return NS_OK;
}
} // namespace net
} // namespace mozilla
--- a/netwerk/protocol/http/HSTSPrimerListener.h
+++ b/netwerk/protocol/http/HSTSPrimerListener.h
@@ -22,16 +22,41 @@ class nsIHstsPrimingCallback;
namespace mozilla {
namespace net {
class HttpChannelParent;
class nsHttpChannel;
/*
+ * How often do we send an HSTS priming request (over all requests)
+ */
+enum HSTSPrimingRequest {
+ // No HSTS priming request. The request is not mixed-content, or we have
+ // already cached the result before nsMixedContentBlocker::ShouldLoad
+ eHSTS_PRIMING_NO_REQUEST = 0,
+ // Sent an HSTS priming request
+ eHSTS_PRIMING_REQUEST_SENT = 1,
+ // Channel marked for priming, but already had a cached result
+ eHSTS_PRIMING_REQUEST_CACHED_HSTS = 2,
+ // Channel marked for priming, but already had a cached result
+ eHSTS_PRIMING_REQUEST_CACHED_NO_HSTS = 3,
+ // An error occured setting up the the priming request channel. If the
+ // priming channel failed in OnstopRequest, there is no HSTS, or the
+ // channel is redirected, that is recorded by
+ // MIXED_CONTENT_HSTS_PRIMING_RESULT.
+ eHSTS_PRIMING_REQUEST_ERROR = 4,
+ // The channel had no load info, so is ineligible for priming
+ eHSTS_PRIMING_REQUEST_NO_LOAD_INFO = 5,
+ // The request was marked for HSTS priming, but was upgraded by
+ // NS_ShouldSecureUpgrade before HSTS priming was sent.
+ eHSTS_PRIMING_REQUEST_ALREADY_UPGRADED = 6,
+};
+
+/*
* How often do we get back an HSTS priming result which upgrades the connection to HTTPS?
*/
enum HSTSPrimingResult {
// This site has been seen before and won't be upgraded
eHSTS_PRIMING_CACHED_NO_UPGRADE = 0,
// This site has been seen before and will be upgraded
eHSTS_PRIMING_CACHED_DO_UPGRADE = 1,
// This site has been seen before and will be blocked
@@ -51,17 +76,17 @@ enum HSTSPrimingResult {
eHSTS_PRIMING_FAILED_BLOCK = 7,
// HSTS priming failed, and the load is allowed by mixed-content
eHSTS_PRIMING_FAILED_ACCEPT = 8,
// The HSTS Priming request timed out, and the load is blocked by
// mixed-content
eHSTS_PRIMING_TIMEOUT_BLOCK = 9,
// The HSTS Priming request timed out, and the load is allowed by
// mixed-content
- eHSTS_PRIMING_TIMEOUT_ACCEPT = 10
+ eHSTS_PRIMING_TIMEOUT_ACCEPT = 10,
};
//////////////////////////////////////////////////////////////////////////
// Class used as streamlistener and notification callback when
// doing the HEAD request for an HSTS Priming check. Needs to be an
// nsIStreamListener in order to receive events from AsyncOpen2
class HSTSPrimingListener final : public nsIStreamListener,
public nsIInterfaceRequestor,
@@ -98,17 +123,17 @@ private:
/**
* Given a request, return NS_OK if it has resulted in a cached HSTS update.
* We don't need to check for the header as that has already been done for us.
*/
nsresult CheckHSTSPrimingRequestStatus(nsIRequest* aRequest);
// send telemetry about how long HSTS priming requests take
- void ReportTiming(nsresult aResult);
+ void ReportTiming(nsIHstsPrimingCallback* aCallback, nsresult aResult);
/**
* the nsIHttpChannel to notify with the result of HSTS priming.
*/
nsCOMPtr<nsIHstsPrimingCallback> mCallback;
/**
* Keep a handle to the priming channel so we can cancel it on timeout
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -478,43 +478,68 @@ nsHttpChannel::Connect()
return TriggerNetwork(0);
}
nsresult
nsHttpChannel::TryHSTSPriming()
{
if (mLoadInfo) {
+ if (mLoadInfo->GetIsHSTSPriming()) {
+ // shortcut priming requests so they don't get counted
+ return ContinueConnect();
+ }
+
// HSTS priming requires the LoadInfo provided with AsyncOpen2
bool requireHSTSPriming =
mLoadInfo->GetForceHSTSPriming();
if (requireHSTSPriming &&
nsMixedContentBlocker::sSendHSTSPriming &&
mInterceptCache == DO_NOT_INTERCEPT) {
bool isHttpsScheme;
nsresult rv = mURI->SchemeIs("https", &isHttpsScheme);
NS_ENSURE_SUCCESS(rv, rv);
if (!isHttpsScheme) {
rv = HSTSPrimingListener::StartHSTSPriming(this, this);
if (NS_FAILED(rv)) {
CloseCacheEntry(false);
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+ HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_ERROR);
return rv;
}
return NS_OK;
}
- // The request was already upgraded, for example by
- // upgrade-insecure-requests or a prior successful priming request
- Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
- HSTSPrimingResult::eHSTS_PRIMING_ALREADY_UPGRADED);
+ if (!mLoadInfo->GetIsHSTSPrimingUpgrade()) {
+ // The request was already upgraded, for example by a prior
+ // successful priming request
+ LOG(("HSTS Priming: request already upgraded"));
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
+ HSTSPrimingResult::eHSTS_PRIMING_ALREADY_UPGRADED);
+
+ // No HSTS Priming request was sent.
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+ HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_ALREADY_UPGRADED);
+ }
+
mLoadInfo->ClearHSTSPriming();
- }
+ return ContinueConnect();
+ }
+
+ if (!mLoadInfo->GetIsHSTSPrimingUpgrade()) {
+ // No HSTS Priming request was sent, and we didn't already record this request
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+ HSTSPrimingRequest::eHSTS_PRIMING_NO_REQUEST);
+ }
+ } else {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
+ HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_NO_LOAD_INFO);
}
return ContinueConnect();
}
// nsIInputAvailableCallback (nsIStreamTransportService.idl)
NS_IMETHODIMP
nsHttpChannel::OnInputAvailableComplete(uint64_t size, nsresult status)
@@ -8593,45 +8618,54 @@ nsHttpChannel::OnPreflightFailed(nsresul
/*
* May be invoked synchronously if HSTS priming has already been performed
* for the host.
*/
nsresult
nsHttpChannel::OnHSTSPrimingSucceeded(bool aCached)
{
+ // If "security.mixed_content.use_hsts" is false, record the result of
+ // HSTS priming and block or proceed with the load as required by
+ // mixed-content blocking
+ bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
+ // Clear out the HSTS priming flags on the LoadInfo to simplify the logic in
+ // TryHSTSPriming()
+ mLoadInfo->ClearHSTSPriming();
+
if (nsMixedContentBlocker::sUseHSTS) {
// redirect the channel to HTTPS if the pref
// "security.mixed_content.use_hsts" is true
LOG(("HSTS Priming succeeded, redirecting to HTTPS [this=%p]", this));
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
(aCached) ? HSTSPrimingResult::eHSTS_PRIMING_CACHED_DO_UPGRADE :
HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED);
+ // we have to record this upgrade here
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3);
+ mLoadInfo->SetIsHSTSPrimingUpgrade(true);
return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
}
- // If "security.mixed_content.use_hsts" is false, record the result of
- // HSTS priming and block or proceed with the load as required by
- // mixed-content blocking
- bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
-
// preserve the mixed-content-before-hsts order and block if required
if (wouldBlock) {
LOG(("HSTS Priming succeeded, blocking for mixed-content [this=%p]",
this));
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED_BLOCK);
CloseCacheEntry(false);
return AsyncAbort(NS_ERROR_CONTENT_BLOCKED);
}
LOG(("HSTS Priming succeeded, loading insecure: [this=%p]", this));
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED_HTTP);
+ // log HTTP_SCHEME_UPGRADE telemetry
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0);
+
nsresult rv = ContinueConnect();
if (NS_FAILED(rv)) {
CloseCacheEntry(false);
return AsyncAbort(rv);
}
return NS_OK;
}
@@ -8639,16 +8673,19 @@ nsHttpChannel::OnHSTSPrimingSucceeded(bo
/*
* May be invoked synchronously if HSTS priming has already been performed
* for the host.
*/
nsresult
nsHttpChannel::OnHSTSPrimingFailed(nsresult aError, bool aCached)
{
bool wouldBlock = mLoadInfo->GetMixedContentWouldBlock();
+ // Clear out the HSTS priming flags on the LoadInfo to simplify the logic in
+ // TryHSTSPriming()
+ mLoadInfo->ClearHSTSPriming();
LOG(("HSTS Priming Failed [this=%p], %s the load", this,
(wouldBlock) ? "blocking" : "allowing"));
if (aError == NS_ERROR_HSTS_PRIMING_TIMEOUT) {
// A priming request was sent, but timed out
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
(wouldBlock) ? HSTSPrimingResult::eHSTS_PRIMING_TIMEOUT_BLOCK :
HSTSPrimingResult::eHSTS_PRIMING_TIMEOUT_ACCEPT);
@@ -8680,16 +8717,19 @@ nsHttpChannel::OnHSTSPrimingFailed(nsres
}
// If we would block, go ahead and abort with the error provided
if (wouldBlock) {
CloseCacheEntry(false);
return AsyncAbort(aError);
}
+ // log HTTP_SCHEME_UPGRADE telemetry
+ Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0);
+
// we can continue the load and the UI has been updated as mixed content
rv = ContinueConnect();
if (NS_FAILED(rv)) {
CloseCacheEntry(false);
return AsyncAbort(rv);
}
return NS_OK;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -10102,45 +10102,54 @@
"record_in_processes": ["main", "content"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 3,
"description": "A simple counter of daily mixed-content unblock operations and top documents loaded"
},
"MIXED_CONTENT_HSTS": {
"record_in_processes": ["main", "content"],
- "alert_emails": ["seceng@mozilla.org"],
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 10,
"description": "How often would blocked mixed content be allowed if HSTS upgrades were allowed? 0=display/no-HSTS, 1=display/HSTS, 2=active/no-HSTS, 3=active/HSTS"
},
- "MIXED_CONTENT_HSTS_PRIMING": {
- "record_in_processes": ["main", "content"],
- "alert_emails": ["seceng@mozilla.org"],
- "bug_numbers": [1246540],
+ "MIXED_CONTENT_HSTS_PRIMING_2": {
+ "record_in_processes": [ "main" ],
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
+ "bug_numbers": [1359987],
"expires_in_version": "60",
"kind": "enumerated",
"n_values": 16,
- "description": "How often would blocked mixed content be allowed if HSTS upgrades were allowed, including how often would we send an HSTS priming request? 0=display/no-HSTS, 1=display/HSTS, 2=active/no-HSTS, 3=active/HSTS, 4=display/no-HSTS-priming, 5=display/do-HSTS-priming, 6=active/no-HSTS-priming, 7=active/do-HSTS-priming"
+ "description": "How often would blocked mixed content be allowed if HSTS upgrades were allowed, including how often would we send an HSTS priming request? 0=passive/HSTS, 1=active/HSTS, 2=passive/no priming, 3=passive/priming, 4=active/no priming, 5=active/priming, 6=passive/will HSTS upgrade, 7=active/will HSTS upgrade"
},
"MIXED_CONTENT_HSTS_PRIMING_RESULT": {
- "record_in_processes": ["main", "content"],
- "alert_emails": ["seceng@mozilla.org"],
+ "record_in_processes": [ "main" ],
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
"bug_numbers": [1246540],
"expires_in_version": "60",
"kind": "enumerated",
"n_values": 16,
"description": "How often do we get back an HSTS priming result which upgrades the connection to HTTPS? 0=cached (no upgrade), 1=cached (do upgrade), 2=cached (blocked), 3=already upgraded, 4=priming succeeded, 5=priming succeeded (block due to pref), 6=priming succeeded (no upgrade due to pref), 7=priming failed (block), 8=priming failed (accept), 9=timeout (block), 10=timeout (accept)"
},
+ "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": {
+ "record_in_processes": [ "main" ],
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
+ "bug_numbers": [1359987],
+ "expires_in_version": "62",
+ "kind": "enumerated",
+ "n_values": 10,
+ "description": "How often does a request result in HSTS priming? (0=Sent HSTS priming, 1=No priming, 2=Priming skipped due to cached HSTS, 3=Priming skipped due to cached NO HSTS, 4=Priming failed (request error), 5=Priming skipped (missing load info), 6=Priming skipped (already upgraded)"
+ },
"HSTS_PRIMING_REQUEST_DURATION": {
- "record_in_processes": ["main", "content"],
- "alert_emails": ["seceng-telemetry@mozilla.org"],
- "bug_numbers": [1311893],
- "expires_in_version": "58",
+ "record_in_processes": [ "main" ],
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
+ "bug_numbers": [1311893, 1359987],
+ "expires_in_version": "62",
"kind": "exponential",
"low": 100,
"high": 30000,
"n_buckets": 100,
"keyed": true,
"description": "The amount of time required for HSTS priming requests (ms), keyed by success or failure of the priming request. (success, failure)"
},
"MIXED_CONTENT_OBJECT_SUBREQUEST": {