Skip to content
This repository was archived by the owner on Dec 11, 2019. It is now read-only.

Commit 0e40245

Browse files
snyderpdiracdeltas
authored andcommitted
replace blocked methods with a proxy object that yields itself, to av… (#10287)
* replace blocked methods with a proxy object that yields itself, to avoid breaking code that expects the feature to be in place * added test case for checking that proxy-based feature blocking works * minor corrections to JS formatting, slight reworking of the ToPrimitive handling code in the proxy * further, extremely minor corrections to JS formatting (" -> ')
1 parent 69eee42 commit 0e40245

File tree

3 files changed

+114
-0
lines changed

3 files changed

+114
-0
lines changed

app/extensions/brave/content/scripts/blockCanvasFingerprinting.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,76 @@ if (chrome.contentSettings.canvasFingerprinting == 'block') {
7373
return script_url.replace(/:\d+:\d+$/, '')
7474
}
7575

76+
// To avoid throwing hard errors on code that expects a fingerprinting feature
77+
// to be in place, create a method that can be called as if it were most
78+
// other types of objects (ie can be called like a function, can be indexed
79+
// into like an array, can have properties looked up, etc).
80+
//
81+
// This is done in two steps. First, create a default, no-op function
82+
// (`defaultFunc` below), and then second, wrap it in a Proxy that traps
83+
// on all these operations, and yields itself. This allows for long
84+
// chains of no-op operations like
85+
// AnalyserNode.prototype.getFloatFrequencyData().bort.alsoBort,
86+
// even though AnalyserNode.prototype.getFloatFrequencyData has been replaced.
87+
var defaultFunc = function () {}
88+
89+
// In order to avoid deeply borking things, we need to make sure we don't
90+
// prevent access to builtin object properties and functions (things
91+
// like (Object.prototype.constructor). So, build a list of those below,
92+
// and then special case those in the allPurposeProxy object's traps.
93+
var funcPropNames = Object.getOwnPropertyNames(defaultFunc)
94+
var unconfigurablePropNames = funcPropNames.filter(function (propName) {
95+
var possiblePropDesc = Object.getOwnPropertyDescriptor(defaultFunc, propName)
96+
return (possiblePropDesc && !possiblePropDesc.configurable)
97+
})
98+
99+
var valueOfCoercionFunc = function (hint) {
100+
if (hint === 'string') {
101+
return ''
102+
}
103+
if (hint === 'number' || hint === 'default') {
104+
return 0
105+
}
106+
return undefined
107+
}
108+
109+
var allPurposeProxy = new Proxy(defaultFunc, {
110+
get: function (target, property) {
111+
112+
if (property === Symbol.toPrimitive) {
113+
return valueOfCoercionFunc
114+
}
115+
116+
if (property === 'toString') {
117+
return ''
118+
}
119+
120+
if (property === 'valueOf') {
121+
return 0
122+
}
123+
124+
return allPurposeProxy
125+
},
126+
set: function () {
127+
return allPurposeProxy
128+
},
129+
apply: function () {
130+
return allPurposeProxy
131+
},
132+
ownKeys: function () {
133+
return unconfigurablePropNames
134+
},
135+
has: function (target, property) {
136+
return (unconfigurablePropNames.indexOf(property) > -1)
137+
},
138+
getOwnPropertyDescriptor: function (target, property) {
139+
if (unconfigurablePropNames.indexOf(property) === -1) {
140+
return undefined
141+
}
142+
return Object.getOwnPropertyDescriptor(defaultFunc, property)
143+
}
144+
})
145+
76146
function reportBlock (type) {
77147
var script_url = getOriginatingScriptUrl()
78148
var msg = {
@@ -82,6 +152,8 @@ if (chrome.contentSettings.canvasFingerprinting == 'block') {
82152

83153
// Block the read from occuring; send info to background page instead
84154
chrome.ipcRenderer.sendToHost('got-canvas-fingerprinting', msg)
155+
156+
return allPurposeProxy
85157
}
86158

87159
/**

test/bravery-components/braveryPanelTest.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,19 @@ describe('Bravery Panel', function () {
832832
.openBraveMenu(braveMenu, braveryPanelCompact)
833833
.waitUntil(verifyFingerprintingStat)
834834
})
835+
it('proxy fingerprinting method', function * () {
836+
const url = Brave.server.url('fingerprinting-proxy-method.html')
837+
yield this.app.client
838+
.tabByIndex(0)
839+
.loadUrl(url)
840+
.waitForUrl(url)
841+
.openBraveMenu(braveMenu, braveryPanel)
842+
.waitForVisible(fpSwitch)
843+
.click(fpSwitch)
844+
.keys(Brave.keys.ESCAPE)
845+
.tabByIndex(0)
846+
.waitForTextValue('#target', 'proxy blocking works')
847+
})
835848
it('block device enumeration', function * () {
836849
const url = Brave.server.url('enumerate_devices.html')
837850
yield this.app.client
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<html>
2+
<head>
3+
</head>
4+
<body>
5+
<div id="target">
6+
proxy blocking being tested
7+
</div>
8+
<script>
9+
(function () {
10+
11+
var canvas = document.createElement("canvas")
12+
canvas.width = 2000
13+
canvas.height = 200
14+
canvas.style.display = "inline"
15+
var gl = canvas.getContext("webgl")
16+
var format = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT)
17+
18+
// If blocking is working correctly, format will not be a
19+
// WebGLShaderPrecisionFormat instance, but a proxy instance, that allows
20+
// us to look up any indexes and attributes on the object we want.
21+
// If the proxy based method of blocking is working, then the below
22+
// *will not* throw.
23+
format.comeAlongBort['are you talking to me'].no.mySonIsAlsoNamedBort()
24+
25+
document.getElementsByTagName('div')[0].innerHTML = 'proxy blocking works'
26+
}())
27+
</script>
28+
</body>
29+
</html>

0 commit comments

Comments
 (0)