Report resources blocked by csp= option in logger

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/552
This commit is contained in:
Raymond Hill 2019-05-11 10:40:34 -04:00
parent 12bdd01595
commit 915c1f1f3c
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
7 changed files with 178 additions and 90 deletions

View file

@ -183,6 +183,10 @@ const µBlock = (function() { // jshint ignore:line
epickerEprom: null,
scriptlets: {},
cspNoInlineScript: "script-src 'unsafe-eval' * blob: data:",
cspNoScripting: 'script-src http: https:',
cspNoInlineFont: 'font-src *',
};
})();

View file

@ -197,6 +197,72 @@ vAPI.SafeAnimationFrame.prototype = {
/******************************************************************************/
/******************************************************************************/
// https://github.com/uBlockOrigin/uBlock-issues/issues/552
// Listen and report CSP violations so that blocked resources through CSP
// are properly reported in the logger.
{
const events = new Set();
let timer;
const send = function() {
vAPI.messaging.send(
'scriptlets',
{
what: 'securityPolicyViolation',
type: 'net',
docURL: document.location.href,
violations: Array.from(events),
},
response => {
if ( response === true ) { return; }
stop();
}
);
events.clear();
};
const sendAsync = function() {
if ( timer !== undefined ) { return; }
timer = self.requestIdleCallback(
( ) => { timer = undefined; send(); },
{ timeout: 2000 }
);
};
const listener = function(ev) {
if ( ev.isTrusted !== true ) { return; }
if ( ev.disposition !== 'enforce' ) { return; }
events.add(JSON.stringify({
url: ev.blockedURL || ev.blockedURI,
policy: ev.originalPolicy,
directive: ev.effectiveDirective || ev.violatedDirective,
}));
sendAsync();
};
const stop = function() {
events.clear();
if ( timer !== undefined ) {
self.cancelIdleCallback(timer);
timer = undefined;
}
document.removeEventListener('securitypolicyviolation', listener);
vAPI.shutdown.remove(stop);
};
document.addEventListener('securitypolicyviolation', listener);
vAPI.shutdown.add(stop);
// We need to call at least once to find out whether we really need to
// listen to CSP violations.
sendAsync();
}
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
vAPI.domWatcher = (function() {
const addedNodeLists = [];
@ -343,7 +409,7 @@ vAPI.domWatcher = (function() {
/******************************************************************************/
/******************************************************************************/
vAPI.matchesProp = (function() {
vAPI.matchesProp = (( ) => {
const docElem = document.documentElement;
if ( typeof docElem.matches !== 'function' ) {
if ( typeof docElem.mozMatchesSelector === 'function' ) {

View file

@ -567,11 +567,8 @@ var onMessage = function(request, sender, callback) {
if ( µb.canInjectScriptletsNow === false ) {
response.scriptlets = µb.scriptletFilteringEngine.retrieve(request);
}
if ( µb.logger.enabled ) {
if ( response.noCosmeticFiltering !== true ) {
µb.logCosmeticFilters(tabId, frameId);
}
µb.logInlineScript(tabId, frameId);
if ( µb.logger.enabled && response.noCosmeticFiltering !== true ) {
µb.logCosmeticFilters(tabId, frameId);
}
break;
@ -1362,7 +1359,98 @@ const logCosmeticFilters = function(tabId, details) {
/******************************************************************************/
var onMessage = function(request, sender, callback) {
const logCSPViolations = function(pageStore, request) {
if ( µb.logger.enabled === false || pageStore === null ) {
return false;
}
if ( request.violations.length === 0 ) {
return true;
}
const fctxt = µb.filteringContext.duplicate();
fctxt.fromTabId(pageStore.tabId)
.setRealm('network')
.setDocOriginFromURL(request.docURL)
.setURL(request.docURL);
let cspData = pageStore.extraData.get('cspData');
if ( cspData === undefined ) {
cspData = new Map();
const policies = [];
const logData = [];
µb.staticNetFilteringEngine.matchAndFetchData(
'csp',
request.docURL,
policies,
logData
);
for ( let i = 0; i < policies.length; i++ ) {
cspData.set(policies[i], logData[i]);
}
fctxt.type = 'inline-script';
fctxt.filter = undefined;
if ( pageStore.filterRequest(fctxt) === 1 ) {
cspData.set(µb.cspNoInlineScript, fctxt.filter);
}
fctxt.type = 'script';
fctxt.filter = undefined;
if ( pageStore.filterScripting(fctxt, true) === 1 ) {
cspData.set(µb.cspNoScripting, fctxt.filter);
}
fctxt.type = 'inline-font';
fctxt.filter = undefined;
if ( pageStore.filterRequest(fctxt) === 1 ) {
cspData.set(µb.cspNoInlineFont, fctxt.filter);
}
if ( cspData.size === 0 ) { return false; }
pageStore.extraData.set('cspData', cspData);
}
const typeMap = logCSPViolations.policyDirectiveToTypeMap;
for ( const json of request.violations ) {
const violation = JSON.parse(json);
let type = typeMap.get(violation.directive);
if ( type === undefined ) { continue; }
const logData = cspData.get(violation.policy);
if ( logData === undefined ) { continue; }
if ( /^[\w.+-]+:\/\//.test(violation.url) === false ) {
violation.url = request.docURL;
if ( type === 'script' ) { type = 'inline-script'; }
else if ( type === 'font' ) { type = 'inline-font'; }
}
fctxt.setURL(violation.url)
.setType(type)
.setFilter(logData)
.toLogger();
}
return true;
};
logCSPViolations.policyDirectiveToTypeMap = new Map([
[ 'img-src', 'image' ],
[ 'connect-src', 'xmlhttprequest' ],
[ 'font-src', 'font' ],
[ 'frame-src', 'sub_frame' ],
[ 'media-src', 'media' ],
[ 'object-src', 'object' ],
[ 'script-src', 'script' ],
[ 'script-src-attr', 'script' ],
[ 'script-src-elem', 'script' ],
[ 'style-src', 'stylesheet' ],
[ 'style-src-attr', 'stylesheet' ],
[ 'style-src-elem', 'stylesheet' ],
]);
/******************************************************************************/
const onMessage = function(request, sender, callback) {
const tabId = sender && sender.tab ? sender.tab.id : 0;
const pageStore = µb.pageStoreFromTabId(tabId);
@ -1373,7 +1461,7 @@ var onMessage = function(request, sender, callback) {
}
// Sync
var response;
let response;
switch ( request.what ) {
case 'domSurveyTransientReport':
@ -1411,6 +1499,10 @@ var onMessage = function(request, sender, callback) {
logCosmeticFilters(tabId, request);
break;
case 'securityPolicyViolation':
response = logCSPViolations(pageStore, request);
break;
case 'temporarilyAllowLargeMediaElement':
if ( pageStore !== null ) {
pageStore.allowLargeMediaElementsUntil = Date.now() + 2000;

View file

@ -221,11 +221,12 @@ const pageStoreJunkyardMax = 10;
/******************************************************************************/
const PageStore = function(tabId, context) {
this.init(tabId, context);
this.extraData = new Map();
this.journal = [];
this.journalTimer = null;
this.journalLastCommitted = this.journalLastUncommitted = undefined;
this.journalLastUncommittedURL = undefined;
this.init(tabId, context);
};
/******************************************************************************/
@ -276,6 +277,7 @@ PageStore.prototype.init = function(tabId, context) {
this.largeMediaTimer = null;
this.netFilteringCache = NetFilteringResultCache.factory();
this.internalRedirectionCount = 0;
this.extraData.clear();
// The current filtering context is cloned because:
// - We may be called with or without the current context having been

View file

@ -1,68 +0,0 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2018-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
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/uBlock
*/
'use strict';
/******************************************************************************/
// The purpose is to find out whether the current document make use of
// inline script tags, and if so report to the main process for logging
// purpose.
(function() {
if ( typeof vAPI !== 'object' ) { return; }
if (
document.querySelector('script:not([src])') === null &&
document.querySelector('a[href^="javascript:"]') === null &&
document.querySelector('[onabort],[onblur],[oncancel],[oncanplay],[oncanplaythrough],[onchange],[onclick],[onclose],[oncontextmenu],[oncuechange],[ondblclick],[ondrag],[ondragend],[ondragenter],[ondragexit],[ondragleave],[ondragover],[ondragstart],[ondrop],[ondurationchange],[onemptied],[onended],[onerror],[onfocus],[oninput],[oninvalid],[onkeydown],[onkeypress],[onkeyup],[onload],[onloadeddata],[onloadedmetadata],[onloadstart],[onmousedown],[onmouseenter],[onmouseleave],[onmousemove],[onmouseout],[onmouseover],[onmouseup],[onwheel],[onpause],[onplay],[onplaying],[onprogress],[onratechange],[onreset],[onresize],[onscroll],[onseeked],[onseeking],[onselect],[onshow],[onstalled],[onsubmit],[onsuspend],[ontimeupdate],[ontoggle],[onvolumechange],[onwaiting],[onafterprint],[onbeforeprint],[onbeforeunload],[onhashchange],[onlanguagechange],[onmessage],[onoffline],[ononline],[onpagehide],[onpageshow],[onrejectionhandled],[onpopstate],[onstorage],[onunhandledrejection],[onunload],[oncopy],[oncut],[onpaste]') === null
) {
return;
}
vAPI.messaging.send('scriptlets', {
what: 'inlinescriptFound',
docURL: window.location.href
});
})();
/*******************************************************************************
DO NOT:
- Remove the following code
- Add code beyond the following code
Reason:
- https://github.com/gorhill/uBlock/pull/3721
- uBO never uses the return value from injected content scripts
**/
void 0;

View file

@ -773,7 +773,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
const builtinDirectives = [];
if ( pageStore.filterScripting(fctxt, true) === 1 ) {
builtinDirectives.push("script-src http: https:");
builtinDirectives.push(µBlock.cspNoScripting);
if ( loggerEnabled ) {
fctxt.setRealm('network').setType('scripting').toLogger();
}
@ -788,9 +788,9 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
fctxt2.setDocOriginFromURL(fctxt.url);
const result = pageStore.filterRequest(fctxt2);
if ( result === 1 ) {
builtinDirectives.push("script-src 'unsafe-eval' * blob: data:");
builtinDirectives.push(µBlock.cspNoInlineScript);
}
if ( result !== 0 && loggerEnabled ) {
if ( result === 2 && loggerEnabled ) {
fctxt2.setRealm('network').toLogger();
}
}
@ -799,14 +799,14 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
// - Use a CSP to also forbid inline fonts if remote fonts are blocked.
fctxt.type = 'inline-font';
if ( pageStore.filterRequest(fctxt) === 1 ) {
builtinDirectives.push('font-src *');
builtinDirectives.push(µBlock.cspNoInlineFont);
if ( loggerEnabled ) {
fctxt.setRealm('network').toLogger();
}
}
if ( builtinDirectives.length !== 0 ) {
cspSubsets[0] = builtinDirectives.join('; ');
cspSubsets[0] = builtinDirectives.join(', ');
}
// ======== filter-based policies

View file

@ -555,14 +555,6 @@ var matchBucket = function(url, hostname, bucket, start) {
});
};
µBlock.logInlineScript = function(tabId, frameId) {
vAPI.tabs.injectScript(tabId, {
frameId: frameId,
file: '/js/scriptlets/inlinescript-logger.js',
runAt: 'document_end'
});
};
/******************************************************************************/
µBlock.scriptlets = (function() {