Bug 1897653 - Make blob urls inherit their principal in third-party checks too - r=pbz,asuth,rpl

We debated over whether or not to do this in https://phabricator.services.mozilla.com/D205231.
It turns out we should have.

If we don't do this, the blob document thinks it is third party, so its subresources become foreign
and don't have access to the first party cookies. All because blob urls don't have a host, so they did't
have a base domain. But they do have origins, which is what base domain should be derived from.

Differential Revision: https://phabricator.services.mozilla.com/D211051
This commit is contained in:
Benjamin VanderSloot 2024-05-29 16:29:32 +00:00
parent b6c9ccb08d
commit 52da37267c
6 changed files with 181 additions and 36 deletions

View file

@ -24,6 +24,7 @@
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsCOMPtr.h"
@ -399,7 +400,9 @@ ThirdPartyUtil::GetTopWindowForChannel(nsIChannel* aChannel,
// "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
// dot may be present. If aHostURI is an IP address, an alias such as
// 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
// be the exact host. The result of this function should only be used in exact
// be the exact host. Blob URIs will incur a lookup for their blob URL entry,
// and will perform the same construction from their principal's base domain.
// The result of this function should only be used in exact
// string comparisons, since substring comparisons will not be valid for the
// special cases elided above.
NS_IMETHODIMP
@ -408,9 +411,26 @@ ThirdPartyUtil::GetBaseDomain(nsIURI* aHostURI, nsACString& aBaseDomain) {
return NS_ERROR_INVALID_ARG;
}
// First, get the base domain from aHostURI. In the common case, this is
// direct. For blob URLs we get this from the blob url's entry in the blob url
// store.
nsresult rv;
nsCOMPtr<nsIPrincipal> blobPrincipal;
if (aHostURI->SchemeIs("blob")) {
if (BlobURLProtocolHandler::GetBlobURLPrincipal(
aHostURI, getter_AddRefs(blobPrincipal))) {
// If the blob URL is expired, this will be the uuid of a NullPrincipal
rv = blobPrincipal->GetBaseDomain(aBaseDomain);
} else {
// If the blob is expired and no longer has a map entry, we fail
rv = nsresult::NS_ERROR_DOM_BAD_URI;
}
} else {
rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
}
// Get the base domain. this will fail if the host contains a leading dot,
// more than one trailing dot, or is otherwise malformed.
nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
// aHostURI is either an IP address, an alias such as 'localhost', an eTLD

View file

@ -870,34 +870,13 @@ void AntiTrackingUtils::ComputeIsThirdPartyToTopWindow(nsIChannel* aChannel) {
bool AntiTrackingUtils::IsThirdPartyChannel(nsIChannel* aChannel) {
MOZ_ASSERT(aChannel);
// We have to handle blob URLs here because they always fail
// IsThirdPartyChannel because of how blob URLs are constructed. We just
// recompare to their ancestor chain from the loadInfo, bailing if any is
// third party.
nsAutoCString scheme;
nsCOMPtr<nsIURI> channelURI;
nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI));
if (NS_SUCCEEDED(rv) && channelURI->SchemeIs("blob")) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
for (const nsCOMPtr<nsIPrincipal>& principal :
loadInfo->AncestorPrincipals()) {
bool thirdParty = true;
rv = loadInfo->PrincipalToInherit()->IsThirdPartyPrincipal(principal,
&thirdParty);
if (NS_SUCCEEDED(rv) && thirdParty) {
return true;
}
}
return false;
}
nsCOMPtr<mozIThirdPartyUtil> tpuService =
mozilla::components::ThirdPartyUtil::Service();
if (!tpuService) {
return true;
}
bool thirdParty = true;
rv = tpuService->IsThirdPartyChannel(aChannel, nullptr, &thirdParty);
nsresult rv = tpuService->IsThirdPartyChannel(aChannel, nullptr, &thirdParty);
if (NS_FAILED(rv)) {
return true;
}
@ -960,15 +939,11 @@ bool AntiTrackingUtils::IsThirdPartyDocument(Document* aDocument) {
return true;
}
bool thirdParty = true;
if (!aDocument->GetChannel() ||
aDocument->GetDocumentURI()->SchemeIs("blob")) {
if (!aDocument->GetChannel()) {
// If we can't get the channel from the document, i.e. initial about:blank
// page, we use the browsingContext of the document to check if it's in the
// third-party context. If the browsing context is still not available, we
// will treat the window as third-party.
// We also rely on IsThirdPartyContext for blob documents because the
// IsThirdPartyChannel check relies on getting the BaseDomain,
// which correctly fails for blobs URIs.
RefPtr<BrowsingContext> bc = aDocument->GetBrowsingContext();
return bc ? IsThirdPartyContext(bc) : true;
}

View file

@ -114,6 +114,10 @@ skip-if = ["os == 'mac' && !debug"] # Bug 1503778, 1577362
["browser_partitionedABA.js"]
["browser_partitionedABnavigatestToAA.js"]
["browser_partitionedBlobSubresources.js"]
["browser_partitionedClearSiteDataHeader.js"]
support-files = ["clearSiteData.sjs"]
@ -122,14 +126,14 @@ support-files = ["clearSiteData.sjs"]
["browser_partitionedCookies.js"]
support-files = ["cookies.sjs"]
["browser_partitionedDOMCache.js"]
["browser_partitionedDedicatedWorker.js"]
support-files = [
"cookies.sjs",
"dedicatedWorker.js",
]
["browser_partitionedDOMCache.js"]
["browser_partitionedIndexedDB.js"]
["browser_partitionedLocalStorage.js"]

View file

@ -0,0 +1,74 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* A test to verify that A(B->A) navigated iframes have unparitioned
* cookies and storage access.
*/
"use strict";
add_setup(async function () {
await setCookieBehaviorPref(
BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
false
);
});
add_task(async function runTest() {
info("Creating the tab");
let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
await BrowserTestUtils.browserLoaded(browser);
info("Creating the third-party iframe");
let ifrBC = await SpecialPowers.spawn(
browser,
[TEST_TOP_PAGE_7],
async page => {
let ifr = content.document.createElement("iframe");
ifr.id = "iframe";
let loading = ContentTaskUtils.waitForEvent(ifr, "load");
content.document.body.appendChild(ifr);
ifr.src = page;
await loading;
return ifr.browsingContext;
}
);
is(
ifrBC.currentWindowGlobal.documentStoragePrincipal.originAttributes
.partitionKey,
"(http,example.net)",
"partitioned after load"
);
info("Navigating the AB iframe to AA");
await SpecialPowers.spawn(browser, [TEST_TOP_PAGE], async page => {
let ifr = content.document.getElementById("iframe");
let loading = ContentTaskUtils.waitForEvent(ifr, "load");
ifr.src = page;
await loading;
});
is(
ifrBC.currentWindowGlobal.documentStoragePrincipal.originAttributes
.partitionKey,
"",
"unpartitioned after navigation"
);
info("Clean up");
BrowserTestUtils.removeTab(tab);
await new Promise(resolve => {
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
});

View file

@ -0,0 +1,72 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* A test to verify that same-site blob iframes' subresources have access to their unpartitioned cookies
*/
"use strict";
add_setup(async function () {
await setCookieBehaviorPref(
BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
false
);
});
add_task(async function runTest() {
info("Creating the tab");
let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
await BrowserTestUtils.browserLoaded(browser);
await SpecialPowers.spawn(browser, [], function () {
content.document.cookie = "foo=bar; SameSite=Strict";
});
info("Creating the bloburl iframe");
await SpecialPowers.spawn(
browser,
[`${TEST_DOMAIN + TEST_PATH}cookies.sjs`],
async url => {
let blobData = `
<html>
<body>
<script>
fetch("${url}")
.then((response) => response.text())
.then((text) => {
window.parent.postMessage(text);
});
</script>
</body>
</html>
`;
let blobURL = content.URL.createObjectURL(new content.Blob([blobData]));
let ifr = content.document.createElement("iframe");
let iframeSubresourceCookiesEventPromise = ContentTaskUtils.waitForEvent(
content.window,
"message"
);
content.document.body.appendChild(ifr);
ifr.src = blobURL;
info("waiting for the iframe to get its http cookies");
let iframeSubresourceCookiesEvent =
await iframeSubresourceCookiesEventPromise;
is(iframeSubresourceCookiesEvent.data, "cookie:foo=bar");
}
);
info("Clean up");
BrowserTestUtils.removeTab(tab);
await new Promise(resolve => {
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
});

View file

@ -244,20 +244,20 @@ add_task(async function testInstallTriggerFromSubframe() {
const testCases = [
["blank iframe with no attributes", SECURE_TESTROOT, {}, expected.http],
["iframe srcdoc=''", SECURE_TESTROOT, { srcdoc: "" }, expected.http],
// These are blocked by a Firefox doorhanger and the user can't allow it neither.
[
"http page iframe src='blob:...'",
SECURE_TESTROOT,
{ src: "blob:" },
expected.httpBlockedOnOrigin,
expected.httpBlob,
],
[
"file page iframe src='blob:...'",
fileURL,
{ src: "blob:" },
expected.otherBlockedOnOrigin,
expected.fileBlob,
],
// These are blocked by a Firefox doorhanger and the user can't allow it neither.
[
"blank iframe embedded into a top-level sandbox page",
`${SECURE_TESTROOT}sandboxed.html`,
@ -331,7 +331,7 @@ add_task(function testInstallBlankFrameNestedIntoBlobURLPage() {
{
source: "test-host",
},
/* expectBlockedOrigin */ true
/* expectBlockedOrigin */ false
);
});