tor-browser

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

browser_canvascompare_iframes_aboutblank.js (9140B)


      1 /**
      2 * This test compares canvas randomization on an iframe and an iframe of about:blank within that iframe, and
      3 * ensures that the canvas randomization key is inherited correctly.  (e.g. that the canvases have the same
      4 * random value.)  There's three pages at play here: the parent frame, the iframe, and the about:blank iframe
      5 * within the iframe.  We only compare the inner-most two, we don't measure the outer one.
      6 *
      7 * It runs all the tests twice - once for when the iframe is cross-domain from the parent, and once when it is
      8 * same-domain.  But in both cases the about:blank iframe is same-domain to its parent.
      9 *
     10 * Covers the following cases:
     11 *  - RFP/FPP is disabled entirely
     12 *  - RFP is enabled entirely, and only in PBM
     13 *  - FPP is enabled entirely, and only in PBM
     14 *  - A normal window when FPP is enabled globally and RFP is enabled in PBM, Protections Enabled and Disabled
     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 
     47  let parent = result.mine;
     48  let child = result.theirs;
     49 
     50  let differencesInRandom = countDifferencesInUint8Arrays(parent, child);
     51  let differencesFromUnmodifiedParent = countDifferencesInUint8Arrays(
     52    UNMODIFIED_CANVAS_DATA,
     53    parent
     54  );
     55  let differencesFromUnmodifiedChild = countDifferencesInUint8Arrays(
     56    UNMODIFIED_CANVAS_DATA,
     57    child
     58  );
     59 
     60  Assert.greaterOrEqual(
     61    differencesFromUnmodifiedParent,
     62    expectedResults[0],
     63    `Checking ${testDesc} for canvas randomization, comparing parent - lower bound for random pixels.`
     64  );
     65  Assert.lessOrEqual(
     66    differencesFromUnmodifiedParent,
     67    expectedResults[1],
     68    `Checking ${testDesc} for canvas randomization, comparing parent - upper bound for random pixels.`
     69  );
     70 
     71  Assert.greaterOrEqual(
     72    differencesFromUnmodifiedChild,
     73    expectedResults[0],
     74    `Checking ${testDesc} for canvas randomization, comparing child - lower bound for random pixels.`
     75  );
     76  Assert.lessOrEqual(
     77    differencesFromUnmodifiedChild,
     78    expectedResults[1],
     79    `Checking ${testDesc} for canvas randomization, comparing child - upper bound for random pixels.`
     80  );
     81 
     82  Assert.greaterOrEqual(
     83    differencesInRandom,
     84    expectedResults[2],
     85    `Checking ${testDesc} and comparing randomization - lower bound for different random pixels.`
     86  );
     87  Assert.lessOrEqual(
     88    differencesInRandom,
     89    expectedResults[3],
     90    `Checking ${testDesc} and comparing randomization - upper bound for different random pixels.`
     91  );
     92 }
     93 
     94 requestLongerTimeout(2);
     95 
     96 let expectedResults = {};
     97 var UNMODIFIED_CANVAS_DATA = undefined;
     98 
     99 add_setup(async function () {
    100  // Make sure Old Randomization is the only one on
    101  await SpecialPowers.pushPrefEnv({
    102    set: [
    103      [
    104        "privacy.fingerprintingProtection.overrides",
    105        "-EfficientCanvasRandomization,+CanvasRandomization",
    106      ],
    107    ],
    108  });
    109 
    110  // Disable the fingerprinting randomization.
    111  await SpecialPowers.pushPrefEnv({
    112    set: [
    113      ["privacy.fingerprintingProtection", false],
    114      ["privacy.fingerprintingProtection.pbmode", false],
    115      ["privacy.resistFingerprinting", false],
    116    ],
    117  });
    118 
    119  let extractCanvasData = function () {
    120    let offscreenCanvas = new OffscreenCanvas(100, 100);
    121 
    122    const context = offscreenCanvas.getContext("2d");
    123 
    124    // Draw a red rectangle
    125    context.fillStyle = "#EE2222";
    126    context.fillRect(0, 0, 100, 100);
    127    context.fillStyle = "#2222EE";
    128    context.fillRect(20, 20, 100, 100);
    129 
    130    const imageData = context.getImageData(0, 0, 100, 100);
    131    return imageData.data;
    132  };
    133 
    134  function runExtractCanvasData(tab) {
    135    let code = extractCanvasData.toString();
    136    return SpecialPowers.spawn(tab.linkedBrowser, [code], async funccode => {
    137      await content.eval(`var extractCanvasData = ${funccode}`);
    138      let result = await content.eval(`extractCanvasData()`);
    139      return result;
    140    });
    141  }
    142 
    143  const emptyPage =
    144    getRootDirectory(gTestPath).replace(
    145      "chrome://mochitests/content",
    146      "https://example.com"
    147    ) + "empty.html";
    148 
    149  // Open a tab for extracting the canvas data.
    150  const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, emptyPage);
    151 
    152  let data = await runExtractCanvasData(tab);
    153  UNMODIFIED_CANVAS_DATA = data;
    154 
    155  BrowserTestUtils.removeTab(tab);
    156  await SpecialPowers.popPrefEnv();
    157 });
    158 
    159 // Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
    160 //   deep copy and avoiding corrupting the original 'const' object
    161 // The first value represents the minimum number of random pixels we should see
    162 // The second, the maximum number of random pixels
    163 // The third, the minimum number of differences between the canvases of the parent and child
    164 // The fourth, the maximum number of differences between the canvases of the parent and child
    165 const rfpFullyRandomized = [10000, 999999999, 20000, 999999999];
    166 const fppRandomizedSameDomain = [1, 260, 0, 0];
    167 const noRandom = [0, 0, 0, 0];
    168 
    169 // Note that we are inheriting the randomization key ACROSS top-level domains that are cross-domain, because the iframe is a 3rd party domain
    170 let uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_canvascompare_aboutblank_iframer.html`;
    171 
    172 expectedResults = structuredClone(noRandom);
    173 add_task(
    174  defaultsTest.bind(null, uri, testCanvasRandomization, expectedResults)
    175 );
    176 
    177 expectedResults = structuredClone(fppRandomizedSameDomain);
    178 add_task(
    179  defaultsPBMTest.bind(null, uri, testCanvasRandomization, expectedResults)
    180 );
    181 
    182 expectedResults = structuredClone(rfpFullyRandomized);
    183 add_task(
    184  simpleRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    185 );
    186 
    187 // Test a private window with RFP enabled in PBMode
    188 expectedResults = structuredClone(rfpFullyRandomized);
    189 add_task(
    190  simplePBMRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    191 );
    192 
    193 expectedResults = structuredClone(fppRandomizedSameDomain);
    194 add_task(
    195  simpleFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    196 );
    197 
    198 // Test a Private Window with FPP Enabled in PBM
    199 expectedResults = structuredClone(fppRandomizedSameDomain);
    200 add_task(
    201  simplePBMFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    202 );
    203 
    204 // Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, No Protections
    205 expectedResults = structuredClone(noRandom);
    206 add_task(
    207  RFPPBMFPP_NormalMode_NoProtectionsTest.bind(
    208    null,
    209    uri,
    210    testCanvasRandomization,
    211    expectedResults
    212  )
    213 );
    214 
    215 // Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, Protections Enabled
    216 expectedResults = structuredClone(fppRandomizedSameDomain);
    217 add_task(
    218  RFPPBMFPP_NormalMode_ProtectionsTest.bind(
    219    null,
    220    uri,
    221    testCanvasRandomization,
    222    expectedResults
    223  )
    224 );
    225 
    226 // And here the we are inheriting the randomization key into an iframe that is same-domain to the parent
    227 uri = `https://${IFRAME_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_canvascompare_aboutblank_iframer.html`;
    228 
    229 expectedResults = structuredClone(noRandom);
    230 add_task(
    231  defaultsTest.bind(null, uri, testCanvasRandomization, expectedResults)
    232 );
    233 
    234 expectedResults = structuredClone(rfpFullyRandomized);
    235 add_task(
    236  simpleRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    237 );
    238 
    239 // Test a private window with RFP enabled in PBMode
    240 expectedResults = structuredClone(rfpFullyRandomized);
    241 add_task(
    242  simplePBMRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    243 );
    244 
    245 expectedResults = structuredClone(fppRandomizedSameDomain);
    246 add_task(
    247  simpleFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    248 );
    249 
    250 // Test a Private Window with FPP Enabled in PBM
    251 expectedResults = structuredClone(fppRandomizedSameDomain);
    252 add_task(
    253  simplePBMFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
    254 );
    255 
    256 // Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, No Protections
    257 expectedResults = structuredClone(noRandom);
    258 add_task(
    259  RFPPBMFPP_NormalMode_NoProtectionsTest.bind(
    260    null,
    261    uri,
    262    testCanvasRandomization,
    263    expectedResults
    264  )
    265 );
    266 
    267 // Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, Protections Enabled
    268 expectedResults = structuredClone(fppRandomizedSameDomain);
    269 add_task(
    270  RFPPBMFPP_NormalMode_ProtectionsTest.bind(
    271    null,
    272    uri,
    273    testCanvasRandomization,
    274    expectedResults
    275  )
    276 );