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