Bug 1872896 - Add deprecation console message for cookies that are foreign and not Partitioned - r=timhuang,cookie-reviewers,anti-tracking-reviewers

In order to do this, I had to alter how we do third-party cookie blocking under CHIPS + TCP.

Before we performed the block in CheckPrefs, where we haven't parsed the cookie header (which can have multiple cookie lines) yet.
Instead, I added a new argument into CanSetCookie, which says if the cookie needs to be partitioned to be accepted (TCP && foreign && not unpartitioned).
Then inside of CanSetCookie, if that is set we warn in the console and drop the cookie if the CHIPS preference is set. The warning changes based on that pref too.

Differential Revision: https://phabricator.services.mozilla.com/D197711
This commit is contained in:
Benjamin VanderSloot 2024-01-09 15:52:30 +00:00
parent 79dda86c11
commit 57b9951a5d
9 changed files with 205 additions and 27 deletions

View file

@ -397,6 +397,12 @@ already_AddRefed<Cookie> CookieCommons::CreateCookieFromDocument(
}
}
bool mustBePartitioned =
isForeignAndNotAddon &&
aDocument->CookieJarSettings()->GetCookieBehavior() ==
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
!aDocument->UsingStorageAccess();
// If we are here, we have been already accepted by the anti-tracking.
// We just need to check if we have to be in session-only mode.
CookieStatus cookieStatus = CookieStatusForWindow(innerWindow, principalURI);
@ -415,7 +421,8 @@ already_AddRefed<Cookie> CookieCommons::CreateCookieFromDocument(
bool canSetCookie = false;
CookieService::CanSetCookie(principalURI, baseDomain, cookieData,
requireHostMatch, cookieStatus, cookieString,
false, isForeignAndNotAddon, crc, canSetCookie);
false, isForeignAndNotAddon, mustBePartitioned,
crc, canSetCookie);
if (!canSetCookie) {
return nullptr;

View file

@ -119,6 +119,7 @@ namespace net {
static StaticRefPtr<CookieService> gCookieService;
constexpr auto CONSOLE_CHIPS_CATEGORY = "cookiesCHIPS"_ns;
constexpr auto CONSOLE_SAMESITE_CATEGORY = "cookieSameSite"_ns;
constexpr auto CONSOLE_OVERSIZE_CATEGORY = "cookiesOversize"_ns;
constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
@ -686,6 +687,12 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
&isForeignAndNotAddon);
}
bool mustBePartitioned =
isForeignAndNotAddon &&
cookieJarSettings->GetCookieBehavior() ==
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
!result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted);
nsCString cookieHeader(aCookieHeader);
bool moreCookieToRead = true;
@ -695,9 +702,10 @@ CookieService::SetCookieStringFromHttp(nsIURI* aHostURI,
CookieStruct cookieData;
bool canSetCookie = false;
moreCookieToRead = CanSetCookie(
aHostURI, baseDomain, cookieData, requireHostMatch, cookieStatus,
cookieHeader, true, isForeignAndNotAddon, crc, canSetCookie);
moreCookieToRead =
CanSetCookie(aHostURI, baseDomain, cookieData, requireHostMatch,
cookieStatus, cookieHeader, true, isForeignAndNotAddon,
mustBePartitioned, crc, canSetCookie);
if (!canSetCookie) {
continue;
@ -1146,8 +1154,8 @@ static void RecordPartitionedTelemetry(const CookieStruct& aCookieData,
bool CookieService::CanSetCookie(
nsIURI* aHostURI, const nsACString& aBaseDomain, CookieStruct& aCookieData,
bool aRequireHostMatch, CookieStatus aStatus, nsCString& aCookieHeader,
bool aFromHttp, bool aIsForeignAndNotAddon, nsIConsoleReportCollector* aCRC,
bool& aSetCookie) {
bool aFromHttp, bool aIsForeignAndNotAddon, bool aPartitionedOnly,
nsIConsoleReportCollector* aCRC, bool& aSetCookie) {
MOZ_ASSERT(aHostURI);
aSetCookie = false;
@ -1338,6 +1346,31 @@ bool CookieService::CanSetCookie(
return newCookie;
}
// If the cookie does not have the partitioned attribute,
// but is foreign we should give the developer a message.
// If CHIPS isn't required yet, we will warn the console
// that we have upcoming changes. Otherwise we give a rejection message.
if (aPartitionedOnly && !aCookieData.isPartitioned() &&
aIsForeignAndNotAddon) {
if (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) {
COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
"foreign cookies must be partitioned");
CookieLogging::LogMessageToConsole(
aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
"CookieForeignNoPartitionedError"_ns,
AutoTArray<nsString, 1>{
NS_ConvertUTF8toUTF16(aCookieData.name()),
});
return newCookie;
}
CookieLogging::LogMessageToConsole(
aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_CHIPS_CATEGORY,
"CookieForeignNoPartitionedWarning"_ns,
AutoTArray<nsString, 1>{
NS_ConvertUTF8toUTF16(aCookieData.name()),
});
}
aSetCookie = true;
return newCookie;
}
@ -1804,22 +1837,6 @@ CookieStatus CookieService::CheckPrefs(
return STATUS_REJECTED;
}
if (aCookieJarSettings->GetCookieBehavior() ==
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
StaticPrefs::network_cookie_cookieBehavior_optInPartitioning() &&
!aStorageAccessPermissionGranted) {
COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,
"context is third party");
CookieLogging::LogMessageToConsole(
aCRC, aHostURI, nsIScriptError::warningFlag,
CONSOLE_REJECTION_CATEGORY, "CookieRejectedThirdParty"_ns,
AutoTArray<nsString, 1>{
NS_ConvertUTF8toUTF16(aCookieHeader),
});
*aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
return STATUS_REJECTED;
}
if (aCookieJarSettings->GetLimitForeignContexts() &&
!aStorageAccessPermissionGranted && aNumOfCookies == 0) {
COOKIE_LOGFAILURE(!aCookieHeader.IsVoid(), aHostURI, aCookieHeader,

View file

@ -70,6 +70,7 @@ class CookieService final : public nsICookieService,
CookieStruct& aCookieData, bool aRequireHostMatch,
CookieStatus aStatus, nsCString& aCookieHeader,
bool aFromHttp, bool aIsForeignAndNotAddon,
bool aPartitionedOnly,
nsIConsoleReportCollector* aCRC, bool& aSetCookie);
static CookieStatus CheckPrefs(
nsIConsoleReportCollector* aCRC, nsICookieJarSettings* aCookieJarSettings,

View file

@ -531,13 +531,20 @@ CookieServiceChild::SetCookieStringFromHttp(nsIURI* aHostURI,
&isForeignAndNotAddon);
}
bool mustBePartitioned =
isForeignAndNotAddon &&
cookieJarSettings->GetCookieBehavior() ==
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
!result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted);
bool moreCookies;
do {
CookieStruct cookieData;
bool canSetCookie = false;
moreCookies = CookieService::CanSetCookie(
aHostURI, baseDomain, cookieData, requireHostMatch, cookieStatus,
cookieString, true, isForeignAndNotAddon, crc, canSetCookie);
cookieString, true, isForeignAndNotAddon, mustBePartitioned, crc,
canSetCookie);
if (!canSetCookie) {
continue;
}

View file

@ -26,6 +26,9 @@ support-files = ["oversize.sjs"]
["browser_partitioned_telemetry.js"]
support-files = ["partitioned.sjs"]
["browser_partitionedConsole.js"]
support-files = ["partitioned.sjs"]
["browser_sameSiteConsole.js"]
support-files = ["sameSite.sjs"]

View file

@ -25,6 +25,14 @@ function securify(cookie) {
}
registerCleanupFunction(() => {
Services.prefs.clearUserPref("dom.security.https_first");
Services.prefs.clearUserPref("network.cookie.cookieBehavior");
Services.prefs.clearUserPref(
"network.cookieJarSettings.unblocked_for_testing"
);
Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
Services.prefs.clearUserPref("network.cookie.sameSite.noneRequiresSecure");
Services.prefs.clearUserPref("network.cookie.sameSite.schemeful");
info("Cleaning up the test");
});

View file

@ -0,0 +1,133 @@
"use strict";
const DOMAIN = "https://example.com/";
const PATH = "browser/netwerk/cookie/test/browser/";
const TOP_PAGE = DOMAIN + PATH + "file_empty.html";
// Run the test with CHIPS disabled, expecting a warning message
add_task(async _ => {
await SpecialPowers.pushPrefEnv({
set: [["network.cookie.cookieBehavior.optInPartitioning", false]],
});
const expected = [];
const consoleListener = {
observe(what) {
if (!(what instanceof Ci.nsIConsoleMessage)) {
return;
}
info("Console Listener: " + what);
for (let i = expected.length - 1; i >= 0; --i) {
const e = expected[i];
if (what.message.includes(e.match)) {
ok(true, "Message received: " + e.match);
expected.splice(i, 1);
e.resolve();
}
}
},
};
Services.console.registerListener(consoleListener);
registerCleanupFunction(() =>
Services.console.unregisterListener(consoleListener)
);
const netPromises = [
new Promise(resolve => {
expected.push({
resolve,
match:
"Cookie “a” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute.",
});
}),
];
// Let's open our tab.
const tab = BrowserTestUtils.addTab(gBrowser, TOP_PAGE);
gBrowser.selectedTab = tab;
const browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
// Set cookies with cross-site HTTP
await SpecialPowers.spawn(browser, [], async function () {
await content.fetch(
"https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs",
{ credentials: "include" }
);
});
// Let's wait for the first set of console events.
await Promise.all(netPromises);
// Let's close the tab.
BrowserTestUtils.removeTab(tab);
});
// Run the test with CHIPS enabled, expecting a different warning message
add_task(async _ => {
await SpecialPowers.pushPrefEnv({
set: [["network.cookie.cookieBehavior.optInPartitioning", true]],
});
const expected = [];
const consoleListener = {
observe(what) {
if (!(what instanceof Ci.nsIConsoleMessage)) {
return;
}
info("Console Listener: " + what);
for (let i = expected.length - 1; i >= 0; --i) {
const e = expected[i];
if (what.message.includes(e.match)) {
ok(true, "Message received: " + e.match);
expected.splice(i, 1);
e.resolve();
}
}
},
};
Services.console.registerListener(consoleListener);
registerCleanupFunction(() =>
Services.console.unregisterListener(consoleListener)
);
const netPromises = [
new Promise(resolve => {
expected.push({
resolve,
match:
"Cookie “a” has been rejected because it is foreign and does not have the “Partitioned“ attribute.",
});
}),
];
// Let's open our tab.
const tab = BrowserTestUtils.addTab(gBrowser, TOP_PAGE);
gBrowser.selectedTab = tab;
const browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
// Set cookies with cross-site HTTP
await SpecialPowers.spawn(browser, [], async function () {
await content.fetch(
"https://example.org/browser/netwerk/cookie/test/browser/partitioned.sjs",
{ credentials: "include" }
);
});
// Let's wait for the first set of console events.
await Promise.all(netPromises);
// Let's close the tab.
BrowserTestUtils.removeTab(tab);
});

View file

@ -87,6 +87,12 @@ CookieRejectedNonsecureOverSecure=Cookie “%1$S” has been rejected because th
# LOCALIZATION NOTE (CookieRejectedForNonSameSiteness): %1$S is the cookie name.
CookieRejectedForNonSameSiteness=Cookie “%1$S” has been rejected because it is in a cross-site context and its “SameSite” is “Lax” or “Strict”.
# LOCALIZATION NOTE (CookieForeignNoPartitionedWarning): %1$S is the cookie name. Do not translate "Partitioned"
CookieForeignNoPartitionedWarning=Cookie “%1$S” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute.
# LOCALIZATION NOTE (CookieForeignNoPartitionedError): %1$S is the cookie name. Do not translate "Partitioned"
CookieForeignNoPartitionedError=Cookie “%1$S” has been rejected because it is foreign and does not have the “Partitioned“ attribute.
# LOCALIZATION NOTE (CookieBlockedCrossSiteRedirect): %1$S is the cookie name. Do not translate "SameSite", "Lax" or "Strict".
CookieBlockedCrossSiteRedirect=Cookie “%1$S” with the “SameSite” attribute value “Lax” or “Strict” was omitted because of a cross-site redirect.

View file

@ -210,8 +210,6 @@ AntiTracking._createTask({
);
},
extraPrefs: [["network.cookie.cookieBehavior.optInPartitioning", true]],
expectedBlockingNotifications:
Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN,
thirdPartyPage: TEST_4TH_PARTY_PAGE,
runInPrivateWindow: false,
iframeSandbox: null,
@ -247,8 +245,6 @@ AntiTracking._createTask({
);
},
extraPrefs: [["network.cookie.cookieBehavior.optInPartitioning", true]],
expectedBlockingNotifications:
Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN,
thirdPartyPage: TEST_4TH_PARTY_PAGE,
runInPrivateWindow: true,
iframeSandbox: null,