summaryrefslogtreecommitdiffstats
path: root/browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js')
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js392
1 files changed, 392 insertions, 0 deletions
diff --git a/browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js b/browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js
new file mode 100644
index 0000000000..6d6e836cd4
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js
@@ -0,0 +1,392 @@
+/**
+ * Bug 1737829 and Bug 1770498 - A test case for making sure the navigator object has been
+ * spoofed/disabled correctly respecting cross-origin resources, iframes
+ * and exemption behavior.
+ *
+ * This test only tests values in the iframe, it does not test them on the framer
+ * We use the cross-origin domain as the base URI of a resource we fetch (on both the framer and framee)
+ * so we can check that the HTTP header is as expected.
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ WindowsVersionInfo:
+ "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
+});
+
+let osVersion = Services.sysinfo.get("version");
+if (AppConstants.platform == "macosx") {
+ // Convert Darwin version to macOS version: 19.x.x -> 10.15 etc.
+ // https://en.wikipedia.org/wiki/Darwin_%28operating_system%29
+ let DarwinVersionParts = osVersion.split(".");
+ let DarwinMajorVersion = +DarwinVersionParts[0];
+ let macOsMinorVersion = DarwinMajorVersion - 4;
+ if (macOsMinorVersion > 15) {
+ macOsMinorVersion = 15;
+ }
+ osVersion = `10.${macOsMinorVersion}`;
+}
+
+const DEFAULT_APPVERSION = {
+ linux: "5.0 (X11)",
+ win: "5.0 (Windows)",
+ macosx: "5.0 (Macintosh)",
+ android: `5.0 (Android ${osVersion})`,
+ other: "5.0 (X11)",
+};
+
+const SPOOFED_APPVERSION = {
+ linux: "5.0 (X11)",
+ win: "5.0 (Windows)",
+ macosx: "5.0 (Macintosh)",
+ android: "5.0 (Android 10)",
+ other: "5.0 (X11)",
+};
+
+let cpuArch = Services.sysinfo.get("arch");
+if (cpuArch == "x86-64") {
+ // Convert CPU arch "x86-64" to "x86_64" used in Linux and Android UAs.
+ cpuArch = "x86_64";
+}
+
+const DEFAULT_PLATFORM = {
+ linux: `Linux ${cpuArch}`,
+ win: "Win32",
+ macosx: "MacIntel",
+ android: `Linux ${cpuArch}`,
+ other: `Linux ${cpuArch}`,
+};
+
+const SPOOFED_PLATFORM = {
+ linux: "Linux x86_64",
+ win: "Win32",
+ macosx: "MacIntel",
+ android: "Linux aarch64",
+ other: "Linux x86_64",
+};
+
+// If comparison with the WindowsOscpu value fails in the future, it's time to
+// evaluate if exposing a new Windows version to the Web is appropriate. See
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1693295
+let WindowsOscpu = null;
+if (AppConstants.platform == "win") {
+ let isWin11 = WindowsVersionInfo.get().buildNumber >= 22000;
+ WindowsOscpu =
+ cpuArch == "x86_64" || (cpuArch == "aarch64" && isWin11)
+ ? `Windows NT ${osVersion}; Win64; x64`
+ : `Windows NT ${osVersion}`;
+}
+
+const DEFAULT_OSCPU = {
+ linux: `Linux ${cpuArch}`,
+ win: WindowsOscpu,
+ macosx: `Intel Mac OS X ${osVersion}`,
+ android: `Linux ${cpuArch}`,
+ other: `Linux ${cpuArch}`,
+};
+
+const SPOOFED_OSCPU = {
+ linux: "Linux x86_64",
+ win: "Windows NT 10.0; Win64; x64",
+ macosx: "Intel Mac OS X 10.15",
+ android: "Linux aarch64",
+ other: "Linux x86_64",
+};
+
+const DEFAULT_UA_OS = {
+ linux: `X11; Linux ${cpuArch}`,
+ win: WindowsOscpu,
+ macosx: `Macintosh; Intel Mac OS X ${osVersion}`,
+ android: `Android ${osVersion}; Mobile`,
+ other: `X11; Linux ${cpuArch}`,
+};
+
+const SPOOFED_UA_NAVIGATOR_OS = {
+ linux: "X11; Linux x86_64",
+ win: "Windows NT 10.0; Win64; x64",
+ macosx: "Macintosh; Intel Mac OS X 10.15",
+ android: "Android 10; Mobile",
+ other: "X11; Linux x86_64",
+};
+const SPOOFED_UA_HTTPHEADER_OS = {
+ linux: "Windows NT 10.0",
+ win: "Windows NT 10.0",
+ macosx: "Windows NT 10.0",
+ android: "Android 10; Mobile",
+ other: "Windows NT 10.0",
+};
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const CONST_APPCODENAME = "Mozilla";
+const CONST_APPNAME = "Netscape";
+const CONST_PRODUCT = "Gecko";
+const CONST_PRODUCTSUB = "20100101";
+const CONST_VENDOR = "";
+const CONST_VENDORSUB = "";
+
+const appVersion = parseInt(Services.appinfo.version);
+const rvVersion =
+ parseInt(
+ Services.prefs.getIntPref("network.http.useragent.forceRVOnly", 0),
+ 0
+ ) || appVersion;
+const spoofedVersion = AppConstants.platform == "android" ? "115" : appVersion;
+
+const LEGACY_UA_GECKO_TRAIL = "20100101";
+
+const DEFAULT_UA_GECKO_TRAIL = {
+ linux: LEGACY_UA_GECKO_TRAIL,
+ win: LEGACY_UA_GECKO_TRAIL,
+ macosx: LEGACY_UA_GECKO_TRAIL,
+ android: `${appVersion}.0`,
+ other: LEGACY_UA_GECKO_TRAIL,
+};
+
+const SPOOFED_UA_GECKO_TRAIL = {
+ linux: LEGACY_UA_GECKO_TRAIL,
+ win: LEGACY_UA_GECKO_TRAIL,
+ macosx: LEGACY_UA_GECKO_TRAIL,
+ android: `${spoofedVersion}.0`,
+ other: LEGACY_UA_GECKO_TRAIL,
+};
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testNavigator(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.appVersion,
+ expectedResults.appVersion,
+ `Checking ${testDesc} navigator.appVersion.`
+ );
+ is(
+ result.platform,
+ expectedResults.platform,
+ `Checking ${testDesc} navigator.platform.`
+ );
+ is(
+ result.userAgent,
+ expectedResults.userAgentNavigator,
+ `Checking ${testDesc} navigator.userAgent.`
+ );
+ is(
+ result.userAgentHTTPHeader,
+ expectedResults.userAgentHTTPHeader,
+ `Checking ${testDesc} userAgentHTTPHeader.`
+ );
+ is(
+ result.framer_crossOrigin_userAgentHTTPHeader,
+ expectedResults.framer_crossOrigin_userAgentHTTPHeader,
+ `Checking ${testDesc} framer contacting cross-origin userAgentHTTPHeader.`
+ );
+ is(
+ result.framee_crossOrigin_userAgentHTTPHeader,
+ expectedResults.framee_crossOrigin_userAgentHTTPHeader,
+ `Checking ${testDesc} framee contacting cross-origin userAgentHTTPHeader.`
+ );
+ is(
+ result.mimeTypesLength,
+ expectedResults.mimeTypesLength,
+ `Navigator.mimeTypes has a length of ${expectedResults.mimeTypesLength}.`
+ );
+ is(
+ result.pluginsLength,
+ expectedResults.pluginsLength,
+ `Navigator.plugins has a length of ${expectedResults.pluginsLength}.`
+ );
+ is(
+ result.oscpu,
+ expectedResults.oscpu,
+ `Checking ${testDesc} navigator.oscpu.`
+ );
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+
+ is(
+ result.appCodeName,
+ CONST_APPCODENAME,
+ "Navigator.appCodeName reports correct constant value."
+ );
+ is(
+ result.appName,
+ CONST_APPNAME,
+ "Navigator.appName reports correct constant value."
+ );
+ is(
+ result.product,
+ CONST_PRODUCT,
+ "Navigator.product reports correct constant value."
+ );
+ is(
+ result.productSub,
+ CONST_PRODUCTSUB,
+ "Navigator.productSub reports correct constant value."
+ );
+ is(
+ result.vendor,
+ CONST_VENDOR,
+ "Navigator.vendor reports correct constant value."
+ );
+ is(
+ result.vendorSub,
+ CONST_VENDORSUB,
+ "Navigator.vendorSub reports correct constant value."
+ );
+
+ is(
+ result.worker_appVersion,
+ expectedResults.appVersion,
+ `Checking ${testDesc} worker navigator.appVersion.`
+ );
+ is(
+ result.worker_platform,
+ expectedResults.platform,
+ `Checking ${testDesc} worker navigator.platform.`
+ );
+ is(
+ result.worker_userAgent,
+ expectedResults.userAgentNavigator,
+ `Checking ${testDesc} worker navigator.userAgent.`
+ );
+ is(
+ result.worker_hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} worker navigator.hardwareConcurrency.`
+ );
+
+ is(
+ result.worker_appCodeName,
+ CONST_APPCODENAME,
+ "worker Navigator.appCodeName reports correct constant value."
+ );
+ is(
+ result.worker_appName,
+ CONST_APPNAME,
+ "worker Navigator.appName reports correct constant value."
+ );
+ is(
+ result.worker_product,
+ CONST_PRODUCT,
+ "worker Navigator.product reports correct constant value."
+ );
+}
+
+const defaultUserAgent = `Mozilla/5.0 (${
+ DEFAULT_UA_OS[AppConstants.platform]
+}; rv:${rvVersion}.0) Gecko/${
+ DEFAULT_UA_GECKO_TRAIL[AppConstants.platform]
+} Firefox/${appVersion}.0`;
+
+const spoofedUserAgentNavigator = `Mozilla/5.0 (${
+ SPOOFED_UA_NAVIGATOR_OS[AppConstants.platform]
+}; rv:${rvVersion}.0) Gecko/${
+ SPOOFED_UA_GECKO_TRAIL[AppConstants.platform]
+} Firefox/${appVersion}.0`;
+
+const spoofedUserAgentHeader = `Mozilla/5.0 (${
+ SPOOFED_UA_HTTPHEADER_OS[AppConstants.platform]
+}; rv:${rvVersion}.0) Gecko/${
+ SPOOFED_UA_GECKO_TRAIL[AppConstants.platform]
+} Firefox/${appVersion}.0`;
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ appVersion: DEFAULT_APPVERSION[AppConstants.platform],
+ hardwareConcurrency: navigator.hardwareConcurrency,
+ mimeTypesLength: 2,
+ oscpu: DEFAULT_OSCPU[AppConstants.platform],
+ platform: DEFAULT_PLATFORM[AppConstants.platform],
+ pluginsLength: 5,
+ userAgentNavigator: defaultUserAgent,
+ userAgentHTTPHeader: defaultUserAgent,
+ framer_crossOrigin_userAgentHTTPHeader: defaultUserAgent,
+ framee_crossOrigin_userAgentHTTPHeader: defaultUserAgent,
+};
+const allSpoofed = {
+ appVersion: SPOOFED_APPVERSION[AppConstants.platform],
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+ mimeTypesLength: 2,
+ oscpu: SPOOFED_OSCPU[AppConstants.platform],
+ platform: SPOOFED_PLATFORM[AppConstants.platform],
+ pluginsLength: 5,
+ userAgentNavigator: spoofedUserAgentNavigator,
+ userAgentHTTPHeader: spoofedUserAgentHeader,
+ framer_crossOrigin_userAgentHTTPHeader: spoofedUserAgentHeader,
+ framee_crossOrigin_userAgentHTTPHeader: spoofedUserAgentHeader,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_navigator_iframer.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testNavigator, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testNavigator, expectedResults));
+
+// In the below tests, we use the cross-origin domain as the base URI of a resource we fetch (on both the framer and framee)
+// so we can check that the HTTP header is as expected.
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testNavigator, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testB.bind(null, uri, testNavigator, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+expectedResults.framer_crossOrigin_userAgentHTTPHeader = defaultUserAgent;
+expectedResults.framee_crossOrigin_userAgentHTTPHeader = spoofedUserAgentHeader;
+add_task(testC.bind(null, uri, testNavigator, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+expectedResults.framer_crossOrigin_userAgentHTTPHeader = defaultUserAgent;
+expectedResults.framee_crossOrigin_userAgentHTTPHeader = spoofedUserAgentHeader;
+add_task(testD.bind(null, uri, testNavigator, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testNavigator, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testNavigator, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testNavigator, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testNavigator, expectedResults));