From 72201527d36830ba1a08e21c04a2a80951105220 Mon Sep 17 00:00:00 2001 From: gorhill Date: Mon, 26 Oct 2015 11:23:56 -0400 Subject: [PATCH] hardening against bad regexes --- src/js/cosmetic-filtering.js | 65 +++++++++++++++++++++++++--------- src/js/static-net-filtering.js | 18 ++++++++++ 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 3ef45d4be..a3b549788 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -39,6 +39,16 @@ var µb = µBlock; var encode = JSON.stringify; var decode = JSON.parse; +var isBadRegex = function(s) { + try { + void new RegExp(s); + } catch (ex) { + isBadRegex.message = ex.toString(); + return true; + } + return false; +}; + /******************************************************************************/ /* var histogram = function(label, buckets) { @@ -252,11 +262,11 @@ FilterParser.prototype.reset = function() { /******************************************************************************/ -FilterParser.prototype.parse = function(s) { +FilterParser.prototype.parse = function(raw) { // important! this.reset(); - var matches = this.reParser.exec(s); + var matches = this.reParser.exec(raw); if ( matches === null || matches.length !== 4 ) { this.cosmetic = false; return this; @@ -301,20 +311,43 @@ FilterParser.prototype.parse = function(s) { // Examples: // focus.de##script:contains(/uabInject/) // focus.de##script:contains(uabInject) - if ( this.suffix.charAt(0) === 's' && this.reScriptContains.test(this.suffix) ) { - // Currently supported only as non-generic selector. Also, exception - // script tag filter makes no sense, ignore. - if ( this.hostnames.length === 0 || this.unhide === 1 ) { - this.invalid = true; - return this; - } - var suffix = this.suffix; - this.suffix = 'script//:'; - if ( suffix.charAt(16) !== '/' || suffix.slice(-2) !== '/)' ) { - this.suffix += suffix.slice(16, -1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } else { - this.suffix += suffix.slice(17, -2).replace(/\\/g, '\\'); - } + + // Inline script tag filter? + if ( + this.suffix.charAt(0) !== 's' || + this.reScriptContains.test(this.suffix) === false ) + { + return this; + } + + // Currently supported only as non-generic selector. Also, exception + // script tag filter makes no sense, ignore. + if ( this.hostnames.length === 0 || this.unhide === 1 ) { + this.invalid = true; + return this; + } + + var suffix = this.suffix; + this.suffix = 'script//:'; + + // Plain string-based? + if ( suffix.charAt(16) !== '/' || suffix.slice(-2) !== '/)' ) { + this.suffix += suffix.slice(16, -1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return this; + } + + // Regex-based + this.suffix += suffix.slice(17, -2).replace(/\\/g, '\\'); + + // Valid regex? + if ( isBadRegex(this.suffix) ) { + console.error( + "uBlock Origin> discarding bad regular expression-based cosmetic filter '%s': '%s'", + raw, + isBadRegex.message + ); + this.invalid = true; + return this; } return this; diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 6bbf13a44..6ce4f14c6 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -176,6 +176,16 @@ var isFirstParty = function(firstPartyDomain, hostname) { return c === '.' || c === ''; }; +var isBadRegex = function(s) { + try { + void new RegExp(s); + } catch (ex) { + isBadRegex.message = ex.toString(); + return true; + } + return false; +}; + var alwaysTruePseudoRegex = { match: { '0': '', index: 0 }, exec: function(s) { @@ -1539,6 +1549,14 @@ FilterParser.prototype.parse = function(raw) { if ( s.charAt(0) === '/' && s.slice(-1) === '/' && s.length > 2 ) { this.isRegex = true; this.f = s.slice(1, -1); + if ( isBadRegex(this.f) ) { + console.error( + "uBlock Origin> discarding bad regular expression-based network filter '%s': '%s'", + raw, + isBadRegex.message + ); + this.unsupported = true; + } return this; }