From f00c3b674e1873b5d6c718661adfb98a46277533 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Tue, 4 Sep 2018 23:29:58 +0200 Subject: [PATCH 01/12] First DOMRect draft For #236 --- .vscode/settings.json | 2 + _locales/en/messages.json | 29 +++ lib/intercept.js | 2 +- lib/modifiedAPI.js | 3 + lib/modifiedDOMRectAPI.js | 409 +++++++++++++++++++++++++++++++++++++ lib/settingDefinitions.js | 15 ++ manifest.json | 1 + options/settingsDisplay.js | 7 + 8 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 lib/modifiedDOMRectAPI.js diff --git a/.vscode/settings.json b/.vscode/settings.json index ed9cc7b..1819391 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,8 @@ "Nachfrageverweigerungsmodus", "Oakenpants", "PDFs", + "Rect", + "Rects", "Spoofer", "Thorin", "Vortäuschaktion", diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 383f776..fbbb1b0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -105,6 +105,10 @@ "message": "Window API", "description": "" }, + "section_DOMRect-api":{ + "message": "DOMRect API", + "description": "" + }, "displayAdvancedSettings_title": { "message": "Expert mode", @@ -213,6 +217,18 @@ "message": "Do you want to allow window readout?", "description": "" }, + "askForDOMRectPermission": { + "message": "Do you want to allow the DOMRect API?", + "description": "" + }, + "askForDOMRectInputPermission": { + "message": "Do you want to allow DOMRect-API input?", + "description": "" + }, + "askForDOMRectReadoutPermission": { + "message": "Do you want to allow DOMRect readout?", + "description": "" + }, "askOnlyOnce_title": { "message": "Ask only once", "description": "" @@ -544,6 +560,10 @@ "message": "Faked window readout on {url}", "description": "" }, + "fakedDOMRectReadout": { + "message": "Faked DOMRect readout on {url}", + "description": "" + }, "fakedInput": { "message": "Faked at input on {url}", "description": "" @@ -869,6 +889,15 @@ "description": "" }, + "protectDOMRect_title": { + "message": "Protect DOMRect API", + "description": "" + }, + "protectDOMRect_description": { + "message": "This protects against the \"getClientRects()\" fingerprinting and several similar methods.", + "description": "" + }, + "theme_title": { "message": "Theme", "description": "" diff --git a/lib/intercept.js b/lib/intercept.js index 7eea9f5..e35437e 100644 --- a/lib/intercept.js +++ b/lib/intercept.js @@ -325,7 +325,7 @@ const getter = changedGetter.getterGenerator(checker); descriptor.get = exportFunction(getter, window); - if (changedGetter.setterGenerator){ + if (descriptor.hasOwnProperty("set") && changedGetter.setterGenerator){ const setter = changedGetter.setterGenerator(window, descriptor.set); descriptor.set = exportFunction(setter, window); } diff --git a/lib/modifiedAPI.js b/lib/modifiedAPI.js index 2d3accb..02d721c 100644 --- a/lib/modifiedAPI.js +++ b/lib/modifiedAPI.js @@ -19,6 +19,7 @@ const getWrapped = require("sdk/getWrapped"); const {hasType, checkerWrapper} = require("./modifiedAPIFunctions"); const modifiedAudioAPI = require("./modifiedAudioAPI"); + const modifiedDOMRectAPI = require("./modifiedDOMRectAPI"); var randomSupply = null; @@ -155,6 +156,7 @@ scope.setRandomSupply = function(supply){ randomSupply = supply; modifiedAudioAPI.setRandomSupply(supply); + modifiedDOMRectAPI.setRandomSupply(supply); }; var canvasContextType = new WeakMap(); // changed functions and their fakes @@ -488,4 +490,5 @@ appendModified(modifiedAudioAPI); appendModified(require("./modifiedHistoryAPI")); appendModified(require("./modifiedWindowAPI")); + appendModified(modifiedDOMRectAPI); }()); \ No newline at end of file diff --git a/lib/modifiedDOMRectAPI.js b/lib/modifiedDOMRectAPI.js new file mode 100644 index 0000000..ca8a3f6 --- /dev/null +++ b/lib/modifiedDOMRectAPI.js @@ -0,0 +1,409 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +(function(){ + "use strict"; + + var scope; + if ((typeof exports) !== "undefined"){ + scope = exports; + } + else { + window.scope.modifiedDOMRectAPI = {}; + scope = window.scope.modifiedDOMRectAPI; + } + + const {hasType, checkerWrapper} = require("./modifiedAPIFunctions"); + const {md5String: hash} = require("./hash"); + const getWrapped = require("sdk/getWrapped"); + + + var randomSupply = null; + scope.setRandomSupply = function(supply){ + randomSupply = supply; + }; + + const cache = new Map(); + function getHash(domRect){ + return hash(new Float64Array([domRect.x, domRect.y, domRect.width, domRect.height])); + } + + const registeredRects = new WeakMap(); + function registerDOMRect(domRect, notify){ + registeredRects.set(getWrapped(domRect), { + notify: function(){ + let done = false; + return function(message){ + if (!done){ + done = true; + notify(message); + } + }; + }() + }); + } + function getDOMRectRegistration(domRect){ + return registeredRects.get(getWrapped(domRect)); + } + function getFakeDomRect(window, domRect, notify){ + + var rng = randomSupply.getRng(4, window); + function getFakeValue(value, i){ + if (value % 1 === 0){ + return value; + } + else { + return value + 0.01 * (rng(i) / 0xffffffff - 0.5); + } + } + const hash = getHash(domRect); + let cached = cache.get(hash); + if (!cached){ + notify("fakedDOMRectReadout"); + cached = new domRect.constructor( + getFakeValue(domRect.x, 0), + getFakeValue(domRect.y, 1), + getFakeValue(domRect.width, 2), + getFakeValue(domRect.height, 3) + ); + cache.set(getHash(cached), cached); + } + return cached; + } + + scope.changedFunctions = { + getClientRects: { + type: "domRect", + getStatus: getStatus, + object: ["Range", "Element"], + fakeGenerator: function(checker){ + return function getClientRects(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + var {prefs, notify, window, original} = check; + var ret = original.apply(this, window.Array.from(args)); + for (let i = 0; i < ret.length; i += 1){ + registerDOMRect(ret[i], notify); + } + return ret; + }); + }; + } + }, + getBoundingClientRect: { + type: "domRect", + getStatus: getStatus, + object: ["Range", "Element"], + fakeGenerator: function(checker){ + return function getBoundingClientRect(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + var {prefs, notify, window, original} = check; + var ret = original.apply(this, window.Array.from(args)); + registerDOMRect(ret, notify); + return ret; + }); + }; + + } + }, + getBounds: { + type: "domRect", + getStatus: getStatus, + object: ["DOMQuad"], + fakeGenerator: function(checker){ + return function getBounds(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + var {prefs, notify, window, original} = check; + var ret = original.apply(this, window.Array.from(args)); + registerDOMRect(ret, notify); + return ret; + }); + }; + } + }, + getBBox: { + type: "domRect", + getStatus: getStatus, + object: ["SVGGraphicsElement"], + fakeGenerator: function(checker){ + return function getBBox(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + var {prefs, notify, window, original} = check; + var ret = original.apply(this, window.Array.from(args)); + registerDOMRect(ret, notify); + return ret; + }); + }; + } + }, + getExtentOfChar: { + type: "domRect", + getStatus: getStatus, + object: ["SVGTextContentElement"], + fakeGenerator: function(checker){ + return function getBBox(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + var {prefs, notify, window, original} = check; + var ret = original.apply(this, window.Array.from(args)); + registerDOMRect(ret, notify); + return ret; + }); + }; + } + }, + }; + + function createCheckerCallback(property){ + return function(args, check){ + const {prefs, notify, window, original} = check; + const originalValue = original.apply(this, window.Array.from(args)); + if (prefs("protectDOMRect", window.location)){ + const registration = getDOMRectRegistration(this); + if (registration){ + return getFakeDomRect(window, this, registration.notify)[property]; + } + } + + return originalValue; + }; + } + function setProperty(domRect, window, original, newValue, property){ // eslint-disable-line max-params + const registration = getDOMRectRegistration(domRect); + if (registration){ + const fakeDomRect = getFakeDomRect(window, domRect, registration.notify); + registeredRects.delete(getWrapped(domRect)); + ["x", "y", "width", "height"].forEach(function(prop){ + if (prop === property){ + domRect[prop] = newValue; + } + else { + domRect[prop] = fakeDomRect[prop]; + } + }); + } + else { + original.apply(domRect, window.Array.from(arguments)); + } + } + + scope.changedGetters = [ + { + objectGetters: [ + function(window){return window.DOMRect.prototype;}, + function(window){return window.DOMRectReadOnly.prototype;} + ], + name: "x", + getterGenerator: function(checker){ + const temp = { + get x(){ + return checkerWrapper(checker, this, arguments, createCheckerCallback("x")); + } + }; + return Object.getOwnPropertyDescriptor(temp, "x").get; + }, + setterGenerator: function(window, original){ + const temp = { + set x(x){ + setProperty(this, window, original, x, "x"); + } + }; + return Object.getOwnPropertyDescriptor(temp, "x").set; + } + }, + { + objectGetters: [ + function(window){return window.DOMRect.prototype;}, + function(window){return window.DOMRectReadOnly.prototype;} + ], + name: "y", + getterGenerator: function(checker){ + const temp = { + get y(){ + return checkerWrapper(checker, this, arguments, createCheckerCallback("y")); + } + }; + return Object.getOwnPropertyDescriptor(temp, "y").get; + }, + setterGenerator: function(window, original){ + const temp = { + set y(y){ + setProperty(this, window, original, y, "y"); + } + }; + return Object.getOwnPropertyDescriptor(temp, "y").set; + } + }, + { + objectGetters: [ + function(window){return window.DOMRect.prototype;}, + function(window){return window.DOMRectReadOnly.prototype;} + ], + name: "width", + getterGenerator: function(checker){ + const temp = { + get width(){ + return checkerWrapper(checker, this, arguments, createCheckerCallback("width")); + } + }; + return Object.getOwnPropertyDescriptor(temp, "width").get; + }, + setterGenerator: function(window, original){ + const temp = { + set width(width){ + setProperty(this, window, original, width, "width"); + } + }; + return Object.getOwnPropertyDescriptor(temp, "width").set; + } + }, + { + objectGetters: [ + function(window){return window.DOMRect.prototype;}, + function(window){return window.DOMRectReadOnly.prototype;} + ], + name: "height", + getterGenerator: function(checker){ + const temp = { + get height(){ + return checkerWrapper(checker, this, arguments, createCheckerCallback("height")); + } + }; + return Object.getOwnPropertyDescriptor(temp, "height").get; + }, + setterGenerator: function(window, original){ + const temp = { + set height(height){ + setProperty(this, window, original, height, "height"); + } + }; + return Object.getOwnPropertyDescriptor(temp, "height").set; + } + }, + { + objectGetters: [ + function(window){return window.DOMRectReadOnly.prototype;} + ], + name: "left", + getterGenerator: function(checker){ + const callback = createCheckerCallback("left"); + const temp = { + get left(){ + return checkerWrapper(checker, this, arguments, callback); + } + }; + return Object.getOwnPropertyDescriptor(temp, "left").get; + } + }, + { + objectGetters: [ + function(window){return window.DOMRectReadOnly.prototype;} + ], + name: "right", + getterGenerator: function(checker){ + const temp = { + get right(){ + return checkerWrapper(checker, this, arguments, createCheckerCallback("right")); + } + }; + return Object.getOwnPropertyDescriptor(temp, "right").get; + } + }, + { + objectGetters: [ + function(window){return window.DOMRectReadOnly.prototype;} + ], + name: "top", + getterGenerator: function(checker){ + const temp = { + get top(){ + return checkerWrapper(checker, this, arguments, createCheckerCallback("top")); + } + }; + return Object.getOwnPropertyDescriptor(temp, "top").get; + } + }, + { + objectGetters: [ + function(window){return window.DOMRectReadOnly.prototype;} + ], + name: "bottom", + getterGenerator: function(checker){ + const temp = { + get bottom(){ + return checkerWrapper(checker, this, arguments, createCheckerCallback("bottom")); + } + }; + return Object.getOwnPropertyDescriptor(temp, "bottom").get; + } + }, + { + objectGetters: [ + function(window){return window.IntersectionObserverEntry.prototype;} + ], + name: "intersectionRect", + getterGenerator: function(checker){ + const temp = { + get intersectionRect(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + const {prefs, notify, window, original} = check; + const originalValue = original.apply(this, window.Array.from(args)); + registerDOMRect(originalValue, notify); + return originalValue; + }); + } + }; + return Object.getOwnPropertyDescriptor(temp, "intersectionRect").get; + } + }, + { + objectGetters: [ + function(window){return window.IntersectionObserverEntry.prototype;} + ], + name: "boundingClientRect", + getterGenerator: function(checker){ + const temp = { + get boundingClientRect(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + const {prefs, notify, window, original} = check; + const originalValue = original.apply(this, window.Array.from(args)); + registerDOMRect(originalValue, notify); + return originalValue; + }); + } + }; + return Object.getOwnPropertyDescriptor(temp, "boundingClientRect").get; + } + }, + { + objectGetters: [ + function(window){return window.IntersectionObserverEntry.prototype;} + ], + name: "rootBounds", + getterGenerator: function(checker){ + const temp = { + get rootBounds(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + const {prefs, notify, window, original} = check; + const originalValue = original.apply(this, window.Array.from(args)); + registerDOMRect(originalValue, notify); + return originalValue; + }); + } + }; + return Object.getOwnPropertyDescriptor(temp, "rootBounds").get; + } + } + ]; + + + function getStatus(obj, status){ + status = Object.create(status); + status.active = hasType(status, "readout"); + return status; + } + + scope.changedGetters.forEach(function(changedGetter){ + changedGetter.type = "readout"; + changedGetter.getStatus = getStatus; + changedGetter.api = "domRect"; + }); +}()); \ No newline at end of file diff --git a/lib/settingDefinitions.js b/lib/settingDefinitions.js index 7533d9b..ef530f3 100644 --- a/lib/settingDefinitions.js +++ b/lib/settingDefinitions.js @@ -95,6 +95,15 @@ {name: "Window-API", level: 1}, "opener", "name", + {name: "DOMRect-API", level: 1}, + "getClientRects", + "getBoundingClientRect", + "getBounds", + "getBBox", + "getExtentOfChar", + "intersectionRect", + "boundingClientRect", + "rootBounds", ], defaultKeyValue: false }, @@ -195,6 +204,7 @@ "audio", "history", "window", + "DOMRect", ], defaultKeyValue: false }, @@ -252,6 +262,11 @@ defaultValue: false, urlSpecific: true }, + { + name: "protectDOMRect", + defaultValue: true, + urlSpecific: true + }, { name: "blockDataURLs", defaultValue: true diff --git a/manifest.json b/manifest.json index 145aa51..e62c476 100644 --- a/manifest.json +++ b/manifest.json @@ -39,6 +39,7 @@ "lib/modifiedAudioAPI.js", "lib/modifiedHistoryAPI.js", "lib/modifiedWindowAPI.js", + "lib/modifiedDOMRectAPI.js", "lib/modifiedAPI.js", "lib/randomSupplies.js", "lib/intercept.js", diff --git a/options/settingsDisplay.js b/options/settingsDisplay.js index 4be9493..589bb18 100644 --- a/options/settingsDisplay.js +++ b/options/settingsDisplay.js @@ -404,6 +404,13 @@ "displayAdvancedSettings": [true] } }, + "DOMRect-API", + { + "name": "protectDOMRect", + "displayDependencies": { + "displayAdvancedSettings": [true] + } + }, "misc", { "name": "theme" From c83b1f8a3bf586d305c0d2b8c2068a682c2a4157 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Wed, 5 Sep 2018 08:21:22 +0200 Subject: [PATCH 02/12] Added test for DOMRect --- test/domRectIFrame.html | 42 +++++++++++++++++++++++++ test/domRectTest.html | 42 +++++++++++++++++++++++++ test/domRectTest.js | 68 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 test/domRectIFrame.html create mode 100644 test/domRectTest.html create mode 100644 test/domRectTest.js diff --git a/test/domRectIFrame.html b/test/domRectIFrame.html new file mode 100644 index 0000000..443ad4d --- /dev/null +++ b/test/domRectIFrame.html @@ -0,0 +1,42 @@ + + + + + + + + +
+

