tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });