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

Commit

Permalink
Merge pull request #14402 from brave/tor/0.23.x-webrtc2
Browse files Browse the repository at this point in the history
block webrtc functions via content script in tor tabs
  • Loading branch information
jumde authored Jun 14, 2018
2 parents 77dd133 + a6aff73 commit 051109c
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 150 deletions.
7 changes: 7 additions & 0 deletions app/browser/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const settings = require('../../js/constants/settings')
const {getBaseUrl} = require('../../js/lib/appUrlUtil')
const siteSettings = require('../../js/state/siteSettings')
const messages = require('../../js/constants/messages')
const webrtcConstants = require('../../js/constants/webrtcConstants')
const debounce = require('../../js/lib/debounce')
const aboutHistoryState = require('../common/state/aboutHistoryState')
const aboutNewTabState = require('../common/state/aboutNewTabState')
Expand Down Expand Up @@ -1086,6 +1087,12 @@ const api = {
console.log('Creating tab with properties: ', createProperties)
}
extensions.createTab(createProperties, (tab) => {
if (tab) {
// Initialize WebRTC IP handling to the safest default. This will
// be set based on shield settings in reducers/tabReducer.js once
// navigation starts.
tab.setWebRTCIPHandlingPolicy(webrtcConstants.disableNonProxiedUdp)
}
cb && cb(tab)
})
}
Expand Down
307 changes: 157 additions & 150 deletions app/extensions/brave/content/scripts/blockCanvasFingerprinting.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,167 +7,187 @@
* Chameleon <https://github.com/ghostwords/chameleon>, Copyright (C) 2015 ghostwords
* Privacy Badger Chrome <https://github.com/EFForg/privacybadger>, Copyright (C) 2015 Electronic Frontier Foundation and other contributors
*/
Error.stackTraceLimit = Infinity // collect all frames

