412 lines
15 KiB
JavaScript
412 lines
15 KiB
JavaScript
/* 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/. */
|
|
|
|
const { NetUtil } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/NetUtil.sys.mjs"
|
|
);
|
|
|
|
registerCleanupFunction(() => {
|
|
Services.prefs.clearUserPref("network.cookie.cookieBehavior");
|
|
Services.prefs.clearUserPref(
|
|
"network.cookieJarSettings.unblocked_for_testing"
|
|
);
|
|
Services.prefs.clearUserPref("network.cookie.CHIPS.enabled");
|
|
Services.prefs.clearUserPref("network.cookie.chips.partitionLimitEnabled");
|
|
Services.prefs.clearUserPref(
|
|
"network.cookie.chips.partitionLimitByteCapacity"
|
|
);
|
|
Services.prefs.clearUserPref("network.cookie.chips.partitionLimitDryRun");
|
|
|
|
Services.cookies.removeAll();
|
|
});
|
|
|
|
// enable chips and chips partition limit
|
|
add_setup(async () => {
|
|
Services.prefs.setIntPref("network.cookie.cookieBehavior", 5);
|
|
Services.prefs.setBoolPref(
|
|
"network.cookieJarSettings.unblocked_for_testing",
|
|
true
|
|
);
|
|
Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", true);
|
|
Services.prefs.setBoolPref(
|
|
"network.cookie.chips.partitionLimitEnabled",
|
|
true
|
|
);
|
|
Services.prefs.setBoolPref(
|
|
"network.cookie.chips.partitionLimitDryRun",
|
|
false
|
|
);
|
|
|
|
// FOG needs a profile directory to put its data in.
|
|
do_get_profile();
|
|
// FOG needs to be initialized in order for data to flow.
|
|
Services.fog.initializeFOG();
|
|
});
|
|
|
|
function headerify(cookie, index, partitioned) {
|
|
let maxAge = 9000 + index; // use index so cookies can be ordered by age
|
|
// there is no way to test insecure purging first with a partitioned cookie without `Secure`
|
|
// these cookies would be immediately rejected
|
|
let mostHeaders = `; Max-Age=${maxAge}; SameSite=None; Secure`;
|
|
let temp = cookie.concat(mostHeaders);
|
|
if (partitioned) {
|
|
temp = temp.concat("; Partitioned");
|
|
}
|
|
return temp;
|
|
}
|
|
|
|
async function checkReportedOverflow(expected) {
|
|
let reported =
|
|
await Glean.networking.cookieChipsPartitionLimitOverflow.testGetValue();
|
|
if (expected == 0) {
|
|
Assert.equal(reported, null);
|
|
return;
|
|
}
|
|
// verify the telemetry by summing, but the individual reports are not aggregated
|
|
Assert.equal(reported.sum, expected);
|
|
}
|
|
|
|
function channelMaybePartitioned(uri, partition) {
|
|
let channel = NetUtil.newChannel({
|
|
uri,
|
|
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
|
|
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
|
|
loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
|
});
|
|
if (partition) {
|
|
let cookieJarSettings = Cc[
|
|
"@mozilla.org/cookieJarSettings;1"
|
|
].createInstance(Ci.nsICookieJarSettings);
|
|
cookieJarSettings.initWithURI(uri, false);
|
|
channel.loadInfo.cookieJarSettings = cookieJarSettings;
|
|
}
|
|
return channel;
|
|
}
|
|
|
|
const BYTE_LIMIT = 10240;
|
|
const BYTE_LIMIT_WITH_BUFFER = BYTE_LIMIT * 1.2; // 12288
|
|
const BYTES_PER_COOKIE = 100;
|
|
const COOKIES_TO_SET_COUNT = 122;
|
|
const COOKIES_TO_SET_BYTES = COOKIES_TO_SET_COUNT * BYTES_PER_COOKIE; // 12200
|
|
|
|
// set many cookies at 100 Bytes each
|
|
// partition maximum is 10KiB or 10240B (only for partitioned cookies)
|
|
// so after this function is called any partitioned cookie (100B) will exceed
|
|
function setManyCookies(uri, channel, partitioned) {
|
|
let cookieString = "";
|
|
let cookieNames = [];
|
|
for (let i = 0; i < COOKIES_TO_SET_COUNT; i++) {
|
|
let name = "c" + i.toString();
|
|
let value =
|
|
i + "_".repeat(BYTES_PER_COOKIE - i.toString().length - name.length);
|
|
let cookie = name + "=" + value;
|
|
cookieNames.push(name);
|
|
|
|
Services.cookies.setCookieStringFromHttp(
|
|
uri,
|
|
headerify(cookie, i, partitioned),
|
|
channel
|
|
);
|
|
|
|
// prep the expected value
|
|
cookieString += cookie;
|
|
if (i < COOKIES_TO_SET_COUNT - 1) {
|
|
cookieString += "; ";
|
|
}
|
|
}
|
|
|
|
return { cookieString, cookieNames };
|
|
}
|
|
|
|
// unpartitioned cookies should not be purged
|
|
add_task(async function test_chips_limit_parent_http_unpartitioned() {
|
|
let baseDomain = "example.org";
|
|
let uri = NetUtil.newURI("https://" + baseDomain + "/");
|
|
let channel = channelMaybePartitioned(uri, baseDomain, false);
|
|
|
|
let expected = setManyCookies(uri, channel, false);
|
|
expected.cookieNames.push("exceeded");
|
|
|
|
// pre-condition: check that all got added as expected
|
|
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
Assert.equal(actual, expected.cookieString);
|
|
await checkReportedOverflow(0);
|
|
|
|
// unpartitioned cookies, no limit here
|
|
let cookie = "exceeded".concat("=").concat("x".repeat(240));
|
|
Services.cookies.setCookieStringFromHttp(
|
|
uri,
|
|
headerify(cookie, COOKIES_TO_SET_COUNT, false), // use count for uniqueness
|
|
channel
|
|
);
|
|
|
|
await checkReportedOverflow(0);
|
|
|
|
// extract cookie names from string and compare to expected values
|
|
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
let cookies = second.split("; ");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
|
|
}
|
|
Assert.deepEqual(cookies, expected.cookieNames);
|
|
Assert.equal(cookies.length, expected.cookieNames.length);
|
|
Services.cookies.removeAll();
|
|
Services.fog.testResetFOG();
|
|
});
|
|
|
|
// parent http partition cookies exceeding capacity should purge in FIFO manner
|
|
add_task(async function test_chips_limit_parent_http_partitioned() {
|
|
let baseDomain = "example.org";
|
|
let uri = NetUtil.newURI("https://" + baseDomain + "/");
|
|
let channel = channelMaybePartitioned(uri, baseDomain, true);
|
|
|
|
let expected = setManyCookies(uri, channel, true);
|
|
expected.cookieNames.push("exceeded");
|
|
// with the buffer quite a few cookies will be purged
|
|
for (let i = 0; i < 23; i++) {
|
|
expected.cookieNames.shift();
|
|
}
|
|
|
|
// pre-condition: check that all got added as expected
|
|
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
Assert.equal(actual, expected.cookieString);
|
|
await checkReportedOverflow(0); // no reporting until over the hard cap
|
|
|
|
// adding 248 Bytes has excess of 208Bytes (3 cookies will be purged (FIFO))
|
|
let cookie = "exceeded".concat("=").concat("x".repeat(240));
|
|
let cookieNameValueLen = cookie.length - 1;
|
|
Services.cookies.setCookieStringFromHttp(
|
|
uri,
|
|
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
|
|
channel
|
|
);
|
|
|
|
let expectedOverflow =
|
|
COOKIES_TO_SET_BYTES + cookieNameValueLen - BYTE_LIMIT_WITH_BUFFER;
|
|
await checkReportedOverflow(expectedOverflow);
|
|
|
|
// extract cookie names from string and compare to expected values
|
|
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
let cookies = second.split("; ");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
|
|
}
|
|
Assert.deepEqual(cookies, expected.cookieNames);
|
|
Assert.equal(cookies.length, expected.cookieNames.length);
|
|
Services.cookies.removeAll();
|
|
Services.fog.testResetFOG();
|
|
});
|
|
|
|
// partition limit should still work for cookie overwrites
|
|
add_task(async function test_chips_limit_overwrites_can_purge() {
|
|
let baseDomain = "example.org";
|
|
let uri = NetUtil.newURI("https://" + baseDomain + "/");
|
|
let channel = channelMaybePartitioned(uri, baseDomain, true);
|
|
|
|
let expected = setManyCookies(uri, channel, true);
|
|
for (let i = 0; i < 22; i++) {
|
|
expected.cookieNames.shift();
|
|
}
|
|
|
|
// pre-condition: check that all got added as expected
|
|
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
Assert.equal(actual, expected.cookieString);
|
|
await checkReportedOverflow(0);
|
|
|
|
// cookie which already exists also triggers purge
|
|
// 244 (new cookie) - 100 (existing cookie) -> 144 newly added bytes
|
|
// So we are in excess by 104 bytes, means 2 cookies need purging (FIFO)
|
|
let cookie = "c101".concat("=").concat("x".repeat(240)); // 244
|
|
let cookieNameValueLen = cookie.length - 1;
|
|
Services.cookies.setCookieStringFromHttp(
|
|
uri,
|
|
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
|
|
channel
|
|
);
|
|
|
|
let expectedOverflow =
|
|
COOKIES_TO_SET_BYTES +
|
|
cookieNameValueLen -
|
|
BYTES_PER_COOKIE -
|
|
BYTE_LIMIT_WITH_BUFFER;
|
|
await checkReportedOverflow(expectedOverflow);
|
|
|
|
// extract cookie names from string and compare to expected values
|
|
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
let cookies = second.split("; ");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
|
|
}
|
|
Assert.deepEqual(cookies, expected.cookieNames);
|
|
Assert.equal(cookies.length, expected.cookieNames.length);
|
|
Services.cookies.removeAll();
|
|
Services.fog.testResetFOG();
|
|
});
|
|
|
|
// dry run mode should not purge, but still report excess via telemetry
|
|
add_task(async function test_chips_limit_dry_run_no_purge() {
|
|
Services.prefs.setBoolPref("network.cookie.chips.partitionLimitDryRun", true);
|
|
|
|
let baseDomain = "example.org";
|
|
let uri = NetUtil.newURI("https://" + baseDomain + "/");
|
|
let channel = channelMaybePartitioned(uri, baseDomain, true);
|
|
let expected = setManyCookies(uri, channel, true);
|
|
expected.cookieNames.push("exceeded");
|
|
|
|
// pre-condition: check that all got added as expected
|
|
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
Assert.equal(actual, expected.cookieString);
|
|
await checkReportedOverflow(0);
|
|
|
|
// adding 248 Bytes has excess of 208Bytes (3 cookies will be purged (FIFO))
|
|
let cookie = "exceeded".concat("=").concat("x".repeat(240));
|
|
let cookieNameValueLen = cookie.length - 1;
|
|
Services.cookies.setCookieStringFromHttp(
|
|
uri,
|
|
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
|
|
channel
|
|
);
|
|
expected.cookieString += "; ".concat(cookie);
|
|
|
|
let expectedOverflow =
|
|
COOKIES_TO_SET_BYTES + cookieNameValueLen - BYTE_LIMIT_WITH_BUFFER;
|
|
await checkReportedOverflow(expectedOverflow);
|
|
|
|
// extract cookie names from string and compare to expected values
|
|
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
let cookies = second.split("; ");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
|
|
}
|
|
Assert.deepEqual(cookies, expected.cookieNames);
|
|
Assert.equal(cookies.length, expected.cookieNames.length);
|
|
Assert.equal(second, expected.cookieString);
|
|
Services.cookies.removeAll();
|
|
Services.fog.testResetFOG();
|
|
});
|
|
|
|
add_task(async function test_chips_limit_chips_off() {
|
|
Services.prefs.setBoolPref(
|
|
"network.cookie.chips.partitionLimitDryRun",
|
|
false
|
|
);
|
|
Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", false);
|
|
|
|
let baseDomain = "example.org";
|
|
let uri = NetUtil.newURI("https://" + baseDomain + "/");
|
|
let channel = channelMaybePartitioned(uri, baseDomain, true);
|
|
let expected = setManyCookies(uri, channel, true);
|
|
expected.cookieNames.push("exceeded");
|
|
|
|
// pre-condition: check that all got added as expected
|
|
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
Assert.equal(actual, expected.cookieString);
|
|
await checkReportedOverflow(0);
|
|
|
|
// shouldn't trigger purge when CHIPS disabled
|
|
let cookie = "exceeded".concat("=").concat("x".repeat(240));
|
|
Services.cookies.setCookieStringFromHttp(
|
|
uri,
|
|
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
|
|
channel
|
|
);
|
|
expected.cookieString += "; ".concat(cookie);
|
|
|
|
await checkReportedOverflow(0);
|
|
|
|
// extract cookie names from string and compare to expected values
|
|
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
let cookies = second.split("; ");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
|
|
}
|
|
Assert.deepEqual(cookies, expected.cookieNames);
|
|
Assert.equal(cookies.length, expected.cookieNames.length);
|
|
Assert.equal(second, expected.cookieString);
|
|
Services.cookies.removeAll();
|
|
Services.fog.testResetFOG();
|
|
});
|
|
|
|
add_task(async function test_chips_limit_chips_limit_off() {
|
|
Services.prefs.setBoolPref(
|
|
"network.cookie.chips.partitionLimitDryRun",
|
|
false
|
|
);
|
|
Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", true);
|
|
|
|
Services.prefs.setBoolPref(
|
|
"network.cookie.chips.partitionLimitEnabled",
|
|
false
|
|
);
|
|
|
|
let baseDomain = "example.org";
|
|
let uri = NetUtil.newURI("https://" + baseDomain + "/");
|
|
let channel = channelMaybePartitioned(uri, baseDomain, true);
|
|
let expected = setManyCookies(uri, channel, true);
|
|
expected.cookieNames.push("exceeded");
|
|
|
|
// pre-condition: check that all got added as expected
|
|
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
Assert.equal(actual, expected.cookieString);
|
|
await checkReportedOverflow(0);
|
|
|
|
// shouldn't trigger purge when CHIPS limit disabled
|
|
let cookie = "exceeded".concat("=").concat("x".repeat(240));
|
|
Services.cookies.setCookieStringFromHttp(
|
|
uri,
|
|
headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness
|
|
channel
|
|
);
|
|
expected.cookieString += "; ".concat(cookie);
|
|
|
|
await checkReportedOverflow(0);
|
|
|
|
// extract cookie names from string and compare to expected values
|
|
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
let cookies = second.split("; ");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
|
|
}
|
|
Assert.deepEqual(cookies, expected.cookieNames);
|
|
Assert.equal(cookies.length, expected.cookieNames.length);
|
|
Assert.equal(second, expected.cookieString);
|
|
Services.cookies.removeAll();
|
|
Services.fog.testResetFOG();
|
|
});
|
|
|
|
// non-chips-partitioned cookies do not trigger the limit
|
|
add_task(async function test_chips_limit_non_chips_partitioned() {
|
|
// channel is partitioned, not cookie header -> non-chips-partition cookies
|
|
let baseDomain = "example.org";
|
|
let uri = NetUtil.newURI("https://" + baseDomain + "/");
|
|
let channel = channelMaybePartitioned(uri, baseDomain, true);
|
|
let expected = setManyCookies(uri, channel, false);
|
|
expected.cookieNames.push("exceeded");
|
|
|
|
// pre-condition: check that all got added as expected
|
|
let actual = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
Assert.equal(actual, expected.cookieString);
|
|
await checkReportedOverflow(0);
|
|
|
|
// non-chips-partitioned cookies, should also have no limit
|
|
let cookie = "exceeded".concat("=").concat("x".repeat(240));
|
|
Services.cookies.setCookieStringFromHttp(
|
|
uri,
|
|
headerify(cookie, COOKIES_TO_SET_COUNT, false), // use count for uniqueness
|
|
channel
|
|
);
|
|
|
|
await checkReportedOverflow(0);
|
|
|
|
// extract cookie names from string and compare to expected values
|
|
let second = Services.cookies.getCookieStringFromHttp(uri, channel);
|
|
let cookies = second.split("; ");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
cookies[i] = cookies[i].substr(0, cookies[i].indexOf("="));
|
|
}
|
|
Assert.deepEqual(cookies, expected.cookieNames);
|
|
Assert.equal(cookies.length, expected.cookieNames.length);
|
|
Services.cookies.removeAll();
|
|
Services.fog.testResetFOG();
|
|
});
|