Bug 1855734 - Use innermost nested URI in PopulateTopLevelInfoFromURI r=freddyb,timhuang

Differential Revision: https://phabricator.services.mozilla.com/D190468
This commit is contained in:
Malte Juergens 2023-11-08 13:44:26 +00:00
parent 0c5967de20
commit 182878bbe0
6 changed files with 102 additions and 29 deletions

View File

@ -384,3 +384,8 @@ http://profile.stage.mozaws.net:80 privileged
https://profile.stage.mozaws.net:443 privileged https://profile.stage.mozaws.net:443 privileged
http://ocsp.pki.goog:80 privileged http://ocsp.pki.goog:80 privileged
https://ocsp.pki.goog:443 privileged https://ocsp.pki.goog:443 privileged
# External IP address only available via http (Bug 1855734)
http://123.123.123.123:80 privileged
https://123.123.123.123:443 privileged,nocert

View File

@ -70,8 +70,19 @@ static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
nsAString& topLevelInfo = aOriginAttributes.*aTarget; nsAString& topLevelInfo = aOriginAttributes.*aTarget;
nsAutoCString scheme; nsAutoCString scheme;
rv = aURI->GetScheme(scheme); nsCOMPtr<nsIURI> uri = aURI;
NS_ENSURE_SUCCESS_VOID(rv); // The URI could be nested (for example view-source:http://example.com), in
// that case we want to get the innermost URI (http://example.com).
nsCOMPtr<nsINestedURI> nestedURI;
do {
NS_ENSURE_SUCCESS_VOID(uri->GetScheme(scheme));
nestedURI = do_QueryInterface(uri);
// We can't just use GetInnermostURI on the nested URI, since that would
// also unwrap some about: URIs to hidden moz-safe-about: URIs, which we do
// not want. Thus we loop through with GetInnerURI until the URI isn't
// nested anymore or we encounter a about: scheme.
} while (nestedURI && !scheme.EqualsLiteral("about") &&
NS_SUCCEEDED(nestedURI->GetInnerURI(getter_AddRefs(uri))));
if (scheme.EqualsLiteral("about")) { if (scheme.EqualsLiteral("about")) {
MakeTopLevelInfo(scheme, nsLiteralCString(ABOUT_URI_FIRST_PARTY_DOMAIN), MakeTopLevelInfo(scheme, nsLiteralCString(ABOUT_URI_FIRST_PARTY_DOMAIN),
@ -84,7 +95,7 @@ static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
if (scheme.EqualsLiteral("moz-nullprincipal")) { if (scheme.EqualsLiteral("moz-nullprincipal")) {
// Get the UUID portion of the URI, ignoring the precursor principal. // Get the UUID portion of the URI, ignoring the precursor principal.
nsAutoCString filePath; nsAutoCString filePath;
rv = aURI->GetFilePath(filePath); rv = uri->GetFilePath(filePath);
MOZ_ASSERT(NS_SUCCEEDED(rv)); MOZ_ASSERT(NS_SUCCEEDED(rv));
// Remove the `{}` characters from both ends. // Remove the `{}` characters from both ends.
filePath.Mid(filePath, 1, filePath.Length() - 2); filePath.Mid(filePath, 1, filePath.Length() - 2);
@ -103,7 +114,7 @@ static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
nsCOMPtr<nsIPrincipal> blobPrincipal; nsCOMPtr<nsIPrincipal> blobPrincipal;
if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal( if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
aURI, getter_AddRefs(blobPrincipal))) { uri, getter_AddRefs(blobPrincipal))) {
MOZ_ASSERT(blobPrincipal); MOZ_ASSERT(blobPrincipal);
topLevelInfo = blobPrincipal->OriginAttributesRef().*aTarget; topLevelInfo = blobPrincipal->OriginAttributesRef().*aTarget;
return; return;
@ -115,7 +126,7 @@ static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
NS_ENSURE_TRUE_VOID(tldService); NS_ENSURE_TRUE_VOID(tldService);
nsAutoCString baseDomain; nsAutoCString baseDomain;
rv = tldService->GetBaseDomain(aURI, 0, baseDomain); rv = tldService->GetBaseDomain(uri, 0, baseDomain);
if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(rv)) {
MakeTopLevelInfo(scheme, baseDomain, aUseSite, topLevelInfo); MakeTopLevelInfo(scheme, baseDomain, aUseSite, topLevelInfo);
return; return;
@ -126,11 +137,11 @@ static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
bool isInsufficientDomainLevels = (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS); bool isInsufficientDomainLevels = (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
int32_t port; int32_t port;
rv = aURI->GetPort(&port); rv = uri->GetPort(&port);
NS_ENSURE_SUCCESS_VOID(rv); NS_ENSURE_SUCCESS_VOID(rv);
nsAutoCString host; nsAutoCString host;
rv = aURI->GetHost(host); rv = uri->GetHost(host);
NS_ENSURE_SUCCESS_VOID(rv); NS_ENSURE_SUCCESS_VOID(rv);
if (isIpAddress) { if (isIpAddress) {
@ -160,7 +171,7 @@ static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
if (isInsufficientDomainLevels) { if (isInsufficientDomainLevels) {
nsAutoCString publicSuffix; nsAutoCString publicSuffix;
rv = tldService->GetPublicSuffix(aURI, publicSuffix); rv = tldService->GetPublicSuffix(uri, publicSuffix);
if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(rv)) {
MakeTopLevelInfo(scheme, publicSuffix, port, aUseSite, topLevelInfo); MakeTopLevelInfo(scheme, publicSuffix, port, aUseSite, topLevelInfo);
return; return;

View File

@ -13,3 +13,5 @@ skip-if = ["toolkit == 'android'"] # no https-only errorpage support in android
["browser_exception.js"] ["browser_exception.js"]
support-files = ["file_upgrade_insecure_server.sjs"] support-files = ["file_upgrade_insecure_server.sjs"]
["browser_fpi_nested_uri.js"]

View File

@ -85,27 +85,7 @@ add_task(async function () {
true true
); );
// click on exception-button and wait for page to load await waitForAndClickOpenInsecureButton(browser);
await SpecialPowers.spawn(browser, [], async function () {
let openInsecureButton = content.document.getElementById("openInsecure");
ok(openInsecureButton != null, "openInsecureButton should exist.");
info("Waiting for openInsecureButton to be enabled.");
function callback() {
if (!openInsecureButton.inert) {
info("openInsecureButton was enabled, waiting two frames.");
observer.disconnect();
content.requestAnimationFrame(() => {
content.requestAnimationFrame(() => {
info("clicking openInsecureButton.");
openInsecureButton.click();
});
});
}
}
const observer = new content.MutationObserver(callback);
observer.observe(openInsecureButton, { attributeFilter: ["inert"] });
callback();
});
await pageShownPromise; await pageShownPromise;

View File

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
* https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that a nested URI (in this case `view-source:`) does not result
// in a redirect loop when HTTPS-Only and First Party Isolation are
// enabled (Bug 1855734).
const INSECURE_VIEW_SOURCE_URL = "view-source:http://123.123.123.123/";
function promiseIsErrorPage() {
return new Promise(resolve => {
BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser).then(() =>
resolve(true)
);
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() =>
resolve(false)
);
});
}
add_task(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["dom.security.https_only_mode", true],
["dom.security.https_only_mode.upgrade_local", true],
["privacy.firstparty.isolate", true],
],
});
let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
info(`Starting to load ${INSECURE_VIEW_SOURCE_URL}`);
BrowserTestUtils.startLoadingURIString(gBrowser, INSECURE_VIEW_SOURCE_URL);
await loaded;
info(`${INSECURE_VIEW_SOURCE_URL} finished loading`);
loaded = promiseIsErrorPage();
await waitForAndClickOpenInsecureButton(gBrowser.selectedBrowser);
info(`Waiting for normal or error page to load`);
const isErrorPage = await loaded;
ok(!isErrorPage, "We should not land on an error page");
await Services.perms.removeAll();
});

View File

@ -64,3 +64,32 @@ async function openErrorPage(src, useFrame, privateWindow, sandboxed) {
return tab; return tab;
} }
/**
* On a loaded HTTPS-Only error page, waits until the "Open Insecure"
* button gets enabled and then presses it.
*
* @returns {Promise<void>}
*/
function waitForAndClickOpenInsecureButton(browser) {
return SpecialPowers.spawn(browser, [], async function () {
let openInsecureButton = content.document.getElementById("openInsecure");
ok(openInsecureButton != null, "openInsecureButton should exist.");
info("Waiting for openInsecureButton to be enabled.");
function callback() {
if (!openInsecureButton.inert) {
info("openInsecureButton was enabled, waiting two frames.");
observer.disconnect();
content.requestAnimationFrame(() => {
content.requestAnimationFrame(() => {
info("clicking openInsecureButton.");
openInsecureButton.click();
});
});
}
}
const observer = new content.MutationObserver(callback);
observer.observe(openInsecureButton, { attributeFilter: ["inert"] });
callback();
});
}