if (chrome.contentSettings.canvasFingerprinting == 'block') {
Error.stackTraceLimit = Infinity // collect all frames

// https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi
/**
* Customize the stack trace
* @param structured If true, change to customized version
* @returns {*} Returns the stack trace
*/
function getStackTrace (structured) {
var errObj = {}
var origFormatter
var stack

if (structured) {
origFormatter = Error.prepareStackTrace
Error.prepareStackTrace = function (errObj, structuredStackTrace) {
return structuredStackTrace
}
// https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi
/**
* Customize the stack trace
* @param structured If true, change to customized version
* @returns {*} Returns the stack trace
*/
function getStackTrace (structured) {
var errObj = {}
var origFormatter
var stack

if (structured) {
origFormatter = Error.prepareStackTrace
Error.prepareStackTrace = function (errObj, structuredStackTrace) {
return structuredStackTrace
}
}

Error.captureStackTrace(errObj, getStackTrace)
stack = errObj.stack

if (structured) {
Error.prepareStackTrace = origFormatter
}
Error.captureStackTrace(errObj, getStackTrace)
stack = errObj.stack

return stack
if (structured) {
Error.prepareStackTrace = origFormatter
}

/**
* Checks the stack trace for the originating URL
* @returns {String} The URL of the originating script (URL:Line number:Column number)
*/
function getOriginatingScriptUrl () {
var trace = getStackTrace(true)
return stack
}

if (trace.length < 3) {
return ''
}
/**
* Checks the stack trace for the originating URL
* @returns {String} The URL of the originating script (URL:Line number:Column number)
*/
function getOriginatingScriptUrl () {
var trace = getStackTrace(true)

// this script is at 0 and 1
var callSite = trace[2]
if (trace.length < 3) {
return ''
}

if (callSite.isEval()) {
// argh, getEvalOrigin returns a string ...
var eval_origin = callSite.getEvalOrigin()
var script_url_matches = eval_origin.match(/\((http.*:\d+:\d+)/)
// this script is at 0 and 1
var callSite = trace[2]

return script_url_matches && script_url_matches[1] || eval_origin
} else {
return callSite.getFileName() + ':' + callSite.getLineNumber() + ':' + callSite.getColumnNumber()
}
if (callSite.isEval()) {
// argh, getEvalOrigin returns a string ...
var eval_origin = callSite.getEvalOrigin()
var script_url_matches = eval_origin.match(/\((http.*:\d+:\d+)/)

return script_url_matches && script_url_matches[1] || eval_origin
} else {
return callSite.getFileName() + ':' + callSite.getLineNumber() + ':' + callSite.getColumnNumber()
}
}

/**
* Strip away the line and column number (from stack trace urls)
* @param script_url The stack trace url to strip
* @returns {String} the pure URL
*/
function stripLineAndColumnNumbers (script_url) {
return script_url.replace(/:\d+:\d+$/, '')
}

/**
* Strip away the line and column number (from stack trace urls)
* @param script_url The stack trace url to strip
* @returns {String} the pure URL
*/
function stripLineAndColumnNumbers (script_url) {
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
}

// 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 allPurposeProxy = new Proxy(defaultFunc, {
get: function (target, property) {

if (property === Symbol.toPrimitive) {
return valueOfCoercionFunc
}

var valueOfCoercionFunc = function (hint) {
if (hint === 'string') {
if (property === 'toString') {
return ''
}
if (hint === 'number' || hint === 'default') {

if (property === 'valueOf') {
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)
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 = {
type,
scriptUrl: stripLineAndColumnNumbers(script_url)
}
function reportBlock (type) {
var script_url = getOriginatingScriptUrl()
var msg = {
type,
scriptUrl: stripLineAndColumnNumbers(script_url)
}

// Block the read from occuring; send info to background page instead
chrome.ipcRenderer.sendToHost('got-canvas-fingerprinting', msg)
// Block the read from occuring; send info to background page instead
chrome.ipcRenderer.sendToHost('got-canvas-fingerprinting', msg)

return allPurposeProxy
return allPurposeProxy
}

/**
* Monitor the reads from a canvas instance
* @param item special item objects
*/
function trapInstanceMethod (item) {
if (!item.methodName) {
chrome.webFrame.setGlobal(item.objName + ".prototype." + item.propName, reportBlock.bind(null, item.type))
} else {
chrome.webFrame.setGlobal(item.methodName, reportBlock.bind(null, item.type))
}
}

/**
* Monitor the reads from a canvas instance
* @param item special item objects
*/
function trapInstanceMethod (item) {
if (!item.methodName) {
chrome.webFrame.setGlobal(item.objName + ".prototype." + item.propName, reportBlock.bind(null, item.type))
} else {
chrome.webFrame.setGlobal(item.methodName, reportBlock.bind(null, item.type))
function blockWebRTC () {
const methods = []
// Based on https://github.com/webrtcHacks/webrtcnotify
const webrtcMethods = ['createOffer', 'createAnswer', 'setLocalDescription', 'setRemoteDescription']
webrtcMethods.forEach(function (method) {
const item = {
type: 'WebRTC',
objName: 'webkitRTCPeerConnection',
propName: method
}
}
methods.push(item)
})
methods.forEach(trapInstanceMethod)

// Block WebRTC device enumeration
trapInstanceMethod({
type: 'WebRTC',
methodName: 'navigator.mediaDevices.enumerateDevices'
})
}

if (chrome.contentSettings.canvasFingerprinting == 'block') {
var methods = []
var canvasMethods = ['getImageData', 'getLineDash', 'measureText', 'isPointInPath']
canvasMethods.forEach(function (method) {
Expand Down Expand Up @@ -243,23 +263,10 @@ if (chrome.contentSettings.canvasFingerprinting == 'block') {
}
methods.push(item)
})

// Based on https://github.com/webrtcHacks/webrtcnotify
var webrtcMethods = ['createOffer', 'createAnswer', 'setLocalDescription', 'setRemoteDescription']
webrtcMethods.forEach(function (method) {
var item = {
type: 'WebRTC',
objName: 'webkitRTCPeerConnection',
propName: method
}
methods.push(item)
})

methods.forEach(trapInstanceMethod)
blockWebRTC()
}

// Block WebRTC device enumeration
trapInstanceMethod({
type: 'WebRTC',
methodName: 'navigator.mediaDevices.enumerateDevices'
})
if (chrome.contentSettings.torEnabled == 'block') {
blockWebRTC()
}
4 changes: 4 additions & 0 deletions js/state/contentSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ const getDefaultUserPrefContentSettings = (braveryDefaults, appSettings, appConf
setting: 'block',
primaryPattern: '*'
}],
torEnabled: [{ // set to 'block' when in a Tor tab
setting: 'allow',
primaryPattern: '*'
}],
dappDetection: [{
setting: getSetting(settings.METAMASK_PROMPT_DISMISSED) || getSetting(settings.METAMASK_ENABLED) ? 'block' : 'allow',
primaryPattern: '*'
Expand Down
1 change: 1 addition & 0 deletions js/state/userPrefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ module.exports.setUserPref = (path, value, incognito = false) => {
newValue = Object.assign({}, value, {
flashEnabled: [blockContentSetting],
flashAllowed: [blockContentSetting],
torEnabled: [blockContentSetting], // currently only used for webrtc blocking
plugins: [blockContentSetting]
})
}
Expand Down

0 comments on commit 051109c

Please sign in to comment.