https://browserleaks.com/rects

+Element.getClientRects (̿▀̿ ̿Ĺ̯̿̿▀̿ ̿)̄
+               +               +               +               +F i n g e r p r i n t i n g ? +
+ + \ No newline at end of file diff --git a/test/domRectTest.html b/test/domRectTest.html new file mode 100644 index 0000000..24b3a28 --- /dev/null +++ b/test/domRectTest.html @@ -0,0 +1,42 @@ + + + + DOMRect test + + + +

DOMRect test

+ +
+
+

+ Hash:
+ Data:
+ +
+
+ + + \ No newline at end of file diff --git a/test/domRectTest.js b/test/domRectTest.js new file mode 100644 index 0000000..b2d67fd --- /dev/null +++ b/test/domRectTest.js @@ -0,0 +1,68 @@ +(function(){ + "use strict"; + function byteArrayToHex(arrayBuffer){ + var chunks = []; + (new Uint32Array(arrayBuffer)).forEach(function(num){ + chunks.push(num.toString(16)); + }); + return chunks.map(function(chunk){ + return "0".repeat(8 - chunk.length) + chunk; + }).join(""); + } + + const container = document.getElementById("tests"); + const iframe = document.getElementById("iframe"); + const template = document.querySelector(".test"); + template.parentElement.removeChild(template); + + function getElements(){ + const doc = iframe.contentDocument; + + return Array.from(doc.querySelectorAll("*[id^=rect]")); + } + + function createTest(title, callback){ + const properties = ["x", "y", "width", "height", "top", "left", "right", "bottom"]; + function performTest(){ + const rects = getElements().map(callback); + const data = new Float64Array(rects.length * properties.length); + rects.forEach(function(rect, i){ + properties.forEach(function(property, j){ + data[i * properties.length + j] = rect[property]; + }); + }); + + crypto.subtle.digest("SHA-256", data) + .then(function(hash){ + output.querySelector(".hash").textContent = byteArrayToHex(hash); + }); + + output.querySelector(".data").innerHTML = "" + + rects.map(function(rect, i){ + return ""; + }).join("") + + "" + + properties.map(function(property){ + return "" + rects.map(function(rect, i){ + return ""; + }).join("") + ""; + }).join("") + + "
rect " + (i + 1) + "
" + property + "" + rect[property] + "
"; + + } + const output = template.cloneNode(true); + output.querySelector(".title").textContent = title; + output.querySelector("button").addEventListener("click", performTest); + + container.appendChild(output); + performTest(); + } + iframe.addEventListener("load", function(){ + createTest("getClientRects", function(element){ + return element.getClientRects()[0]; + }); + createTest("getBoundingClientRect", function(element){ + return element.getBoundingClientRect(); + }); + }); +}()); \ No newline at end of file From 350fdd32788ba4723217ec726dca84a25346479e Mon Sep 17 00:00:00 2001 From: kkapsner Date: Wed, 5 Sep 2018 15:48:16 +0200 Subject: [PATCH 03/12] Added DOMRect detection tests. --- test/detectionTest.js | 97 +++++++++++++++++++++++++++++++++++++++++++ test/index.html | 1 + 2 files changed, 98 insertions(+) diff --git a/test/detectionTest.js b/test/detectionTest.js index 77e3d10..a09154b 100644 --- a/test/detectionTest.js +++ b/test/detectionTest.js @@ -177,6 +177,16 @@ addTest("property descriptor", function(log){ configurable: true } }, + { + object: Element.prototype, + name: "getClientRects", + descriptor: { + value: function getClientRects(){}, + writable: true, + enumerable: true, + configurable: true + } + }, ]; return properties.reduce(function(pass, property){ @@ -419,4 +429,91 @@ addTest("window name change", function(log){ return true; } return false; +}); + +function checkDOMRectData(rect, data, log){ + "use strict"; + + var detected = false; + ["x", "y", "width", "height"].forEach(function(property){ + if (data[property] !== rect[property]){ + log("Wrong value for", property, ":", data[property], "!=", rect[property]); + detected = true; + } + }); + return detected; +} + +function getRectByData(data){ + "use strict"; + + var el = document.createElement("div"); + el.style.cssText = "position: fixed;" + + "left: " + data.x + "px; " + + "top: " + data.y + "px; " + + "width: " + data.width + "px; " + + "height: " + data.height + "px;"; + + document.body.appendChild(el); + var rect = el.getBoundingClientRect(); + document.body.removeChild(el); + return rect; +} + +addTest("self created DOMRect", function(log){ + "use strict"; + + var data = { + x: Math.PI, + y: Math.E, + width: Math.LOG10E, + height: Math.LOG2E + }; + var rect = new DOMRect(data.x, data.y, data.width, data.height); + return checkDOMRectData(rect, data, log); +}); + +addTest("known DOMRect", function(log){ + "use strict"; + + var data = { + x: 1 + 1/4, + y: 2, + width: 3, + height: 4 + }; + + var rect = getRectByData(data); + + return checkDOMRectData(rect, data, log); +}); +addTest("changed DOMRect", function(log){ + "use strict"; + + var data = { + x: Math.PI, + y: 2, + width: 3, + height: 4 + }; + + var rect = getRectByData(data); + rect.x = Math.PI; + + return checkDOMRectData(rect, data, log); +}); +addTest("recreated DOMRect", function(log){ + "use strict"; + + var data = { + x: Math.PI, + y: Math.E, + width: Math.LOG10E, + height: Math.LOG2E + }; + + var rect = getRectByData(data); + var rect2 = getRectByData(rect); + + return checkDOMRectData(rect2, rect, log); }); \ No newline at end of file diff --git a/test/index.html b/test/index.html index 777ecf0..d83b9d5 100644 --- a/test/index.html +++ b/test/index.html @@ -10,6 +10,7 @@
  • Fingerprinting test
  • Data-URL test
  • Audio Fingerprint test
  • +
  • DOMRect Fingerprint test
  • Detection test
  • Performance test
  • Support for webGL
  • From 1d5953e8c78bc8ebb2dd91e72c27e503cb3189b0 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Wed, 5 Sep 2018 15:50:59 +0200 Subject: [PATCH 04/12] Added protection from undefined properties. DOMRect has some new properties that are only in nightly at the moment. --- lib/intercept.js | 57 +++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/lib/intercept.js b/lib/intercept.js index e35437e..c5055ef 100644 --- a/lib/intercept.js +++ b/lib/intercept.js @@ -100,7 +100,13 @@ forEachFunction(function({name, object}){ var map = originalPropertyDescriptors[name] || new WeakMap(); originalPropertyDescriptors[name] = map; - map.set(object, Object.getOwnPropertyDescriptor(object, name)); + + const originalPropertyDescriptor = Object.getOwnPropertyDescriptor(object, name); + if (!originalPropertyDescriptor){ + return; + } + + map.set(object, originalPropertyDescriptor); Object.defineProperty( object, name, @@ -145,11 +151,14 @@ if (preIntercepted){ preIntercepted = false; forEachFunction(function({name, object}){ - Object.defineProperty( - object, - name, - originalPropertyDescriptors[name].get(object) - ); + const originalPropertyDescriptor = originalPropertyDescriptors[name].get(object); + if (originalPropertyDescriptor){ + Object.defineProperty( + object, + name, + originalPropertyDescriptor + ); + } }); } }; @@ -286,26 +295,28 @@ var original = constructor.prototype[name]; const checker = generateChecker(name, changedFunction, siteStatus, original); var descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, name); - if (descriptor.hasOwnProperty("value")){ - if (changedFunction.fakeGenerator){ - descriptor.value = exportFunction( - changedFunction.fakeGenerator(checker), - window - ); + if (descriptor){ + if (descriptor.hasOwnProperty("value")){ + if (changedFunction.fakeGenerator){ + descriptor.value = exportFunction( + changedFunction.fakeGenerator(checker), + window + ); + } + else { + descriptor.value = null; + } } else { - descriptor.value = null; + descriptor.get = exportFunction(function(){ + return exportFunction( + changedFunction.fakeGenerator(checker), + window + ); + }, window); } + Object.defineProperty(constructor.prototype, name, descriptor); } - else { - descriptor.get = exportFunction(function(){ - return exportFunction( - changedFunction.fakeGenerator(checker), - window - ); - }, window); - } - Object.defineProperty(constructor.prototype, name, descriptor); } }); } @@ -319,7 +330,7 @@ const object = objectGetter(getWrapped(window)); if (object){ const descriptor = Object.getOwnPropertyDescriptor(object, name); - if (descriptor.hasOwnProperty("get")){ + if (descriptor && descriptor.hasOwnProperty("get")){ var original = descriptor.get; const checker = generateChecker(name, changedGetter, siteStatus, original); const getter = changedGetter.getterGenerator(checker); From df1d59cf524c1788e72bbd068ff382952e8f2b55 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Wed, 5 Sep 2018 15:53:01 +0200 Subject: [PATCH 05/12] Added support for pixel fractions Pixel fractions can be controlled by CSS. These fractions must not be faked. For #236. --- _locales/en/messages.json | 9 +++++++++ lib/intercept.js | 2 +- lib/modifiedDOMRectAPI.js | 26 +++++++++++++------------- lib/settingDefinitions.js | 4 ++++ options/settingsDisplay.js | 7 +++++++ 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index fbbb1b0..2742f56 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -898,6 +898,15 @@ "description": "" }, + "domRectIntegerFactor_title": { + "message": "DOMRect integer factor", + "description": "" + }, + "domRectIntegerFactor_description": { + "message": "Some fraction of a pixel can be controlled by CSS. To prevent detection values of a DOMRect that multiplied with this factor are integers will not be altered.", + "description": "" + }, + "theme_title": { "message": "Theme", "description": "" diff --git a/lib/intercept.js b/lib/intercept.js index c5055ef..11a2137 100644 --- a/lib/intercept.js +++ b/lib/intercept.js @@ -337,7 +337,7 @@ descriptor.get = exportFunction(getter, window); if (descriptor.hasOwnProperty("set") && changedGetter.setterGenerator){ - const setter = changedGetter.setterGenerator(window, descriptor.set); + const setter = changedGetter.setterGenerator(window, descriptor.set, prefs); descriptor.set = exportFunction(setter, window); } diff --git a/lib/modifiedDOMRectAPI.js b/lib/modifiedDOMRectAPI.js index ca8a3f6..9208d9c 100644 --- a/lib/modifiedDOMRectAPI.js +++ b/lib/modifiedDOMRectAPI.js @@ -45,11 +45,11 @@ function getDOMRectRegistration(domRect){ return registeredRects.get(getWrapped(domRect)); } - function getFakeDomRect(window, domRect, notify){ + function getFakeDomRect(window, domRect, prefs, notify){ var rng = randomSupply.getRng(4, window); function getFakeValue(value, i){ - if (value % 1 === 0){ + if ((value * prefs("domRectIntegerFactor", window.location)) % 1 === 0){ return value; } else { @@ -159,17 +159,17 @@ if (prefs("protectDOMRect", window.location)){ const registration = getDOMRectRegistration(this); if (registration){ - return getFakeDomRect(window, this, registration.notify)[property]; + return getFakeDomRect(window, this, prefs, registration.notify)[property]; } } return originalValue; }; } - function setProperty(domRect, window, original, newValue, property){ // eslint-disable-line max-params + function setProperty(domRect, window, original, newValue, property, prefs){ // eslint-disable-line max-params const registration = getDOMRectRegistration(domRect); if (registration){ - const fakeDomRect = getFakeDomRect(window, domRect, registration.notify); + const fakeDomRect = getFakeDomRect(window, domRect, prefs, registration.notify); registeredRects.delete(getWrapped(domRect)); ["x", "y", "width", "height"].forEach(function(prop){ if (prop === property){ @@ -200,10 +200,10 @@ }; return Object.getOwnPropertyDescriptor(temp, "x").get; }, - setterGenerator: function(window, original){ + setterGenerator: function(window, original, prefs){ const temp = { set x(x){ - setProperty(this, window, original, x, "x"); + setProperty(this, window, original, x, "x", prefs); } }; return Object.getOwnPropertyDescriptor(temp, "x").set; @@ -223,10 +223,10 @@ }; return Object.getOwnPropertyDescriptor(temp, "y").get; }, - setterGenerator: function(window, original){ + setterGenerator: function(window, original, prefs){ const temp = { set y(y){ - setProperty(this, window, original, y, "y"); + setProperty(this, window, original, y, "y", prefs); } }; return Object.getOwnPropertyDescriptor(temp, "y").set; @@ -246,10 +246,10 @@ }; return Object.getOwnPropertyDescriptor(temp, "width").get; }, - setterGenerator: function(window, original){ + setterGenerator: function(window, original, prefs){ const temp = { set width(width){ - setProperty(this, window, original, width, "width"); + setProperty(this, window, original, width, "width", prefs); } }; return Object.getOwnPropertyDescriptor(temp, "width").set; @@ -269,10 +269,10 @@ }; return Object.getOwnPropertyDescriptor(temp, "height").get; }, - setterGenerator: function(window, original){ + setterGenerator: function(window, original, prefs){ const temp = { set height(height){ - setProperty(this, window, original, height, "height"); + setProperty(this, window, original, height, "height", prefs); } }; return Object.getOwnPropertyDescriptor(temp, "height").set; diff --git a/lib/settingDefinitions.js b/lib/settingDefinitions.js index ef530f3..7ee596d 100644 --- a/lib/settingDefinitions.js +++ b/lib/settingDefinitions.js @@ -267,6 +267,10 @@ defaultValue: true, urlSpecific: true }, + { + name: "domRectIntegerFactor", + defaultValue: 4 + }, { name: "blockDataURLs", defaultValue: true diff --git a/options/settingsDisplay.js b/options/settingsDisplay.js index 589bb18..5f592ce 100644 --- a/options/settingsDisplay.js +++ b/options/settingsDisplay.js @@ -411,6 +411,13 @@ "displayAdvancedSettings": [true] } }, + { + "name": "domRectIntegerFactor", + "displayDependencies": { + "protectDOMRect": [true], + "displayAdvancedSettings": [true] + } + }, "misc", { "name": "theme" From 6a6a123c3bf198b78b53158dbfb5a96e5d60aad7 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Wed, 5 Sep 2018 16:42:25 +0200 Subject: [PATCH 06/12] Added german DOMRect translation. --- _locales/de/messages.json | 54 +++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 9fd93b5..91de628 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -105,6 +105,10 @@ "message": "Window API", "description": "" }, + "section_DOMRect-api":{ + "message": "DOMRect API", + "description": "" + }, "displayAdvancedSettings_title": { "message": "Expertenmodus", @@ -213,6 +217,18 @@ "message": "Wollen Sie das Auslesen über die Window-API erlauben?", "description": "" }, + "askForDOMRectPermission": { + "message": "Wollen Sie die DOMRect-API erlauben?", + "description": "" + }, + "askForDOMRectInputPermission": { + "message": "Wollen Sie das Schreiben über die DOMRect-API erlauben?", + "description": "" + }, + "askForDOMRectReadoutPermission": { + "message": "Wollen Sie das Auslesen über die DOMRect-API erlauben?", + "description": "" + }, "askOnlyOnce_title": { "message": "Nur einmal nachfragen", "description": "" @@ -544,6 +560,10 @@ "message": "Window-Auslese vorgetäuscht auf {url}", "description": "" }, + "fakedDOMRectReadout": { + "message": "DOMRect-Auslese vorgetäuscht auf {url}", + "description": "" + }, "fakedInput": { "message": "Bei Ausgabe vorgetäuscht auf {url}", "description": "" @@ -695,14 +715,14 @@ "message": "Dateispezifische Whitelist", "description": "" }, - "whiteList_description": { - "message": "Domänen oder URLs, die die -API verwenden dürfen. Mehrere Einträge müssen durch ein Komma getrennt werden.", - "description": "" - }, "whiteList_title": { "message": "Whitelist", "description": "" }, + "whiteList_description": { + "message": "Domänen oder URLs, die die -API verwenden dürfen. Mehrere Einträge müssen durch ein Komma getrennt werden.", + "description": "" + }, "whitelist": { "message": "erlauben", "description": "" @@ -716,14 +736,14 @@ "description": "" }, - "sessionWhiteList_description": { - "message": "Domänen oder URLs, die die -API in der aktuellen Sitzung verwenden dürfen. Mehrere Einträge müssen durch ein Komma getrennt werden.", - "description": "" - }, "sessionWhiteList_title": { "message": "Sitzungs-Whitelist", "description": "" }, + "sessionWhiteList_description": { + "message": "Domänen oder URLs, die die -API in der aktuellen Sitzung verwenden dürfen. Mehrere Einträge müssen durch ein Komma getrennt werden.", + "description": "" + }, "whitelistDomainTemporarily": { "message": "erlaube Domain übergangsweise", @@ -870,6 +890,24 @@ "description": "" }, + "protectDOMRect_title": { + "message": "DOMRect-API beschützen", + "description": "" + }, + "protectDOMRect_description": { + "message": "Beschützt vor dem \"getClientRects()\" Fingerprint und einige andere ähnliche Methoden.", + "description": "" + }, + + "domRectIntegerFactor_title": { + "message": "DOMRect Ganzzahlfaktor", + "description": "" + }, + "domRectIntegerFactor_description": { + "message": "Ein Bruchteil eines Pixels kann durch CSS kontrolliert wurden. Eigenschaften eines DOMRect, die multipliziert mit diesem Faktor eine ganze Zahl ergeben, dürfen nicht verändert werden um eine Detection zu verhindern.", + "description": "" + }, + "theme_title": { "message": "Theme", "description": "" From b2b66b89602dbcd9d65ae8713c11531bee6db681 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Wed, 5 Sep 2018 16:42:57 +0200 Subject: [PATCH 07/12] Updated documentation. --- .documentation/addon description/de/description.txt | 1 + .documentation/addon description/en/description.txt | 1 + README.md | 8 ++++++++ releaseNotes.txt | 1 + 4 files changed, 11 insertions(+) diff --git a/.documentation/addon description/de/description.txt b/.documentation/addon description/de/description.txt index 0ac42b2..d31cdd1 100644 --- a/.documentation/addon description/de/description.txt +++ b/.documentation/addon description/de/description.txt @@ -20,6 +20,7 @@ Geschützte "Fingerprinting"-APIs:
  • audio
  • history
  • window (standardmäßig deaktiviert)
  • +
  • DOMRect
  • Falls Sie Fehler finden oder Verbesserungsvorschläge haben, teilen Sie mir das bitte auf https://github.com/kkapsner/CanvasBlocker/issues mit. \ No newline at end of file diff --git a/.documentation/addon description/en/description.txt b/.documentation/addon description/en/description.txt index d52d667..57b1a73 100644 --- a/.documentation/addon description/en/description.txt +++ b/.documentation/addon description/en/description.txt @@ -20,6 +20,7 @@ Protected "fingerprinting" APIs:
  • audio
  • history
  • window (disabled by default)
  • +
  • DOMRect
  • Please report issues and feature requests at https://github.com/kkapsner/CanvasBlocker/issues diff --git a/README.md b/README.md index bbbaa14..864245e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ The different block modes are:
  • allow everything: Ignore all lists and allow the <canvas> API on all websites.
  • +Protected "fingerprinting" APIs: + * canvas 2d + * webGL + * audio + * history + * window (disabled by default) + * DOMRect + Special thanks to: * spodermenpls for finding all the typos * Thorin-Oakenpants for the icon idea diff --git a/releaseNotes.txt b/releaseNotes.txt index fbb1923..30b85fb 100644 --- a/releaseNotes.txt +++ b/releaseNotes.txt @@ -4,6 +4,7 @@ Version 0.5.4: new features: - added save/load directly to/from file option + - added protection for DOMRect (getClientRects) fixes: - From a01ccf458f277cfd45d412f6550d49470cde169b Mon Sep 17 00:00:00 2001 From: kkapsner Date: Wed, 5 Sep 2018 16:47:32 +0200 Subject: [PATCH 08/12] Corrected type and api on DOMRect changed functions --- lib/modifiedDOMRectAPI.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/modifiedDOMRectAPI.js b/lib/modifiedDOMRectAPI.js index 9208d9c..65fbed1 100644 --- a/lib/modifiedDOMRectAPI.js +++ b/lib/modifiedDOMRectAPI.js @@ -73,7 +73,8 @@ scope.changedFunctions = { getClientRects: { - type: "domRect", + type: "readout", + api: "domRect", getStatus: getStatus, object: ["Range", "Element"], fakeGenerator: function(checker){ @@ -90,7 +91,8 @@ } }, getBoundingClientRect: { - type: "domRect", + type: "readout", + api: "domRect", getStatus: getStatus, object: ["Range", "Element"], fakeGenerator: function(checker){ @@ -106,7 +108,8 @@ } }, getBounds: { - type: "domRect", + type: "readout", + api: "domRect", getStatus: getStatus, object: ["DOMQuad"], fakeGenerator: function(checker){ @@ -121,7 +124,8 @@ } }, getBBox: { - type: "domRect", + type: "readout", + api: "domRect", getStatus: getStatus, object: ["SVGGraphicsElement"], fakeGenerator: function(checker){ @@ -136,7 +140,8 @@ } }, getExtentOfChar: { - type: "domRect", + type: "readout", + api: "domRect", getStatus: getStatus, object: ["SVGTextContentElement"], fakeGenerator: function(checker){ From f8dc7ff05b2b1251eb8dc572a7b02e5ed5b582bc Mon Sep 17 00:00:00 2001 From: kkapsner Date: Wed, 5 Sep 2018 16:56:34 +0200 Subject: [PATCH 09/12] DOMRect: added test for range functions --- test/domRectTest.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/domRectTest.js b/test/domRectTest.js index b2d67fd..83fe4f9 100644 --- a/test/domRectTest.js +++ b/test/domRectTest.js @@ -58,11 +58,21 @@ performTest(); } iframe.addEventListener("load", function(){ - createTest("getClientRects", function(element){ + createTest("Element.getClientRects", function(element){ return element.getClientRects()[0]; }); - createTest("getBoundingClientRect", function(element){ + createTest("Element.getBoundingClientRect", function(element){ return element.getBoundingClientRect(); }); + createTest("Range.getClientRects", function(element){ + var range = document.createRange(); + range.selectNode(element); + return range.getClientRects()[0]; + }); + createTest("Range.getBoundingClientRect", function(element){ + var range = document.createRange(); + range.selectNode(element); + return range.getBoundingClientRect(); + }); }); }()); \ No newline at end of file From d3d137beed51dfb571569b523e3ecacc2167899d Mon Sep 17 00:00:00 2001 From: kkapsner Date: Thu, 6 Sep 2018 17:30:44 +0200 Subject: [PATCH 10/12] Fixed accidental blocking See #243 --- lib/modifiedDOMRectAPI.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modifiedDOMRectAPI.js b/lib/modifiedDOMRectAPI.js index 65fbed1..e58f4fb 100644 --- a/lib/modifiedDOMRectAPI.js +++ b/lib/modifiedDOMRectAPI.js @@ -400,9 +400,9 @@ ]; - function getStatus(obj, status){ + function getStatus(obj, status, prefs){ status = Object.create(status); - status.active = hasType(status, "readout"); + status.active = prefs("protectDOMRect", status.url) && hasType(status, "readout"); return status; } From 639ecb7cf469b3d7e774b3749ea7232ff4727647 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Thu, 6 Sep 2018 20:16:44 +0200 Subject: [PATCH 11/12] Orthographie --- _locales/de/messages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index d084acc..58ff5bb 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -895,7 +895,7 @@ "description": "" }, "protectDOMRect_description": { - "message": "Beschützt vor dem \"getClientRects()\" Fingerprint und einige andere ähnliche Methoden.", + "message": "Beschützt vor dem \"getClientRects()\" Fingerprint und einigen anderen ähnlichen Methoden.", "description": "" }, @@ -904,7 +904,7 @@ "description": "" }, "domRectIntegerFactor_description": { - "message": "Ein Bruchteil eines Pixels kann durch CSS kontrolliert wurden. Eigenschaften eines DOMRect, die multipliziert mit diesem Faktor eine ganze Zahl ergeben, dürfen nicht verändert werden um eine Detection zu verhindern.", + "message": "Ein Bruchteil eines Pixels kann durch CSS kontrolliert werden. Eigenschaften eines DOMRect, die multipliziert mit diesem Faktor eine ganze Zahl ergeben, dürfen nicht verändert werden um eine Detektion zu verhindern.", "description": "" }, From eed3e670ce2beee6d748edf711f31ac9295fbea1 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Thu, 6 Sep 2018 20:23:13 +0200 Subject: [PATCH 12/12] Harmonized ask texts --- _locales/en/messages.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index a5648c1..30e0901 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -186,11 +186,11 @@ "description": "" }, "askForAudioInputPermission": { - "message": "Do you want to allow audio-API input?", + "message": "Do you want to allow audio API input?", "description": "" }, "askForAudioReadoutPermission": { - "message": "Do you want to allow audio readout?", + "message": "Do you want to allow audio API readout?", "description": "" }, "askForHistoryPermission": { @@ -198,11 +198,11 @@ "description": "" }, "askForHistoryInputPermission": { - "message": "Do you want to allow history-API input?", + "message": "Do you want to allow history API input?", "description": "" }, "askForHistoryReadoutPermission": { - "message": "Do you want to allow history readout?", + "message": "Do you want to allow history API readout?", "description": "" }, "askForWindowPermission": { @@ -210,11 +210,11 @@ "description": "" }, "askForWindowInputPermission": { - "message": "Do you want to allow window-API input?", + "message": "Do you want to allow window API input?", "description": "" }, "askForWindowReadoutPermission": { - "message": "Do you want to allow window readout?", + "message": "Do you want to allow window API readout?", "description": "" }, "askForDOMRectPermission": { @@ -222,11 +222,11 @@ "description": "" }, "askForDOMRectInputPermission": { - "message": "Do you want to allow DOMRect-API input?", + "message": "Do you want to allow DOMRect API input?", "description": "" }, "askForDOMRectReadoutPermission": { - "message": "Do you want to allow DOMRect readout?", + "message": "Do you want to allow DOMRect API readout?", "description": "" }, "askOnlyOnce_title": {