From f075f96c587a69f612c21483ab30e8f9bf3a8d00 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 17 Sep 2018 07:38:28 -0400 Subject: [PATCH] fix #589: use DOM-based CSP directive (idea from https://github.com/hackademix/noscript/commit/6e80d3f130773fc9a9123c5c4c2e97d63e90fa2a) --- src/js/contentscript-no-inline-script.js | 62 ++++++++++++++++ src/js/contentscript-no-workers.js | 62 ++++++++++++++++ src/js/matrix.js | 42 ++++++++--- src/js/messaging.js | 9 ++- src/js/traffic.js | 93 +++++++++++++++++++++++- 5 files changed, 257 insertions(+), 11 deletions(-) create mode 100644 src/js/contentscript-no-inline-script.js create mode 100644 src/js/contentscript-no-workers.js diff --git a/src/js/contentscript-no-inline-script.js b/src/js/contentscript-no-inline-script.js new file mode 100644 index 0000000..746d744 --- /dev/null +++ b/src/js/contentscript-no-inline-script.js @@ -0,0 +1,62 @@ +/******************************************************************************* + + uMatrix - a Chromium browser extension to black/white list requests. + Copyright (C) 2018 Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uMatrix +*/ + +'use strict'; + +// The idea of using to enforce CSP directive has been +// borrowed from NoScript: +// https://github.com/hackademix/noscript/commit/6e80d3f130773fc9a9123c5c4c2e97d63e90fa2a + +(function() { + let html = document.documentElement; + if ( html instanceof HTMLElement === false ) { return; } + + let meta; + try { + meta = document.createElement('meta'); + } catch(ex) { + } + if ( meta === undefined ) { return; } + meta.setAttribute('http-equiv', 'content-security-policy'); + meta.setAttribute('content', "script-src 'unsafe-eval' blob: *"); + + // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-security-policy + // + // Only a head element can be parent: + // > If the meta element is not a child of a head element, return. + // + // The CSP directive is enforced as soon as the meta tag is inserted: + // > Enforce the policy policy. + let head = document.head, + parent = head; + if ( parent === null ) { + parent = document.createElement('head'); + html.appendChild(parent); + } + parent.appendChild(meta); + + // Restore DOM to its original state. + if ( head === null ) { + html.removeChild(parent); + } else { + parent.removeChild(meta); + } +})(); diff --git a/src/js/contentscript-no-workers.js b/src/js/contentscript-no-workers.js new file mode 100644 index 0000000..5f951a8 --- /dev/null +++ b/src/js/contentscript-no-workers.js @@ -0,0 +1,62 @@ +/******************************************************************************* + + uMatrix - a Chromium browser extension to black/white list requests. + Copyright (C) 2018 Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uMatrix +*/ + +'use strict'; + +// The idea of using to enforce CSP directive has been +// borrowed from NoScript: +// https://github.com/hackademix/noscript/commit/6e80d3f130773fc9a9123c5c4c2e97d63e90fa2a + +(function() { + let html = document.documentElement; + if ( html instanceof HTMLElement === false ) { return; } + + let meta; + try { + meta = document.createElement('meta'); + } catch(ex) { + } + if ( meta === undefined ) { return; } + meta.setAttribute('http-equiv', 'content-security-policy'); + meta.setAttribute('content', "worker-src 'none'"); + + // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-security-policy + // + // Only a head element can be parent: + // > If the meta element is not a child of a head element, return. + // + // The CSP directive is enforced as soon as the meta tag is inserted: + // > Enforce the policy policy. + let head = document.head, + parent = head; + if ( parent === null ) { + parent = document.createElement('head'); + html.appendChild(parent); + } + parent.appendChild(meta); + + // Restore DOM to its original state. + if ( head === null ) { + html.removeChild(parent); + } else { + parent.removeChild(meta); + } +})(); diff --git a/src/js/matrix.js b/src/js/matrix.js index 1472c1d..aaa223b 100644 --- a/src/js/matrix.js +++ b/src/js/matrix.js @@ -1,7 +1,7 @@ /******************************************************************************* - uMatrix - a Chromium browser extension to black/white list requests. - Copyright (C) 2014-2018 Raymond Hill + uMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-present Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -199,6 +199,30 @@ Matrix.prototype.reset = function() { this.rules = new Map(); this.rootValue = Matrix.RedIndirect; this.modifiedTime = 0; + if ( this.modifyEventTimer !== undefined ) { + clearTimeout(this.modifyEventTimer); + } + this.modifyEventTimer = undefined; + this.modified(); +}; + +/******************************************************************************/ + +Matrix.prototype.modified = function() { + this.modifiedTime = Date.now(); + if ( this.modifyEventTimer !== undefined ) { return; } + this.modifyEventTimer = vAPI.setTimeout( + ( ) => { + this.modifyEventTimer = undefined; + window.dispatchEvent( + new CustomEvent( + 'matrixRulesetChange', + { detail: this } + ) + ); + }, + 149 + ); }; /******************************************************************************/ @@ -242,7 +266,7 @@ Matrix.prototype.assign = function(other) { for ( entry of other.switches ) { this.switches.set(entry[0], entry[1]); } - this.modifiedTime = other.modifiedTime; + this.modified(); return this; }; @@ -268,7 +292,7 @@ Matrix.prototype.setSwitch = function(switchName, srcHostname, newVal) { } else { this.switches.set(srcHostname, bits); } - this.modifiedTime = Date.now(); + this.modified(); return true; }; @@ -290,7 +314,7 @@ Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) { } else { this.rules.set(k, newBitmap); } - this.modifiedTime = Date.now(); + this.modified(); return true; }; @@ -531,7 +555,7 @@ Matrix.prototype.setSwitchZ = function(switchName, srcHostname, newState) { } else { this.switches.set(srcHostname, bits); } - this.modifiedTime = Date.now(); + this.modified(); state = this.evaluateSwitchZ(switchName, srcHostname); if ( state === newState ) { return true; @@ -645,7 +669,7 @@ Matrix.prototype.fromArray = function(lines, append) { if ( append !== true ) { this.assign(matrix); } - this.modifiedTime = Date.now(); + this.modified(); }; Matrix.prototype.toArray = function() { @@ -707,7 +731,7 @@ Matrix.prototype.fromString = function(text, append) { this.assign(matrix); } - this.modifiedTime = Date.now(); + this.modified(); }; Matrix.prototype.toString = function() { @@ -750,7 +774,7 @@ Matrix.prototype.fromSelfie = function(selfie) { if ( selfie.version !== selfieVersion ) { return false; } this.switches = new Map(selfie.switches); this.rules = new Map(selfie.rules); - this.modifiedTime = Date.now(); + this.modified(); return true; }; diff --git a/src/js/messaging.js b/src/js/messaging.js index 34ddf3e..9688d71 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1,7 +1,7 @@ /******************************************************************************* uMatrix - a browser extension to black/white list requests. - Copyright (C) 2014-2018 Raymond Hill + Copyright (C) 2014-present Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -229,6 +229,13 @@ var matrixSnapshot = function(pageStore, details) { let µmuri = µm.URI; let anyIndex = headerIndices.get('*'); + // Ensure that the current scope is also reported in the matrix. This may + // not be the case for documents which are fetched without going through + // our webRequest listener (ex. `file:`). + if ( pageStore.hostnameTypeCells.has(r.hostname + ' doc') === false ) { + pageStore.hostnameTypeCells.set(r.hostname + ' doc', new Set([ 0 ])); + } + for ( let entry of pageStore.hostnameTypeCells ) { let pos = entry[0].indexOf(' '); let reqHostname = entry[0].slice(0, pos); diff --git a/src/js/traffic.js b/src/js/traffic.js index eb62c50..461f575 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -1,7 +1,7 @@ /******************************************************************************* uMatrix - a browser extension to black/white list requests. - Copyright (C) 2014-2018 Raymond Hill + Copyright (C) 2014-present Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -483,6 +483,97 @@ vAPI.net.onHeadersReceived = { callback: onHeadersReceived }; +/******************************************************************************* + + Use a `http-equiv` `meta` tag to enforce CSP directives for documents + which protocol is `file:` (which do not cause our webRequest.onHeadersReceived + handler to be called). + + Idea borrowed from NoScript: + https://github.com/hackademix/noscript/commit/6e80d3f130773fc9a9123c5c4c2e97d63e90fa2a + +**/ + +(function() { + if ( + typeof self.browser !== 'object' || + typeof browser.contentScripts !== 'object' + ) { + return; + } + + let csRules = [ + { + name: 'script', + file: '/js/contentscript-no-inline-script.js', + pending: undefined, + registered: undefined, + mustRegister: false + }, + ]; + + let csSwitches = [ + { + name: 'no-workers', + file: '/js/contentscript-no-workers.js', + pending: undefined, + registered: undefined, + mustRegister: false + }, + ]; + + let register = function(entry) { + if ( entry.pending !== undefined ) { return; } + entry.pending = browser.contentScripts.register({ + js: [ { file: entry.file } ], + matches: [ 'file:///*' ], + runAt: 'document_start' + }).then( + result => { + if ( entry.mustRegister ) { + entry.registered = result; + } + entry.pending = undefined; + }, + ( ) => { + entry.registered = undefined; + entry.pending = undefined; + } + ); + }; + + let unregister = function(entry) { + if ( entry.registered === undefined ) { return; } + entry.registered.unregister(); + entry.registered = undefined; + }; + + let handler = function(ev) { + let matrix = ev && ev.detail; + if ( matrix !== µMatrix.tMatrix ) { return; } + for ( let cs of csRules ) { + cs.mustRegister = matrix.mustBlock('file-scheme', 'file-scheme', cs.name); + if ( cs.mustRegister === (cs.registered !== undefined) ) { continue; } + if ( cs.mustRegister ) { + register(cs); + } else { + unregister(cs); + } + } + for ( let cs of csSwitches ) { + cs.mustRegister = matrix.evaluateSwitchZ(cs.name, 'file-scheme'); + if ( cs.mustRegister === (cs.registered !== undefined) ) { continue; } + if ( cs.mustRegister ) { + register(cs); + } else { + unregister(cs); + } + } + }; + + window.addEventListener('matrixRulesetChange', handler); +})(); + /******************************************************************************/ var start = function() {