test_chips_partition_capping.js (15110B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const { NetUtil } = ChromeUtils.importESModule( 6 "resource://gre/modules/NetUtil.sys.mjs" 7 ); 8 9 registerCleanupFunction(() => { 10 Services.prefs.clearUserPref("network.cookie.cookieBehavior"); 11 Services.prefs.clearUserPref( 12 "network.cookieJarSettings.unblocked_for_testing" 13 ); 14 Services.prefs.clearUserPref("network.cookie.CHIPS.enabled"); 15 Services.prefs.clearUserPref("network.cookie.chips.partitionLimitEnabled"); 16 Services.prefs.clearUserPref( 17 "network.cookie.chips.partitionLimitByteCapacity" 18 ); 19 Services.prefs.clearUserPref("network.cookie.chips.partitionLimitDryRun"); 20 21 Services.cookies.removeAll(); 22 }); 23 24 // enable chips and chips partition limit 25 add_setup(async () => { 26 Services.prefs.setIntPref("network.cookie.cookieBehavior", 5); 27 Services.prefs.setBoolPref( 28 "network.cookieJarSettings.unblocked_for_testing", 29 true 30 ); 31 Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", true); 32 Services.prefs.setBoolPref( 33 "network.cookie.chips.partitionLimitEnabled", 34 true 35 ); 36 Services.prefs.setBoolPref( 37 "network.cookie.chips.partitionLimitDryRun", 38 false 39 ); 40 41 // FOG needs a profile directory to put its data in. 42 do_get_profile(); 43 // FOG needs to be initialized in order for data to flow. 44 Services.fog.initializeFOG(); 45 }); 46 47 function headerify(cookie, index, partitioned) { 48 let maxAge = 9000 + index; // use index so cookies can be ordered by age 49 // there is no way to test insecure purging first with a partitioned cookie without `Secure` 50 // these cookies would be immediately rejected 51 let mostHeaders = `; Max-Age=${maxAge}; SameSite=None; Secure`; 52 let temp = cookie.concat(mostHeaders); 53 if (partitioned) { 54 temp = temp.concat("; Partitioned"); 55 } 56 return temp; 57 } 58 59 async function checkReportedOverflow(expected) { 60 let reported = 61 await Glean.networking.cookieChipsPartitionLimitOverflow.testGetValue(); 62 if (expected == 0) { 63 Assert.equal(reported, null); 64 return; 65 } 66 // verify the telemetry by summing, but the individual reports are not aggregated 67 Assert.equal(reported.sum, expected); 68 } 69 70 function channelMaybePartitioned(uri, partition) { 71 let channel = NetUtil.newChannel({ 72 uri, 73 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, 74 securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT, 75 loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), 76 }); 77 if (partition) { 78 let cookieJarSettings = Cc[ 79 "@mozilla.org/cookieJarSettings;1" 80 ].createInstance(Ci.nsICookieJarSettings); 81 cookieJarSettings.initWithURI(uri, false); 82 channel.loadInfo.cookieJarSettings = cookieJarSettings; 83 } 84 return channel; 85 } 86 87 const BYTE_LIMIT = 10240; 88 const BYTE_LIMIT_WITH_BUFFER = BYTE_LIMIT * 1.2; // 12288 89 const BYTES_PER_COOKIE = 100; 90 const COOKIES_TO_SET_COUNT = 122; 91 const COOKIES_TO_SET_BYTES = COOKIES_TO_SET_COUNT * BYTES_PER_COOKIE; // 12200 92 93 // set many cookies at 100 Bytes each 94 // partition maximum is 10KiB or 10240B (only for partitioned cookies) 95 // so after this function is called any partitioned cookie (100B) will exceed 96 function setManyCookies(uri, channel, partitioned) { 97 let cookieString = ""; 98 let cookieNames = []; 99 for (let i = 0; i < COOKIES_TO_SET_COUNT; i++) { 100 let name = "c" + i.toString(); 101 let value = 102 i + "_".repeat(BYTES_PER_COOKIE - i.toString().length - name.length); 103 let cookie = name + "=" + value; 104 cookieNames.push(name); 105 106 Services.cookies.setCookieStringFromHttp( 107 uri, 108 headerify(cookie, i, partitioned), 109 channel 110 ); 111 112 // prep the expected value 113 cookieString += cookie; 114 if (i < COOKIES_TO_SET_COUNT - 1) { 115 cookieString += "; "; 116 } 117 } 118 119 return { cookieString, cookieNames }; 120 } 121 122 // unpartitioned cookies should not be purged 123 add_task(async function test_chips_limit_parent_http_unpartitioned() { 124 let baseDomain = "example.org"; 125 let uri = NetUtil.newURI("https://" + baseDomain + "/"); 126 let channel = channelMaybePartitioned(uri, baseDomain, false); 127 128 let expected = setManyCookies(uri, channel, false); 129 expected.cookieNames.push("exceeded"); 130 131 // pre-condition: check that all got added as expected 132 let actual = Services.cookies.getCookieStringFromHttp(uri, channel); 133 Assert.equal(actual, expected.cookieString); 134 await checkReportedOverflow(0); 135 136 // unpartitioned cookies, no limit here 137 let cookie = "exceeded".concat("=").concat("x".repeat(240)); 138 Services.cookies.setCookieStringFromHttp( 139 uri, 140 headerify(cookie, COOKIES_TO_SET_COUNT, false), // use count for uniqueness 141 channel 142 ); 143 144 await checkReportedOverflow(0); 145 146 // extract cookie names from string and compare to expected values 147 let second = Services.cookies.getCookieStringFromHttp(uri, channel); 148 let cookies = second.split("; "); 149 for (let i = 0; i < cookies.length; i++) { 150 cookies[i] = cookies[i].substr(0, cookies[i].indexOf("=")); 151 } 152 Assert.deepEqual(cookies, expected.cookieNames); 153 Assert.equal(cookies.length, expected.cookieNames.length); 154 Services.cookies.removeAll(); 155 Services.fog.testResetFOG(); 156 }); 157 158 // parent http partition cookies exceeding capacity should purge in FIFO manner 159 add_task(async function test_chips_limit_parent_http_partitioned() { 160 let baseDomain = "example.org"; 161 let uri = NetUtil.newURI("https://" + baseDomain + "/"); 162 let channel = channelMaybePartitioned(uri, baseDomain, true); 163 164 let expected = setManyCookies(uri, channel, true); 165 expected.cookieNames.push("exceeded"); 166 // with the buffer quite a few cookies will be purged 167 for (let i = 0; i < 23; i++) { 168 expected.cookieNames.shift(); 169 } 170 171 // pre-condition: check that all got added as expected 172 let actual = Services.cookies.getCookieStringFromHttp(uri, channel); 173 Assert.equal(actual, expected.cookieString); 174 await checkReportedOverflow(0); // no reporting until over the hard cap 175 176 // adding 248 Bytes has excess of 208Bytes (3 cookies will be purged (FIFO)) 177 let cookie = "exceeded".concat("=").concat("x".repeat(240)); 178 let cookieNameValueLen = cookie.length - 1; 179 Services.cookies.setCookieStringFromHttp( 180 uri, 181 headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness 182 channel 183 ); 184 185 let expectedOverflow = 186 COOKIES_TO_SET_BYTES + cookieNameValueLen - BYTE_LIMIT_WITH_BUFFER; 187 await checkReportedOverflow(expectedOverflow); 188 189 // extract cookie names from string and compare to expected values 190 let second = Services.cookies.getCookieStringFromHttp(uri, channel); 191 let cookies = second.split("; "); 192 for (let i = 0; i < cookies.length; i++) { 193 cookies[i] = cookies[i].substr(0, cookies[i].indexOf("=")); 194 } 195 Assert.deepEqual(cookies, expected.cookieNames); 196 Assert.equal(cookies.length, expected.cookieNames.length); 197 Services.cookies.removeAll(); 198 Services.fog.testResetFOG(); 199 }); 200 201 // partition limit should still work for cookie overwrites 202 add_task(async function test_chips_limit_overwrites_can_purge() { 203 let baseDomain = "example.org"; 204 let uri = NetUtil.newURI("https://" + baseDomain + "/"); 205 let channel = channelMaybePartitioned(uri, baseDomain, true); 206 207 let expected = setManyCookies(uri, channel, true); 208 for (let i = 0; i < 22; i++) { 209 expected.cookieNames.shift(); 210 } 211 212 // pre-condition: check that all got added as expected 213 let actual = Services.cookies.getCookieStringFromHttp(uri, channel); 214 Assert.equal(actual, expected.cookieString); 215 await checkReportedOverflow(0); 216 217 // cookie which already exists also triggers purge 218 // 244 (new cookie) - 100 (existing cookie) -> 144 newly added bytes 219 // So we are in excess by 104 bytes, means 2 cookies need purging (FIFO) 220 let cookie = "c101".concat("=").concat("x".repeat(240)); // 244 221 let cookieNameValueLen = cookie.length - 1; 222 Services.cookies.setCookieStringFromHttp( 223 uri, 224 headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness 225 channel 226 ); 227 228 let expectedOverflow = 229 COOKIES_TO_SET_BYTES + 230 cookieNameValueLen - 231 BYTES_PER_COOKIE - 232 BYTE_LIMIT_WITH_BUFFER; 233 await checkReportedOverflow(expectedOverflow); 234 235 // extract cookie names from string and compare to expected values 236 let second = Services.cookies.getCookieStringFromHttp(uri, channel); 237 let cookies = second.split("; "); 238 for (let i = 0; i < cookies.length; i++) { 239 cookies[i] = cookies[i].substr(0, cookies[i].indexOf("=")); 240 } 241 Assert.deepEqual(cookies, expected.cookieNames); 242 Assert.equal(cookies.length, expected.cookieNames.length); 243 Services.cookies.removeAll(); 244 Services.fog.testResetFOG(); 245 }); 246 247 // dry run mode should not purge, but still report excess via telemetry 248 add_task(async function test_chips_limit_dry_run_no_purge() { 249 Services.prefs.setBoolPref("network.cookie.chips.partitionLimitDryRun", true); 250 251 let baseDomain = "example.org"; 252 let uri = NetUtil.newURI("https://" + baseDomain + "/"); 253 let channel = channelMaybePartitioned(uri, baseDomain, true); 254 let expected = setManyCookies(uri, channel, true); 255 expected.cookieNames.push("exceeded"); 256 257 // pre-condition: check that all got added as expected 258 let actual = Services.cookies.getCookieStringFromHttp(uri, channel); 259 Assert.equal(actual, expected.cookieString); 260 await checkReportedOverflow(0); 261 262 // adding 248 Bytes has excess of 208Bytes (3 cookies will be purged (FIFO)) 263 let cookie = "exceeded".concat("=").concat("x".repeat(240)); 264 let cookieNameValueLen = cookie.length - 1; 265 Services.cookies.setCookieStringFromHttp( 266 uri, 267 headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness 268 channel 269 ); 270 expected.cookieString += "; ".concat(cookie); 271 272 let expectedOverflow = 273 COOKIES_TO_SET_BYTES + cookieNameValueLen - BYTE_LIMIT_WITH_BUFFER; 274 await checkReportedOverflow(expectedOverflow); 275 276 // extract cookie names from string and compare to expected values 277 let second = Services.cookies.getCookieStringFromHttp(uri, channel); 278 let cookies = second.split("; "); 279 for (let i = 0; i < cookies.length; i++) { 280 cookies[i] = cookies[i].substr(0, cookies[i].indexOf("=")); 281 } 282 Assert.deepEqual(cookies, expected.cookieNames); 283 Assert.equal(cookies.length, expected.cookieNames.length); 284 Assert.equal(second, expected.cookieString); 285 Services.cookies.removeAll(); 286 Services.fog.testResetFOG(); 287 }); 288 289 add_task(async function test_chips_limit_chips_off() { 290 Services.prefs.setBoolPref( 291 "network.cookie.chips.partitionLimitDryRun", 292 false 293 ); 294 Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", false); 295 296 let baseDomain = "example.org"; 297 let uri = NetUtil.newURI("https://" + baseDomain + "/"); 298 let channel = channelMaybePartitioned(uri, baseDomain, true); 299 let expected = setManyCookies(uri, channel, true); 300 expected.cookieNames.push("exceeded"); 301 302 // pre-condition: check that all got added as expected 303 let actual = Services.cookies.getCookieStringFromHttp(uri, channel); 304 Assert.equal(actual, expected.cookieString); 305 await checkReportedOverflow(0); 306 307 // shouldn't trigger purge when CHIPS disabled 308 let cookie = "exceeded".concat("=").concat("x".repeat(240)); 309 Services.cookies.setCookieStringFromHttp( 310 uri, 311 headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness 312 channel 313 ); 314 expected.cookieString += "; ".concat(cookie); 315 316 await checkReportedOverflow(0); 317 318 // extract cookie names from string and compare to expected values 319 let second = Services.cookies.getCookieStringFromHttp(uri, channel); 320 let cookies = second.split("; "); 321 for (let i = 0; i < cookies.length; i++) { 322 cookies[i] = cookies[i].substr(0, cookies[i].indexOf("=")); 323 } 324 Assert.deepEqual(cookies, expected.cookieNames); 325 Assert.equal(cookies.length, expected.cookieNames.length); 326 Assert.equal(second, expected.cookieString); 327 Services.cookies.removeAll(); 328 Services.fog.testResetFOG(); 329 }); 330 331 add_task(async function test_chips_limit_chips_limit_off() { 332 Services.prefs.setBoolPref( 333 "network.cookie.chips.partitionLimitDryRun", 334 false 335 ); 336 Services.prefs.setBoolPref("network.cookie.CHIPS.enabled", true); 337 338 Services.prefs.setBoolPref( 339 "network.cookie.chips.partitionLimitEnabled", 340 false 341 ); 342 343 let baseDomain = "example.org"; 344 let uri = NetUtil.newURI("https://" + baseDomain + "/"); 345 let channel = channelMaybePartitioned(uri, baseDomain, true); 346 let expected = setManyCookies(uri, channel, true); 347 expected.cookieNames.push("exceeded"); 348 349 // pre-condition: check that all got added as expected 350 let actual = Services.cookies.getCookieStringFromHttp(uri, channel); 351 Assert.equal(actual, expected.cookieString); 352 await checkReportedOverflow(0); 353 354 // shouldn't trigger purge when CHIPS limit disabled 355 let cookie = "exceeded".concat("=").concat("x".repeat(240)); 356 Services.cookies.setCookieStringFromHttp( 357 uri, 358 headerify(cookie, COOKIES_TO_SET_COUNT, true), // use count for uniqueness 359 channel 360 ); 361 expected.cookieString += "; ".concat(cookie); 362 363 await checkReportedOverflow(0); 364 365 // extract cookie names from string and compare to expected values 366 let second = Services.cookies.getCookieStringFromHttp(uri, channel); 367 let cookies = second.split("; "); 368 for (let i = 0; i < cookies.length; i++) { 369 cookies[i] = cookies[i].substr(0, cookies[i].indexOf("=")); 370 } 371 Assert.deepEqual(cookies, expected.cookieNames); 372 Assert.equal(cookies.length, expected.cookieNames.length); 373 Assert.equal(second, expected.cookieString); 374 Services.cookies.removeAll(); 375 Services.fog.testResetFOG(); 376 }); 377 378 // non-chips-partitioned cookies do not trigger the limit 379 add_task(async function test_chips_limit_non_chips_partitioned() { 380 // channel is partitioned, not cookie header -> non-chips-partition cookies 381 let baseDomain = "example.org"; 382 let uri = NetUtil.newURI("https://" + baseDomain + "/"); 383 let channel = channelMaybePartitioned(uri, baseDomain, true); 384 let expected = setManyCookies(uri, channel, false); 385 expected.cookieNames.push("exceeded"); 386 387 // pre-condition: check that all got added as expected 388 let actual = Services.cookies.getCookieStringFromHttp(uri, channel); 389 Assert.equal(actual, expected.cookieString); 390 await checkReportedOverflow(0); 391 392 // non-chips-partitioned cookies, should also have no limit 393 let cookie = "exceeded".concat("=").concat("x".repeat(240)); 394 Services.cookies.setCookieStringFromHttp( 395 uri, 396 headerify(cookie, COOKIES_TO_SET_COUNT, false), // use count for uniqueness 397 channel 398 ); 399 400 await checkReportedOverflow(0); 401 402 // extract cookie names from string and compare to expected values 403 let second = Services.cookies.getCookieStringFromHttp(uri, channel); 404 let cookies = second.split("; "); 405 for (let i = 0; i < cookies.length; i++) { 406 cookies[i] = cookies[i].substr(0, cookies[i].indexOf("=")); 407 } 408 Assert.deepEqual(cookies, expected.cookieNames); 409 Assert.equal(cookies.length, expected.cookieNames.length); 410 Services.cookies.removeAll(); 411 Services.fog.testResetFOG(); 412 });