Permalink
Browse files

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 (" -> ')
  • Loading branch information...
snyderp authored and diracdeltas committed Aug 9, 2017
1 parent 69eee42 commit 0e40245c3c9da18777db8e11471dec0096181cd7
@@ -73,6 +73,76 @@ if (chrome.contentSettings.canvasFingerprinting == 'block') {
return script_url.replace(/:\d+:\d+$/, '')
}
// To avoid throwing hard errors on code that expects a fingerprinting feature
// to be in place, create a method that can be called as if it were most
// other types of objects (ie can be called like a function, can be indexed
// into like an array, can have properties looked up, etc).
//
// This is done in two steps. First, create a default, no-op function
// (`defaultFunc` below), and then second, wrap it in a Proxy that traps
// on all these operations, and yields itself. This allows for long
// chains of no-op operations like
// AnalyserNode.prototype.getFloatFrequencyData().bort.alsoBort,
// even though AnalyserNode.prototype.getFloatFrequencyData has been replaced.
var defaultFunc = function () {}
// In order to avoid deeply borking things, we need to make sure we don't
// prevent access to builtin object properties and functions (things
// like (Object.prototype.constructor). So, build a list of those below,
// and then special case those in the allPurposeProxy object's traps.
var funcPropNames = Object.getOwnPropertyNames(defaultFunc)
var unconfigurablePropNames = funcPropNames.filter(function (propName) {
var possiblePropDesc = Object.getOwnPropertyDescriptor(defaultFunc, propName)
return (possiblePropDesc && !possiblePropDesc.configurable)
})
var valueOfCoercionFunc = function (hint) {
if (hint === 'string') {
return ''
}
if (hint === 'number' || hint === 'default') {
return 0
}
return undefined
}
var allPurposeProxy = new Proxy(defaultFunc, {
get: function (target, property) {
if (property === Symbol.toPrimitive) {
return valueOfCoercionFunc
}
if (property === 'toString') {
return ''
}
if (property === 'valueOf') {
return 0
}
return allPurposeProxy
},
set: function () {
return allPurposeProxy
},
apply: function () {
return allPurposeProxy
},
ownKeys: function () {
return unconfigurablePropNames
},
has: function (target, property) {
return (unconfigurablePropNames.indexOf(property) > -1)
},
getOwnPropertyDescriptor: function (target, property) {
if (unconfigurablePropNames.indexOf(property) === -1) {
return undefined
}
return Object.getOwnPropertyDescriptor(defaultFunc, property)
}
})
function reportBlock (type) {
var script_url = getOriginatingScriptUrl()
var msg = {
@@ -82,6 +152,8 @@ if (chrome.contentSettings.canvasFingerprinting == 'block') {
// Block the read from occuring; send info to background page instead
chrome.ipcRenderer.sendToHost('got-canvas-fingerprinting', msg)
return allPurposeProxy
}
/**
@@ -832,6 +832,19 @@ describe('Bravery Panel', function () {
.openBraveMenu(braveMenu, braveryPanelCompact)
.waitUntil(verifyFingerprintingStat)
})
it('proxy fingerprinting method', function * () {
const url = Brave.server.url('fingerprinting-proxy-method.html')
yield this.app.client
.tabByIndex(0)
.loadUrl(url)
.waitForUrl(url)
.openBraveMenu(braveMenu, braveryPanel)
.waitForVisible(fpSwitch)
.click(fpSwitch)
.keys(Brave.keys.ESCAPE)
.tabByIndex(0)
.waitForTextValue('#target', 'proxy blocking works')
})
it('block device enumeration', function * () {
const url = Brave.server.url('enumerate_devices.html')
yield this.app.client
@@ -0,0 +1,29 @@
<html>
<head>
</head>
<body>
<div id="target">
proxy blocking being tested
</div>
<script>
(function () {
var canvas = document.createElement("canvas")
canvas.width = 2000
canvas.height = 200
canvas.style.display = "inline"
var gl = canvas.getContext("webgl")
var format = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT)
// If blocking is working correctly, format will not be a
// WebGLShaderPrecisionFormat instance, but a proxy instance, that allows
// us to look up any indexes and attributes on the object we want.
// If the proxy based method of blocking is working, then the below
// *will not* throw.
format.comeAlongBort['are you talking to me'].no.mySonIsAlsoNamedBort()
document.getElementsByTagName('div')[0].innerHTML = 'proxy blocking works'
}())
</script>
</body>
</html>

0 comments on commit 0e40245

Please sign in to comment.