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

Commit

Permalink
Add more canvas/webgl read detection
Browse files Browse the repository at this point in the history
  • Loading branch information
diracdeltas committed Apr 14, 2016
1 parent 1d365ba commit 478ad8b
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 75 deletions.
105 changes: 32 additions & 73 deletions app/extensions/brave/brave-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -765,11 +765,13 @@ if (typeof KeyEvent === 'undefined') {
})

/** Begin canvas fingerprinting detection **/
/**
* @return {string}
*/
function getPageScript () {
// return a string
return '(' + function (ERROR) {
return '(' + Function.prototype.toString.call(function (ERROR) {
ERROR.stackTraceLimit = Infinity // collect all frames
var event_id = document.currentScript.getAttribute('data-event-id')
var event_id = document.currentScript ? document.currentScript.getAttribute('data-event-id') : ''

This comment has been minimized.

Copy link
@garvankeeley

garvankeeley Jun 1, 2016

Contributor

@diracdeltas What is this line for? In particular, what should I know about 'data-event-id'? Thanks.
(For bringing to UIWebView-land I'll need to understand this line-by-line and I'l probably have a bunch of q's)

This comment has been minimized.

Copy link
@diracdeltas

diracdeltas Jun 1, 2016

Author Member

Sure, no problem. This part was mostly copied from Privacy Badger Chrome anyway.

This may not be necessary on mobile, but on laptop, brave-default.js can't directly modify DOM js object prototypes on the page. Instead, brave-default.js creates a new script tag and inserts the script returned by getPageScript. This script is able to modify js objects on the page.

This script needs to post messages back to brave-default.js reporting which fingerprinting methods were blocked. The way it does that is via document.dispatchEvent(new window.CustomEvent(event_id, ...)). In brave-default.js, we listen for these messages using document.addEventListener(event_id, ...).


// from Underscore v1.6.0
function debounce (func, wait, immediate) {
Expand Down Expand Up @@ -885,110 +887,64 @@ if (typeof KeyEvent === 'undefined') {
}

/**
* Monitor the writes in a canvas instance
* Monitor the reads from a canvas instance
* @param item special item objects
*/
function trapInstanceMethod (item) {
var is_canvas_write = (
item.propName === 'fillText' || item.propName === 'strokeText'
)

item.obj[item.propName] = (function (orig) {
return function () {
var args = arguments

if (is_canvas_write) {
// to avoid false positives,
// bail if the text being written is too short
if (!args[0] || args[0].length < 5) {
return orig.apply(this, args)
}
}

console.log('trapping', item.objName)
var script_url = getOriginatingScriptUrl()
var msg = {
obj: item.objName,
prop: item.propName,
scriptUrl: stripLineAndColumnNumbers(script_url)
}

if (item.hasOwnProperty('extra')) {
msg.extra = item.extra.apply(this, args)
}

// Block the read from occuring; send info to background page instead
console.log('blocking canvas read', msg)
send(msg)

/*
if (is_canvas_write) {
// optimization: one canvas write is enough,
// restore original write method
// to this CanvasRenderingContext2D object instance
this[item.propName] = orig
}
return orig.apply(this, args)
*/
}
}(item.obj[item.propName]))
}

var methods = []
var canvasMethods = ['getImageData', 'fillText', 'strokeText']
var canvasMethods = ['getImageData', 'getLineDash', 'measureText']
canvasMethods.forEach(function (method) {
var item = {
objName: 'CanvasRenderingContext2D.prototype',
propName: method,
obj: window.CanvasRenderingContext2D.prototype,
extra: function () {
return {
canvas: true
}
}
obj: window.CanvasRenderingContext2D.prototype
}

if (method === 'getImageData') {
item.extra = function () {
var args = arguments
var width = args[2]
var height = args[3]

// "this" is a CanvasRenderingContext2D object
if (width === undefined) {
width = this.canvas.width
}
if (height === undefined) {
height = this.canvas.height
}
methods.push(item)
})

return {
canvas: true,
width: width,
height: height
}
}
var canvasElementMethods = ['toDataURL', 'toBlob']
canvasElementMethods.forEach(function (method) {
var item = {
objName: 'HTMLCanvasElement.prototype',
propName: method,
obj: window.HTMLCanvasElement.prototype
}

methods.push(item)
})

methods.push({
objName: 'HTMLCanvasElement.prototype',
propName: 'toDataURL',
obj: window.HTMLCanvasElement.prototype,
extra: function () {
// "this" is a canvas element
return {
canvas: true,
width: this.width,
height: this.height
}
var webglMethods = ['getSupportedExtensions', 'getParameter', 'getContextAttributes',
'getShaderPrecisionFormat', 'getExtension']
webglMethods.forEach(function (method) {
var item = {
objName: 'WebGLRenderingContext.prototype',
propName: method,
obj: window.WebGLRenderingContext.prototype
}
methods.push(item)
})

methods.forEach(trapInstanceMethod)

// save locally to keep from getting overwritten by site code
} + '(Error));'
}) + '(Error));'
}

/**
Expand All @@ -1013,10 +969,13 @@ if (typeof KeyEvent === 'undefined') {
}

ipcRenderer.on('block-canvas-fingerprinting', function () {
var event_id = Math.random()
var event_id = Math.random().toString()

// listen for messages from the script we are about to insert
document.addEventListener(event_id, function (e) {
if (!e.detail) {
return
}
// pass these on to the background page
ipcRenderer.send('got-canvas-fingerprinting', e.detail)
})
Expand Down
4 changes: 2 additions & 2 deletions js/components/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ class Frame extends ImmutableComponent {
ipc.send(messages.CHECK_CERT_ERROR_ACCEPTED, parsedUrl.host, this.props.frame.get('key'))
}
}
// TODO: Add setting for blocking canvas fingerprinting
this.webview.send(messages.BLOCK_CANVAS_FINGERPRINTING)
windowActions.updateBackForwardState(
this.props.frame,
this.webview.canGoBack(),
Expand All @@ -321,8 +323,6 @@ class Frame extends ImmutableComponent {
if (getSetting(settings.PASSWORD_MANAGER_ENABLED)) {
this.webview.send(messages.AUTOFILL_PASSWORD)
}
// TODO: Add setting for blocking canvas fingerprinting
this.webview.send(messages.BLOCK_CANVAS_FINGERPRINTING)
let security = this.props.frame.get('security')
if (this.props.frame.get('location') === 'about:certerror' &&
security && security.get('certDetails')) {
Expand Down

0 comments on commit 478ad8b

Please sign in to comment.