forked from mirrors/gecko-dev
Bug 1878716: Add a test that compares the canvases in a data iframe r=timhuang
Differential Revision: https://phabricator.services.mozilla.com/D206129
This commit is contained in:
parent
cea7622639
commit
9833cb3e54
4 changed files with 382 additions and 0 deletions
|
|
@ -16,6 +16,8 @@ support-files = [
|
|||
"file_canvascompare_blob_iframee.html",
|
||||
"file_canvascompare_blob_iframer.html",
|
||||
"file_canvascompare_blob_popupmaker.html",
|
||||
"file_canvascompare_data_iframee.html",
|
||||
"file_canvascompare_data_iframer.html",
|
||||
"file_canvascompare_data_popupmaker.html",
|
||||
"file_canvascompare_iframer.html",
|
||||
"file_canvascompare_iframee.html",
|
||||
|
|
@ -74,6 +76,8 @@ lineno = "64"
|
|||
|
||||
["browser_canvascompare_iframes_blob.js"]
|
||||
|
||||
["browser_canvascompare_iframes_data.js"]
|
||||
|
||||
["browser_canvascompare_popups.js"]
|
||||
lineno = "66"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
/**
|
||||
* This test compares canvas randomization on an iframe and a data iframe within that iframe, and
|
||||
* ensures that the canvas randomization key is inherited correctly. (e.g. that the canvases have the same
|
||||
* random value.) There's three pages at play here: the parent frame, the iframe, and the data: iframe
|
||||
* within the iframe. We only compare the inner-most two, we don't measure the outer one.
|
||||
*
|
||||
* It runs all the tests twice - once for when the iframe is cross-domain from the parent, and once when it is
|
||||
* same-domain. But in both cases the data: iframe is same-domain to its parent.
|
||||
*
|
||||
* Covers the following cases:
|
||||
* - RFP/FPP is disabled entirely
|
||||
* - RFP is enabled entirely, and only in PBM
|
||||
* - FPP is enabled entirely, and only in PBM
|
||||
* - A normal window when FPP is enabled globally and RFP is enabled in PBM, Protections Enabled and Disabled
|
||||
*
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// =============================================================================================
|
||||
|
||||
/**
|
||||
* Compares two Uint8Arrays and returns the number of bits that are different.
|
||||
*
|
||||
* @param {Uint8ClampedArray} arr1 - The first Uint8ClampedArray to compare.
|
||||
* @param {Uint8ClampedArray} arr2 - The second Uint8ClampedArray to compare.
|
||||
* @returns {number} - The number of bits that are different between the two
|
||||
* arrays.
|
||||
*/
|
||||
function countDifferencesInUint8Arrays(arr1, arr2) {
|
||||
let count = 0;
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
let diff = arr1[i] ^ arr2[i];
|
||||
while (diff > 0) {
|
||||
count += diff & 1;
|
||||
diff >>= 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// =============================================================================================
|
||||
|
||||
async function testCanvasRandomization(result, expectedResults, extraData) {
|
||||
let testDesc = extraData.testDesc;
|
||||
|
||||
let parent = result.mine;
|
||||
let child = result.theirs;
|
||||
|
||||
let differencesInRandom = countDifferencesInUint8Arrays(parent, child);
|
||||
let differencesFromUnmodifiedParent = countDifferencesInUint8Arrays(
|
||||
UNMODIFIED_CANVAS_DATA,
|
||||
parent
|
||||
);
|
||||
let differencesFromUnmodifiedChild = countDifferencesInUint8Arrays(
|
||||
UNMODIFIED_CANVAS_DATA,
|
||||
child
|
||||
);
|
||||
|
||||
Assert.greaterOrEqual(
|
||||
differencesFromUnmodifiedParent,
|
||||
expectedResults[0],
|
||||
`Checking ${testDesc} for canvas randomization, comparing parent - lower bound for random pixels.`
|
||||
);
|
||||
Assert.lessOrEqual(
|
||||
differencesFromUnmodifiedParent,
|
||||
expectedResults[1],
|
||||
`Checking ${testDesc} for canvas randomization, comparing parent - upper bound for random pixels.`
|
||||
);
|
||||
|
||||
Assert.greaterOrEqual(
|
||||
differencesFromUnmodifiedChild,
|
||||
expectedResults[0],
|
||||
`Checking ${testDesc} for canvas randomization, comparing child - lower bound for random pixels.`
|
||||
);
|
||||
Assert.lessOrEqual(
|
||||
differencesFromUnmodifiedChild,
|
||||
expectedResults[1],
|
||||
`Checking ${testDesc} for canvas randomization, comparing child - upper bound for random pixels.`
|
||||
);
|
||||
|
||||
Assert.greaterOrEqual(
|
||||
differencesInRandom,
|
||||
expectedResults[2],
|
||||
`Checking ${testDesc} and comparing randomization - lower bound for different random pixels.`
|
||||
);
|
||||
Assert.lessOrEqual(
|
||||
differencesInRandom,
|
||||
expectedResults[3],
|
||||
`Checking ${testDesc} and comparing randomization - upper bound for different random pixels.`
|
||||
);
|
||||
}
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let expectedResults = {};
|
||||
var UNMODIFIED_CANVAS_DATA = undefined;
|
||||
|
||||
add_setup(async function () {
|
||||
// Disable the fingerprinting randomization.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["privacy.fingerprintingProtection", false],
|
||||
["privacy.fingerprintingProtection.pbmode", false],
|
||||
["privacy.resistFingerprinting", false],
|
||||
],
|
||||
});
|
||||
|
||||
let extractCanvasData = function () {
|
||||
let offscreenCanvas = new OffscreenCanvas(100, 100);
|
||||
|
||||
const context = offscreenCanvas.getContext("2d");
|
||||
|
||||
// Draw a red rectangle
|
||||
context.fillStyle = "#EE2222";
|
||||
context.fillRect(0, 0, 100, 100);
|
||||
context.fillStyle = "#2222EE";
|
||||
context.fillRect(20, 20, 100, 100);
|
||||
|
||||
const imageData = context.getImageData(0, 0, 100, 100);
|
||||
return imageData.data;
|
||||
};
|
||||
|
||||
function runExtractCanvasData(tab) {
|
||||
let code = extractCanvasData.toString();
|
||||
return SpecialPowers.spawn(tab.linkedBrowser, [code], async funccode => {
|
||||
await content.eval(`var extractCanvasData = ${funccode}`);
|
||||
let result = await content.eval(`extractCanvasData()`);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
const emptyPage =
|
||||
getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
"https://example.com"
|
||||
) + "empty.html";
|
||||
|
||||
// Open a tab for extracting the canvas data.
|
||||
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, emptyPage);
|
||||
|
||||
let data = await runExtractCanvasData(tab);
|
||||
UNMODIFIED_CANVAS_DATA = data;
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
|
||||
// deep copy and avoiding corrupting the original 'const' object
|
||||
// The first value represents the minimum number of random pixels we should see
|
||||
// The second, the maximum number of random pixels
|
||||
// The third, the minimum number of differences between the canvases of the parent and child
|
||||
// The fourth, the maximum number of differences between the canvases of the parent and child
|
||||
const rfpFullyRandomized = [10000, 999999999, 20000, 999999999];
|
||||
const fppRandomizedSameDomain = [1, 260, 0, 0];
|
||||
const noRandom = [0, 0, 0, 0];
|
||||
|
||||
// Note that we are inheriting the randomization key ACROSS top-level domains that are cross-domain, because the iframe is a 3rd party domain
|
||||
let uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_canvascompare_data_iframer.html`;
|
||||
|
||||
expectedResults = structuredClone(noRandom);
|
||||
add_task(
|
||||
defaultsTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
expectedResults = structuredClone(fppRandomizedSameDomain);
|
||||
add_task(
|
||||
defaultsPBMTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
expectedResults = structuredClone(rfpFullyRandomized);
|
||||
add_task(
|
||||
simpleRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
// Test a private window with RFP enabled in PBMode
|
||||
expectedResults = structuredClone(rfpFullyRandomized);
|
||||
add_task(
|
||||
simplePBMRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
expectedResults = structuredClone(fppRandomizedSameDomain);
|
||||
add_task(
|
||||
simpleFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
// Test a Private Window with FPP Enabled in PBM
|
||||
expectedResults = structuredClone(fppRandomizedSameDomain);
|
||||
add_task(
|
||||
simplePBMFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
// Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, No Protections
|
||||
expectedResults = structuredClone(noRandom);
|
||||
add_task(
|
||||
RFPPBMFPP_NormalMode_NoProtectionsTest.bind(
|
||||
null,
|
||||
uri,
|
||||
testCanvasRandomization,
|
||||
expectedResults
|
||||
)
|
||||
);
|
||||
|
||||
// Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, Protections Enabled
|
||||
expectedResults = structuredClone(fppRandomizedSameDomain);
|
||||
add_task(
|
||||
RFPPBMFPP_NormalMode_ProtectionsTest.bind(
|
||||
null,
|
||||
uri,
|
||||
testCanvasRandomization,
|
||||
expectedResults
|
||||
)
|
||||
);
|
||||
|
||||
// And here the we are inheriting the randomization key into an iframe that is same-domain to the parent
|
||||
uri = `https://${IFRAME_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_canvascompare_data_iframer.html`;
|
||||
|
||||
expectedResults = structuredClone(noRandom);
|
||||
add_task(
|
||||
defaultsTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
expectedResults = structuredClone(fppRandomizedSameDomain);
|
||||
add_task(
|
||||
defaultsPBMTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
expectedResults = structuredClone(rfpFullyRandomized);
|
||||
add_task(
|
||||
simpleRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
// Test a private window with RFP enabled in PBMode
|
||||
expectedResults = structuredClone(rfpFullyRandomized);
|
||||
add_task(
|
||||
simplePBMRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
expectedResults = structuredClone(fppRandomizedSameDomain);
|
||||
add_task(
|
||||
simpleFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
// Test a Private Window with FPP Enabled in PBM
|
||||
expectedResults = structuredClone(fppRandomizedSameDomain);
|
||||
add_task(
|
||||
simplePBMFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
|
||||
);
|
||||
|
||||
// Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, No Protections
|
||||
expectedResults = structuredClone(noRandom);
|
||||
add_task(
|
||||
RFPPBMFPP_NormalMode_NoProtectionsTest.bind(
|
||||
null,
|
||||
uri,
|
||||
testCanvasRandomization,
|
||||
expectedResults
|
||||
)
|
||||
);
|
||||
|
||||
// Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, Protections Enabled
|
||||
expectedResults = structuredClone(fppRandomizedSameDomain);
|
||||
add_task(
|
||||
RFPPBMFPP_NormalMode_ProtectionsTest.bind(
|
||||
null,
|
||||
uri,
|
||||
testCanvasRandomization,
|
||||
expectedResults
|
||||
)
|
||||
);
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset="utf8">
|
||||
<script type="text/javascript">
|
||||
window.onload = async () => {
|
||||
parent.postMessage("ready", "*");
|
||||
}
|
||||
|
||||
window.addEventListener("message", async function listener(event) {
|
||||
if (event.data[0] == "gimme") {
|
||||
var s = `<html><script>
|
||||
function give_result() {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 100;
|
||||
canvas.height = 100;
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
|
||||
context.fillStyle = "#EE2222";
|
||||
context.fillRect(0, 0, 100, 100);
|
||||
context.fillStyle = "#2222EE";
|
||||
context.fillRect(20, 20, 100, 100);
|
||||
|
||||
// Add the canvas element to the document
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
const imageData = context.getImageData(0, 0, 100, 100);
|
||||
|
||||
return imageData.data;
|
||||
}
|
||||
window.addEventListener("load", async function listener(event) {
|
||||
parent.postMessage(["frame_ready"], "*");
|
||||
});
|
||||
window.addEventListener('message', async function listener(event) {
|
||||
if (event.data[0] == 'frame_request') {
|
||||
|
||||
parent.postMessage(['frame_response', give_result()], '*');
|
||||
}
|
||||
});`;
|
||||
// eslint-disable-next-line
|
||||
s += `</` + `script></html>`;
|
||||
|
||||
let iframe = document.createElement("iframe");
|
||||
iframe.src = "data:text/html;base64," + btoa(s);
|
||||
document.body.append(iframe);
|
||||
} else if (event.data[0] == "frame_ready") {
|
||||
let iframe = document.getElementsByTagName("iframe")[0];
|
||||
iframe.contentWindow.postMessage(["frame_request"], "*");
|
||||
} else if (event.data[0] == "frame_response") {
|
||||
function give_result() {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 100;
|
||||
canvas.height = 100;
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
|
||||
context.fillStyle = "#EE2222";
|
||||
context.fillRect(0, 0, 100, 100);
|
||||
context.fillStyle = "#2222EE";
|
||||
context.fillRect(20, 20, 100, 100);
|
||||
|
||||
// Add the canvas element to the document
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
const imageData = context.getImageData(0, 0, 100, 100);
|
||||
|
||||
return imageData.data;
|
||||
}
|
||||
let myResult = give_result();
|
||||
|
||||
parent.postMessage({mine: myResult, theirs: event.data[1]}, "*")
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<body>
|
||||
<output id="result"></output>
|
||||
</body>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
<script src="shared_test_funcs.js"></script>
|
||||
<script>
|
||||
async function runTheTest(iframe_domain, cross_origin_domain) {
|
||||
const iframes = document.querySelectorAll("iframe");
|
||||
iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_canvascompare_data_iframee.html`;
|
||||
await waitForMessage("ready", `https://${iframe_domain}`);
|
||||
|
||||
const promiseForRFPTest = new Promise(resolve => {
|
||||
window.addEventListener("message", event => {
|
||||
if(event.origin != `https://${iframe_domain}`) {
|
||||
throw new Error(`origin should be ${iframe_domain}`);
|
||||
}
|
||||
resolve(event.data);
|
||||
}, { once: true });
|
||||
});
|
||||
iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
|
||||
var result = await promiseForRFPTest;
|
||||
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe width=100></iframe>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue