From 5c9c87e485308cb2dc6da102a0e20d8fce84124e Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 26 Mar 2023 12:31:36 -0400 Subject: [PATCH] Add ability for scriptlets to share local data Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/1741 As a result of the new capability, usage of RegExp API in `aost` scriptlet has been shielded from the webpage tampering with the API. --- assets/resources/scriptlets.js | 29 +++++++++++++++++++++++++++-- src/js/scriptlet-filtering.js | 22 ++++++++++------------ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index 2210f06cb..28e253d64 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -22,6 +22,9 @@ web page context. */ +// Externally added to the private namespace in which scriptlets execute. +/* global sriptletGlobals */ + 'use strict'; export const builtinScriptlets = []; @@ -34,6 +37,25 @@ export const builtinScriptlets = []; *******************************************************************************/ +builtinScriptlets.push({ + name: 'safe-self.fn', + fn: safeSelf, +}); +function safeSelf() { + if ( sriptletGlobals.has('safeSelf') ) { + return sriptletGlobals.get('safeSelf'); + } + const safe = { + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + }; + sriptletGlobals.set('safeSelf', safe); + return safe; +} + +/******************************************************************************/ + builtinScriptlets.push({ name: 'pattern-to-regex.fn', fn: patternToRegex, @@ -257,6 +279,7 @@ builtinScriptlets.push({ aliases: [ 'aost.js' ], fn: abortOnStackTrace, dependencies: [ + 'safe-self.fn', 'pattern-to-regex.fn', 'get-exception-token.fn', ], @@ -268,6 +291,7 @@ function abortOnStackTrace( logLevel = '' ) { if ( typeof chain !== 'string' ) { return; } + const safe = safeSelf(); const reNeedle = patternToRegex(needle); const exceptionToken = getExceptionToken(); const log = console.log.bind(console); @@ -279,11 +303,12 @@ function abortOnStackTrace( docURL = docURL.slice(0, pos); } // Normalize stack trace + const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/; const lines = []; for ( let line of err.stack.split(/[\n\r]+/) ) { if ( line.includes(exceptionToken) ) { continue; } line = line.trim(); - let match = /(.*?@)?(\S+)(:\d+):\d+\)?$/.exec(line); + let match = safe.RegExp_exec.call(reLine, line); if ( match === null ) { continue; } let url = match[2]; if ( url.startsWith('(') ) { url = url.slice(1); } @@ -301,7 +326,7 @@ function abortOnStackTrace( } lines[0] = `stackDepth:${lines.length-1}`; const stack = lines.join('\t'); - const r = reNeedle.test(stack); + const r = safe.RegExp_test.call(reNeedle, stack); if ( logLevel === '1' || logLevel === '2' && r || diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js index 1c4cb33fd..3cf05a5b3 100644 --- a/src/js/scriptlet-filtering.js +++ b/src/js/scriptlet-filtering.js @@ -379,22 +379,20 @@ scriptletFilteringEngine.retrieve = function(request, options = {}) { if ( cacheDetails.code === '' ) { return; } - const out = [ cacheDetails.code ]; - - if ( µb.hiddenSettings.debugScriptlets ) { - out.unshift('debugger;'); - } - - out.unshift( + const out = [ '(function() {', '// >>>> start of private namespace', - '' - ); - out.push( + '', + µb.hiddenSettings.debugScriptlets ? 'debugger;' : ';', + '', + // For use by scriptlets to share local data among themselves + 'const sriptletGlobals = new Map();', + '', + cacheDetails.code, '', '// <<<< end of private namespace', - '})();' - ); + '})();', + ]; return out.join('\n'); };