Skip to content

Commit

Permalink
Improve various scriptlets
Browse files Browse the repository at this point in the history
Specifically, improve proxying of native methods.
  • Loading branch information
gorhill committed Aug 16, 2024
1 parent 9ced01e commit 56dfdd2
Showing 1 changed file with 126 additions and 142 deletions.
268 changes: 126 additions & 142 deletions assets/resources/scriptlets.js
Original file line number Diff line number Diff line change
Expand Up @@ -1446,9 +1446,6 @@ function replaceFetchResponseFn(
builtinScriptlets.push({
name: 'proxy-apply.fn',
fn: proxyApplyFn,
dependencies: [
'safe-self.fn',
],
});
function proxyApplyFn(
target = '',
Expand All @@ -1465,12 +1462,28 @@ function proxyApplyFn(
}
const fn = context[prop];
if ( typeof fn !== 'function' ) { return; }
const fnname = fn.name;
const toString = (function toString() {
return `function ${fnname}() { [native code] }`;
}).bind(null);
if ( fn.prototype && fn.prototype.constructor === fn ) {
context[prop] = new Proxy(fn, { construct: handler });
return (...args) => { return Reflect.construct(...args); };
context[prop] = new Proxy(fn, {
construct: handler,
get(target, prop, receiver) {
if ( prop === 'toString' ) { return toString; }
return Reflect.get(target, prop, receiver);
},
});
return (...args) => Reflect.construct(...args);
}
context[prop] = new Proxy(fn, { apply: handler });
return (...args) => { return Reflect.apply(...args); };
context[prop] = new Proxy(fn, {
apply: handler,
get(target, prop, receiver) {
if ( prop === 'toString' ) { return toString; }
return Reflect.get(target, prop, receiver);
},
});
return (...args) => Reflect.apply(...args);
}

/*******************************************************************************
Expand Down Expand Up @@ -1676,6 +1689,7 @@ builtinScriptlets.push({
],
fn: addEventListenerDefuser,
dependencies: [
'proxy-apply.fn',
'run-at.fn',
'safe-self.fn',
'should-debug.fn',
Expand Down Expand Up @@ -1732,44 +1746,29 @@ function addEventListenerDefuser(
}
return matchesBoth;
};
const trapEddEventListeners = ( ) => {
const eventListenerHandler = {
apply: function(target, thisArg, args) {
let t, h;
try {
t = String(args[0]);
if ( typeof args[1] === 'function' ) {
h = String(safe.Function_toString(args[1]));
} else if ( typeof args[1] === 'object' && args[1] !== null ) {
if ( typeof args[1].handleEvent === 'function' ) {
h = String(safe.Function_toString(args[1].handleEvent));
}
} else {
h = String(args[1]);
runAt(( ) => {
proxyApplyFn('EventTarget.prototype.addEventListener', function(target, thisArg, args) {
let t, h;
try {
t = String(args[0]);
if ( typeof args[1] === 'function' ) {
h = String(safe.Function_toString(args[1]));
} else if ( typeof args[1] === 'object' && args[1] !== null ) {
if ( typeof args[1].handleEvent === 'function' ) {
h = String(safe.Function_toString(args[1].handleEvent));
}
} catch(ex) {
}
if ( type === '' && pattern === '' ) {
safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
} else if ( shouldPrevent(thisArg, t, h) ) {
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
}
return Reflect.apply(target, thisArg, args);
},
get(target, prop, receiver) {
if ( prop === 'toString' ) {
return target.toString.bind(target);
} else {
h = String(args[1]);
}
return Reflect.get(target, prop, receiver);
},
};
self.EventTarget.prototype.addEventListener = new Proxy(
self.EventTarget.prototype.addEventListener,
eventListenerHandler
);
};
runAt(( ) => {
trapEddEventListeners();
} catch(ex) {
}
if ( type === '' && pattern === '' ) {
safe.uboLog(logPrefix, `Called: ${t}\n${h}\n${elementDetails(thisArg)}`);
} else if ( shouldPrevent(thisArg, t, h) ) {
return safe.uboLog(logPrefix, `Prevented: ${t}\n${h}\n${elementDetails(thisArg)}`);
}
return Reflect.apply(target, thisArg, args);
});
}, extraArgs.runAt);
}

Expand Down Expand Up @@ -2475,6 +2474,7 @@ builtinScriptlets.push({
],
fn: noSetIntervalIf,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
Expand All @@ -2495,35 +2495,27 @@ function noSetIntervalIf(
delay = parseInt(delay, 10);
}
const reNeedle = safe.patternToRegex(needle);
self.setInterval = new Proxy(self.setInterval, {
apply: function(target, thisArg, args) {
const a = args[0] instanceof Function
? String(safe.Function_toString(args[0]))
: String(args[0]);
const b = args[1];
if ( needle === '' && delay === undefined ) {
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
return Reflect.apply(target, thisArg, args);
}
let defuse;
if ( needle !== '' ) {
defuse = reNeedle.test(a) !== needleNot;
}
if ( defuse !== false && delay !== undefined ) {
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
}
if ( defuse ) {
args[0] = function(){};
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
}
proxyApplyFn('setInterval', function setInterval(target, thisArg, args) {
const a = args[0] instanceof Function
? String(safe.Function_toString(args[0]))
: String(args[0]);
const b = args[1];
if ( needle === '' && delay === undefined ) {
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
return Reflect.apply(target, thisArg, args);
},
get(target, prop, receiver) {
if ( prop === 'toString' ) {
return target.toString.bind(target);
}
return Reflect.get(target, prop, receiver);
},
}
let defuse;
if ( needle !== '' ) {
defuse = reNeedle.test(a) !== needleNot;
}
if ( defuse !== false && delay !== undefined ) {
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
}
if ( defuse ) {
args[0] = function(){};
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
}
return Reflect.apply(target, thisArg, args);
});
}

Expand All @@ -2538,6 +2530,7 @@ builtinScriptlets.push({
],
fn: noSetTimeoutIf,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
Expand All @@ -2558,35 +2551,27 @@ function noSetTimeoutIf(
delay = parseInt(delay, 10);
}
const reNeedle = safe.patternToRegex(needle);
self.setTimeout = new Proxy(self.setTimeout, {
apply: function(target, thisArg, args) {
const a = args[0] instanceof Function
? String(safe.Function_toString(args[0]))
: String(args[0]);
const b = args[1];
if ( needle === '' && delay === undefined ) {
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
return Reflect.apply(target, thisArg, args);
}
let defuse;
if ( needle !== '' ) {
defuse = reNeedle.test(a) !== needleNot;
}
if ( defuse !== false && delay !== undefined ) {
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
}
if ( defuse ) {
args[0] = function(){};
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
}
proxyApplyFn('setTimeout', function setTimeout(target, thisArg, args) {
const a = args[0] instanceof Function
? String(safe.Function_toString(args[0]))
: String(args[0]);
const b = args[1];
if ( needle === '' && delay === undefined ) {
safe.uboLog(logPrefix, `Called:\n${a}\n${b}`);
return Reflect.apply(target, thisArg, args);
},
get(target, prop, receiver) {
if ( prop === 'toString' ) {
return target.toString.bind(target);
}
return Reflect.get(target, prop, receiver);
},
}
let defuse;
if ( needle !== '' ) {
defuse = reNeedle.test(a) !== needleNot;
}
if ( defuse !== false && delay !== undefined ) {
defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot;
}
if ( defuse ) {
args[0] = function(){};
safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
}
return Reflect.apply(target, thisArg, args);
});
}

Expand Down Expand Up @@ -2815,6 +2800,7 @@ builtinScriptlets.push({
],
fn: noWindowOpenIf,
dependencies: [
'proxy-apply.fn',
'safe-self.fn',
],
});
Expand Down Expand Up @@ -2845,51 +2831,49 @@ function noWindowOpenIf(
setTimeout(( ) => { decoyElem.remove(); }, autoRemoveAfter * 1000);
return decoyElem;
};
window.open = new Proxy(window.open, {
apply: function(target, thisArg, args) {
const haystack = args.join(' ');
if ( rePattern.test(haystack) !== targetMatchResult ) {
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Allowed (${args.join(', ')})`);
}
return Reflect.apply(target, thisArg, args);
}
safe.uboLog(logPrefix, `Prevented (${args.join(', ')})`);
if ( autoRemoveAfter < 0 ) { return null; }
const decoyElem = decoy === 'obj'
? createDecoy('object', 'data', ...args)
: createDecoy('iframe', 'src', ...args);
let popup = decoyElem.contentWindow;
if ( typeof popup === 'object' && popup !== null ) {
Object.defineProperty(popup, 'closed', { value: false });
} else {
const noopFunc = (function(){}).bind(self);
popup = new Proxy(self, {
get: function(target, prop) {
if ( prop === 'closed' ) { return false; }
const r = Reflect.get(...arguments);
if ( typeof r === 'function' ) { return noopFunc; }
return target[prop];
},
set: function() {
return Reflect.set(...arguments);
},
});
}
if ( safe.logLevel !== 0 ) {
popup = new Proxy(popup, {
get: function(target, prop) {
safe.uboLog(logPrefix, 'window.open / get', prop, '===', target[prop]);
return Reflect.get(...arguments);
},
set: function(target, prop, value) {
safe.uboLog(logPrefix, 'window.open / set', prop, '=', value);
return Reflect.set(...arguments);
},
});
proxyApplyFn('open', function open(target, thisArg, args) {
const haystack = args.join(' ');
if ( rePattern.test(haystack) !== targetMatchResult ) {
if ( safe.logLevel > 1 ) {
safe.uboLog(logPrefix, `Allowed (${args.join(', ')})`);
}
return popup;
return Reflect.apply(target, thisArg, args);
}
safe.uboLog(logPrefix, `Prevented (${args.join(', ')})`);
if ( autoRemoveAfter < 0 ) { return null; }
const decoyElem = decoy === 'obj'
? createDecoy('object', 'data', ...args)
: createDecoy('iframe', 'src', ...args);
let popup = decoyElem.contentWindow;
if ( typeof popup === 'object' && popup !== null ) {
Object.defineProperty(popup, 'closed', { value: false });
} else {
const noopFunc = function open(){};
popup = new Proxy(self, {
get: function(target, prop) {
if ( prop === 'closed' ) { return false; }
const r = Reflect.get(...arguments);
if ( typeof r === 'function' ) { return noopFunc; }
return target[prop];
},
set: function() {
return Reflect.set(...arguments);
},
});
}
if ( safe.logLevel !== 0 ) {
popup = new Proxy(popup, {
get: function(target, prop) {
safe.uboLog(logPrefix, 'window.open / get', prop, '===', target[prop]);
return Reflect.get(...arguments);
},
set: function(target, prop, value) {
safe.uboLog(logPrefix, 'window.open / set', prop, '=', value);
return Reflect.set(...arguments);
},
});
}
return popup;
});
}

Expand Down

5 comments on commit 56dfdd2

@anvakl
Copy link
Contributor

@anvakl anvakl commented on 56dfdd2 Aug 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:
In no-setTimeout-if.js and no-setInterval-if.js why don't we just return when defuse==true like we do in addEventListener-defuser.js instead of setting args[0] = function(){};? Right now the defused operation is wait and do nothing. I don't understand the point of waiting if we are going to do nothing i.e. function(){};

@gorhill
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caller expect a valid timer id as return value.

@anvakl
Copy link
Contributor

@anvakl anvakl commented on 56dfdd2 Aug 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make myself clear, I am suggesting changing

if ( defuse ) {
            args[0] = function(){};
            safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
        }

to

if ( defuse ) {
            return safe.uboLog(logPrefix, `Prevented:\n${a}\n${b}`);
        }

and you say this won't work.

@gorhill
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anvakl
Copy link
Contributor

@anvakl anvakl commented on 56dfdd2 Aug 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. I didn't know setTimeout had a return value timeoutId.

Please sign in to comment.