tor-browser

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

browser_canvas_popups.js (6673B)


      1 /**
      2 * This tests that the canvas is correctly randomized on a popup (not the starting page)
      3 *
      4 * Covers the following cases:
      5 *  - RFP/FPP is disabled entirely
      6 *  - RFP is enabled entirely, and only in PBM
      7 *  - FPP is enabled entirely, and only in PBM
      8 *  - A normal window when FPP is enabled globally and RFP is enabled in PBM, Protections Enabled and Disabled
      9 
     10 *
     11 *  - (A) RFP is exempted on the maker and popup
     12 *  - (C) RFP is exempted on the maker but not the popup
     13 *  - (E) RFP is not exempted on the maker nor the popup
     14 *  - (G) RFP is not exempted on the maker but is on the popup
     15 *
     16 */
     17 
     18 "use strict";
     19 
     20 // =============================================================================================
     21 
     22 /**
     23 * Compares two Uint8Arrays and returns the number of bits that are different.
     24 *
     25 * @param {Uint8ClampedArray} arr1 - The first Uint8ClampedArray to compare.
     26 * @param {Uint8ClampedArray} arr2 - The second Uint8ClampedArray to compare.
     27 * @returns {number} - The number of bits that are different between the two
     28 *                     arrays.
     29 */
     30 function countDifferencesInUint8Arrays(arr1, arr2) {
     31  let count = 0;
     32  for (let i = 0; i < arr1.length; i++) {
     33    let diff = arr1[i] ^ arr2[i];
     34    while (diff > 0) {
     35      count += diff & 1;
     36      diff >>= 1;
     37    }
     38  }
     39  return count;
     40 }
     41 
     42 // =============================================================================================
     43 
     44 async function testCanvasRandomization(result, expectedResults, extraData) {
     45  let testDesc = extraData.testDesc;
     46  let differences = countDifferencesInUint8Arrays(
     47    result,
     48    UNMODIFIED_CANVAS_DATA
     49  );
     50 
     51  Assert.greaterOrEqual(
     52    differences,
     53    expectedResults[0],
     54    `Checking ${testDesc} for canvas randomization - did not see enough random pixels.`
     55  );
     56  Assert.lessOrEqual(
     57    differences,
     58    expectedResults[1],
     59    `Checking ${testDesc} for canvas randomization - saw too many random pixels.`
     60  );
     61 }
     62 
     63 requestLongerTimeout(2);
     64 
     65 let expectedResults = {};
     66 var UNMODIFIED_CANVAS_DATA = undefined;
     67 
     68 add_setup(async function () {
     69  // Make sure Old Randomization is the only one on
     70  await SpecialPowers.pushPrefEnv({
     71    set: [
     72      [
     73        "privacy.fingerprintingProtection.overrides",
     74        "-EfficientCanvasRandomization,+CanvasRandomization",
     75      ],
     76    ],
     77  });
     78 
     79  // Disable the fingerprinting randomization.
     80  await SpecialPowers.pushPrefEnv({
     81    set: [
     82      ["privacy.fingerprintingProtection", false],
     83      ["privacy.fingerprintingProtection.pbmode", false],
     84      ["privacy.resistFingerprinting", false],
     85    ],
     86  });
     87 
     88  let extractCanvasData = function () {
     89    let offscreenCanvas = new OffscreenCanvas(100, 100);
     90 
     91    const context = offscreenCanvas.getContext("2d");
     92 
     93    // Draw a red rectangle
     94    context.fillStyle = "#EE2222";
     95    context.fillRect(0, 0, 100, 100);
     96    context.fillStyle = "#2222EE";
     97    context.fillRect(20, 20, 100, 100);
     98 
     99    const imageData = context.getImageData(0, 0, 100, 100);
    100    return imageData.data;
    101  };
    102 
    103  function runExtractCanvasData(tab) {
    104    let code = extractCanvasData.toString();
    105    return SpecialPowers.spawn(tab.linkedBrowser, [code], async funccode => {
    106      await content.eval(`var extractCanvasData = ${funccode}`);
    107      let result = await content.eval(`extractCanvasData()`);
    108      return result;
    109    });
    110  }
    111 
    112  const emptyPage =
    113    getRootDirectory(gTestPath).replace(
    114      "chrome://mochitests/content",
    115      "https://example.com"
    116    ) + "empty.html";
    117 
    118  // Open a tab for extracting the canvas data.
    119  const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, emptyPage);
    120 
    121  let data = await runExtractCanvasData(tab);
    122  UNMODIFIED_CANVAS_DATA = data;
    123 
    124  BrowserTestUtils.removeTab(tab);
    125  await SpecialPowers.popPrefEnv();
    126 });
    127 
    128 // Be sure to always use `let expectedResults = structuredClone(rfpFullyRandomized)` to do a
    129 //   deep copy and avoiding corrupting the original 'const' object
    130 // The first value represents the minimum number of random pixels we should see
    131 // The second, the maximum number of random pixels
    132 const rfpFullyRandomized = [10000, 999999999];
    133 const fppRandomized = [1, 260];
    134 const noRandom = [0, 0];
    135 
    136 // Note that the starting page and the popup will be cross-domain from each other, but this test does not check that we inherit the randomizationkey,
    137 // only that the popup is randomized.
    138 const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_canvas_iframer.html?mode=popup`;
    139 
    140 expectedResults = structuredClone(noRandom);
    141 add_task(
    142  defaultsTest.bind(null, uri, testCanvasRandomization, expectedResults)
    143 );
    144 
    145 expectedResults = structuredClone(fppRandomized);
    146 add_task(
    147  defaultsPBMTest.bind(null, uri, testCanvasRandomization, expectedResults)
    148 );
    149 
    150 expectedResults = structuredClone(rfpFullyRandomized);
    151 add_task(
    152  simpleRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    153 );
    154 
    155 // Test a private window with RFP enabled in PBMode
    156 expectedResults = structuredClone(rfpFullyRandomized);
    157 add_task(
    158  simplePBMRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    159 );
    160 
    161 expectedResults = structuredClone(fppRandomized);
    162 add_task(
    163  simpleFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    164 );
    165 
    166 // Test a Private Window with FPP Enabled in PBM
    167 expectedResults = structuredClone(fppRandomized);
    168 add_task(
    169  simplePBMFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    170 );
    171 
    172 // Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, No Protections
    173 expectedResults = structuredClone(noRandom);
    174 add_task(
    175  RFPPBMFPP_NormalMode_NoProtectionsTest.bind(
    176    null,
    177    uri,
    178    testCanvasRandomization,
    179    expectedResults
    180  )
    181 );
    182 
    183 // Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, Protections Enabled
    184 expectedResults = structuredClone(fppRandomized);
    185 add_task(
    186  RFPPBMFPP_NormalMode_ProtectionsTest.bind(
    187    null,
    188    uri,
    189    testCanvasRandomization,
    190    expectedResults
    191  )
    192 );
    193 
    194 // (A) RFP is exempted on the opener and openee
    195 expectedResults = structuredClone(noRandom);
    196 add_task(testA.bind(null, uri, testCanvasRandomization, expectedResults));
    197 
    198 // (C) RFP is exempted on the opener but not the openee
    199 expectedResults = structuredClone(rfpFullyRandomized);
    200 add_task(testC.bind(null, uri, testCanvasRandomization, expectedResults));
    201 
    202 // (E) RFP is not exempted on the opener nor the openee
    203 expectedResults = structuredClone(rfpFullyRandomized);
    204 add_task(testE.bind(null, uri, testCanvasRandomization, expectedResults));
    205 
    206 // (G) RFP is not exempted on the opener but is on the openee
    207 expectedResults = structuredClone(rfpFullyRandomized);
    208 add_task(testG.bind(null, uri, testCanvasRandomization, expectedResults));