зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1691888: Break endless upgrade downgrade loops when using https-only r=necko-reviewers,valentin,JulianWels
Differential Revision: https://phabricator.services.mozilla.com/D106475
This commit is contained in:
Родитель
14a5c74d1f
Коммит
39ef03a187
|
@ -17,6 +17,7 @@
|
|||
#include "nsIHttpsOnlyModePermission.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsIRedirectHistoryEntry.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "prnetdb.h"
|
||||
|
||||
|
@ -205,6 +206,84 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(nsIURI* aURI,
|
|||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(nsIURI* aURI,
|
||||
nsILoadInfo* aLoadInfo) {
|
||||
// 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
|
||||
bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
|
||||
if (!IsHttpsOnlyModeEnabled(isPrivateWin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Check if the upgrade downgrade pref even wants us to try to break the
|
||||
// cycle.
|
||||
if (!mozilla::StaticPrefs::
|
||||
dom_security_https_only_mode_break_upgrade_downgrade_endless_loop()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. If it's not a top-level load, then there is nothing to do here either.
|
||||
if (aLoadInfo->GetExternalContentPolicyType() !=
|
||||
ExtContentPolicy::TYPE_DOCUMENT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. If the load is exempt, then it's defintely not related to https-only
|
||||
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
|
||||
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. If the load is triggered by a user gesture, then it's definitely
|
||||
// not a loop we need to break.
|
||||
if (aLoadInfo->GetHasValidUserGestureActivation()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. If the URI to be loaded is not http, then it's defnitely no endless
|
||||
// loop caused by https-only.
|
||||
if (!aURI->SchemeIs("http")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoCString uriHost;
|
||||
aURI->GetAsciiHost(uriHost);
|
||||
|
||||
// 7. Check actual redirects. If the Principal that kicked off the
|
||||
// load/redirect is not https, then it's definitely not a redirect cause by
|
||||
// https-only. If the scheme of the principal however is https and the
|
||||
// asciiHost of the URI to be loaded and the asciiHost of the Principal are
|
||||
// identical, then we are dealing with an upgrade downgrade scenario and we
|
||||
// have to break the cycle.
|
||||
if (!aLoadInfo->RedirectChain().IsEmpty()) {
|
||||
nsCOMPtr<nsIPrincipal> redirectPrincipal;
|
||||
for (nsIRedirectHistoryEntry* entry : aLoadInfo->RedirectChain()) {
|
||||
entry->GetPrincipal(getter_AddRefs(redirectPrincipal));
|
||||
if (redirectPrincipal && redirectPrincipal->SchemeIs("https")) {
|
||||
nsAutoCString redirectHost;
|
||||
redirectPrincipal->GetAsciiHost(redirectHost);
|
||||
if (uriHost.Equals(redirectHost)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Meta redirects and JS based redirects (win.location). If the security
|
||||
// context that triggered the load is not https, then it's defnitely no
|
||||
// endless loop caused by https-only. If the scheme is http however and the
|
||||
// asciiHost of the URI to be loaded matches the asciiHost of the Principal,
|
||||
// then we are dealing with an upgrade downgrade scenario and we have to break
|
||||
// the cycle.
|
||||
nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
|
||||
if (!triggeringPrincipal->SchemeIs("https")) {
|
||||
return false;
|
||||
}
|
||||
nsAutoCString triggeringHost;
|
||||
triggeringPrincipal->GetAsciiHost(triggeringHost);
|
||||
return uriHost.Equals(triggeringHost);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(nsIChannel* aChannel,
|
||||
nsresult aError) {
|
||||
|
|
|
@ -49,6 +49,20 @@ class nsHTTPSOnlyUtils {
|
|||
*/
|
||||
static bool ShouldUpgradeWebSocket(nsIURI* aURI, nsILoadInfo* aLoadInfo);
|
||||
|
||||
/**
|
||||
* Determines if we might get stuck in an upgrade-downgrade-endless loop
|
||||
* where https-only upgrades the request to https and the website downgrades
|
||||
* the scheme to http again causing an endless upgrade downgrade loop. E.g.
|
||||
* https-only upgrades to https and the website answers with a meta-refresh
|
||||
* to downgrade to same-origin http version. Similarly this method breaks
|
||||
* the endless cycle for JS based redirects and 302 based redirects.
|
||||
* @param aURI nsIURI of request
|
||||
* @param aLoadInfo nsILoadInfo of request
|
||||
* @return true if an endless loop is detected
|
||||
*/
|
||||
static bool IsUpgradeDowngradeEndlessLoop(nsIURI* aURI,
|
||||
nsILoadInfo* aLoadInfo);
|
||||
|
||||
/**
|
||||
* Checks if the error code is on a block-list of codes that are probably not
|
||||
* related to a HTTPS-Only Mode upgrade.
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Custom *.sjs file specifically for the needs of Bug 1691888
|
||||
"use strict";
|
||||
|
||||
const REDIRECT_META = `
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url='http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test1b'">
|
||||
</head>
|
||||
<body>
|
||||
META REDIRECT
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const REDIRECT_JS = `
|
||||
<html>
|
||||
<body>
|
||||
JS REDIRECT
|
||||
<script>
|
||||
let url= "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test2b";
|
||||
window.location = url;
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const REDIRECT_302 =
|
||||
"http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test3b";
|
||||
|
||||
function handleRequest(request, response) {
|
||||
// avoid confusing cache behaviour
|
||||
response.setHeader("Cache-Control", "no-cache", false);
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
|
||||
// if the scheme is not https, meaning that the initial request did not
|
||||
// get upgraded, then we rather fall through and display unexpected content.
|
||||
if (request.scheme === "https") {
|
||||
let query = request.queryString;
|
||||
|
||||
if (query === "test1a") {
|
||||
response.write(REDIRECT_META);
|
||||
return;
|
||||
}
|
||||
|
||||
if (query === "test2a") {
|
||||
response.write(REDIRECT_JS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (query === "test3a") {
|
||||
response.setStatusLine("1.1", 302, "Found");
|
||||
response.setHeader("Location", REDIRECT_302, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// we should never get here, just in case,
|
||||
// let's return something unexpected
|
||||
response.write("<html><body>DO NOT DISPLAY THIS</body></html>")
|
||||
}
|
|
@ -16,6 +16,10 @@ fail-if = xorigin
|
|||
support-files = file_http_background_request.sjs
|
||||
[test_http_background_auth_request.html]
|
||||
support-files = file_http_background_auth_request.sjs
|
||||
[test_break_endless_upgrade_downgrade_loop.html]
|
||||
skip-if = (toolkit == 'android') # no support for error pages, Bug 1697866
|
||||
support-files = file_break_endless_upgrade_downgrade_loop.sjs
|
||||
|
||||
[test_user_suggestion_box.html]
|
||||
support-files = file_user_suggestion_box.sjs
|
||||
skip-if = toolkit == 'android' # no https-only errorpage support in android
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bug 1691888: Break endless upgrade downgrade loops when using https-only</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
/*
|
||||
* Description of the test:
|
||||
* We perform three tests where our upgrade/downgrade redirect loop detector should break the
|
||||
* endless loop:
|
||||
* Test 1: Meta Refresh
|
||||
* Test 2: JS Redirect
|
||||
* Test 3: 302 redirect
|
||||
*/
|
||||
|
||||
SimpleTest.requestFlakyTimeout("We need to wait for the HTTPS-Only error page to appear");
|
||||
SimpleTest.requestLongerTimeout(10);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const REQUEST_URL =
|
||||
"http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs";
|
||||
|
||||
function resolveAfter5Seconds() {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
async function verifyResult(aTestName) {
|
||||
let errorPageL10nId = "about-httpsonly-title-alert";
|
||||
let innerHTML = content.document.body.innerHTML;
|
||||
ok(innerHTML.includes(errorPageL10nId), "the error page should be shown for " + aTestName);
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
await SpecialPowers.pushPrefEnv({ set: [
|
||||
["dom.security.https_only_mode", true],
|
||||
["dom.security.https_only_mode_break_upgrade_downgrade_endless_loop", true],
|
||||
]});
|
||||
|
||||
// Test 1: Meta Refresh Redirect
|
||||
let winTest1 = window.open(REQUEST_URL + "?test1a", "_blank");
|
||||
// Test 2: JS win.location Redirect
|
||||
let winTest2 = window.open(REQUEST_URL + "?test2a", "_blank");
|
||||
// Test 3: 302 Redirect
|
||||
let winTest3 = window.open(REQUEST_URL + "?test3a", "_blank");
|
||||
|
||||
// provide enough time for:
|
||||
// the redirects to occur, and the error page to be displayed
|
||||
await resolveAfter5Seconds();
|
||||
|
||||
await SpecialPowers.spawn(winTest1, ["test1"], verifyResult);
|
||||
winTest1.close();
|
||||
|
||||
await SpecialPowers.spawn(winTest2, ["test2"], verifyResult);
|
||||
winTest2.close();
|
||||
|
||||
await SpecialPowers.spawn(winTest3, ["test3"], verifyResult);
|
||||
winTest3.close();
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
runTests();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -2785,6 +2785,13 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
# If true, tries to break upgrade downgrade cycles where https-only tries
|
||||
# to upgrad ethe connection, but the website tries to downgrade again.
|
||||
- name: dom.security.https_only_mode_break_upgrade_downgrade_endless_loop
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# If true and HTTPS-only mode is enabled, requests
|
||||
# to local IP addresses are also upgraded
|
||||
- name: dom.security.https_only_mode.upgrade_local
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "mozilla/dom/BrowsingContext.h"
|
||||
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
|
||||
#include "mozilla/dom/Performance.h"
|
||||
#include "mozilla/dom/PerformanceStorage.h"
|
||||
#include "mozilla/dom/WindowGlobalParent.h"
|
||||
|
@ -5097,6 +5098,15 @@ nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const {
|
|||
return NS_ERROR_REDIRECT_LOOP;
|
||||
}
|
||||
|
||||
// in case https-only mode is enabled which upgrades top-level requests to
|
||||
// https and the page answers with a redirect (meta, 302, win.location, ...)
|
||||
// then this method can break the cycle which causes the https-only exception
|
||||
// page to appear.
|
||||
if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(mURI, mLoadInfo)) {
|
||||
LOG(("upgrade downgrade redirect loop!\n"));
|
||||
return NS_ERROR_REDIRECT_LOOP;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче