From eb7bdba47faaa1cc6e39bd076a82972749a0fe1a Mon Sep 17 00:00:00 2001 From: gorhill Date: Wed, 18 Oct 2017 15:00:22 -0400 Subject: [PATCH] fix #3140 --- platform/chromium/vapi-webrequest.js | 23 ++++++- platform/webext/vapi-webrequest.js | 49 +++++++++----- src/js/pagestore.js | 15 ++++- src/js/traffic.js | 95 ++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 18 deletions(-) diff --git a/platform/chromium/vapi-webrequest.js b/platform/chromium/vapi-webrequest.js index ea9a5e139..49a87e3e2 100644 --- a/platform/chromium/vapi-webrequest.js +++ b/platform/chromium/vapi-webrequest.js @@ -25,7 +25,12 @@ /******************************************************************************/ -vAPI.net = {}; +vAPI.net = { + onBeforeRequest: {}, + onBeforeMaybeSpuriousCSPReport: {}, + onHeadersReceived: {}, + nativeCSPReportFiltering: false +}; vAPI.net.registerListeners = function() { @@ -286,6 +291,22 @@ vAPI.net.registerListeners = function() { ); } + // https://github.com/gorhill/uBlock/issues/3140 + this.nativeCSPReportFiltering = validTypes.csp_report; + if ( + this.nativeCSPReportFiltering && + typeof this.onBeforeMaybeSpuriousCSPReport.callback === 'function' + ) { + wrApi.onBeforeRequest.addListener( + this.onBeforeMaybeSpuriousCSPReport.callback, + { + urls: [ 'http://*/*', 'https://*/*' ], + types: [ 'csp_report' ] + }, + [ 'blocking', 'requestBody' ] + ); + } + // Chromium 48 and lower does not support `ping` type. // Chromium 56 and higher does support `csp_report` stype. if ( onBeforeSendHeaders ) { diff --git a/platform/webext/vapi-webrequest.js b/platform/webext/vapi-webrequest.js index 2bb53e980..2caeb33e7 100644 --- a/platform/webext/vapi-webrequest.js +++ b/platform/webext/vapi-webrequest.js @@ -25,7 +25,14 @@ /******************************************************************************/ -vAPI.net = {}; +vAPI.net = { + onBeforeRequest: {}, + onBeforeMaybeSpuriousCSPReport: {}, + onHeadersReceived: {}, + nativeCSPReportFiltering: true +}; + +/******************************************************************************/ vAPI.net.registerListeners = function() { @@ -129,20 +136,6 @@ vAPI.net.registerListeners = function() { return onBeforeRequestClient(details); }; - var onHeadersReceivedClient = this.onHeadersReceived.callback, - onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0), - onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); - var onHeadersReceived = function(details) { - normalizeRequestDetails(details); - if ( - onHeadersReceivedClientTypes.length !== 0 && - onHeadersReceivedClientTypes.indexOf(details.type) === -1 - ) { - return; - } - return onHeadersReceivedClient(details); - }; - if ( onBeforeRequest ) { let urls = this.onBeforeRequest.urls || ['']; let types = this.onBeforeRequest.types || undefined; @@ -165,6 +158,32 @@ vAPI.net.registerListeners = function() { ); } + // https://github.com/gorhill/uBlock/issues/3140 + if ( typeof this.onBeforeMaybeSpuriousCSPReport.callback === 'function' ) { + wrApi.onBeforeRequest.addListener( + this.onBeforeMaybeSpuriousCSPReport.callback, + { + urls: [ 'http://*/*', 'https://*/*' ], + types: [ 'csp_report' ] + }, + [ 'blocking', 'requestBody' ] + ); + } + + var onHeadersReceivedClient = this.onHeadersReceived.callback, + onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0), + onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); + var onHeadersReceived = function(details) { + normalizeRequestDetails(details); + if ( + onHeadersReceivedClientTypes.length !== 0 && + onHeadersReceivedClientTypes.indexOf(details.type) === -1 + ) { + return; + } + return onHeadersReceivedClient(details); + }; + if ( onHeadersReceived ) { let urls = this.onHeadersReceived.urls || ['']; let types = onHeadersReceivedTypes; diff --git a/src/js/pagestore.js b/src/js/pagestore.js index cbac482ea..50b70604f 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -589,15 +589,26 @@ PageStore.prototype.filterRequest = function(context) { var requestType = context.requestType; - if ( requestType === 'csp_report' ) { + // https://github.com/gorhill/uBlock/issues/3140 + // Special handling of CSP reports if and only if these can't be filtered + // natively. + if ( + requestType === 'csp_report' && + vAPI.net.nativeCSPReportFiltering !== true + ) { if ( this.internalRedirectionCount !== 0 ) { if ( µb.logger.isEnabled() ) { - this.logData = { result: 1, source: 'global', raw: 'no-spurious-csp-report' }; + this.logData = { + result: 1, + source: 'global', + raw: 'no-spurious-csp-report' + }; } return 1; } } + if ( requestType.endsWith('font') ) { if ( requestType === 'font' ) { this.remoteFontCount += 1; diff --git a/src/js/traffic.js b/src/js/traffic.js index 9519502d2..961e0d3ff 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -382,6 +382,97 @@ var onBeforeBehindTheSceneRequest = function(details) { /******************************************************************************/ +// https://github.com/gorhill/uBlock/issues/3140 + +var onBeforeMaybeSpuriousCSPReport = function(details) { + var tabId = details.tabId; + + // Ignore behind-the-scene requests. + if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } + + // Lookup the page store associated with this tab id. + var µb = µBlock, + pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore === null ) { return; } + + // If uBO is disabled for the page, it can't possibly causes CSP reports + // to be triggered. + if ( pageStore.getNetFilteringSwitch() === false ) { return; } + + // A resource was redirected to a neutered one? + // TODO: mind injected scripts/styles as well. + if ( pageStore.internalRedirectionCount === 0 ) { return; } + + var textDecoder = onBeforeMaybeSpuriousCSPReport.textDecoder; + if ( + textDecoder === undefined && + typeof self.TextDecoder === 'function' + ) { + textDecoder = + onBeforeMaybeSpuriousCSPReport.textDecoder = new TextDecoder(); + } + + // Find out whether the CSP report is a potentially spurious CSP report. + // If from this point on we are unable to parse the CSP report data, the + // safest assumption to protect users is to assume the CSP report is + // spurious. + if ( + textDecoder !== undefined && + details.method === 'POST' + ) { + var raw = details.requestBody && details.requestBody.raw; + if ( + Array.isArray(raw) && + raw.length !== 0 && + raw[0] instanceof Object && + raw[0].bytes instanceof ArrayBuffer + ) { + var data; + try { + data = JSON.parse(textDecoder.decode(raw[0].bytes)); + } catch (ex) { + } + if ( data instanceof Object ) { + var report = data['csp-report']; + if ( report instanceof Object ) { + var blockedURI = report['blocked-uri'] || + report['blockedURI'], + sourceFile = report['source-file'] || + report['sourceFile']; + if ( + (typeof blockedURI !== 'string' || + blockedURI.startsWith('data') === false) && + (typeof sourceFile !== 'string' || + sourceFile.startsWith('data') === false) + ) { + return; + } + } + } + } + } + + // Potentially spurious CSP report. + if ( µb.logger.isEnabled() ) { + var hostname = µb.URI.hostnameFromURI(details.url); + µb.logger.writeOne( + tabId, + 'net', + { result: 1, source: 'global', raw: 'no-spurious-csp-report' }, + 'csp_report', + details.url, + hostname, + hostname + ); + } + + return { cancel: true }; +}; + +onBeforeMaybeSpuriousCSPReport.textDecoder = undefined; + +/******************************************************************************/ + // To handle: // - inline script tags // - websockets @@ -647,6 +738,10 @@ vAPI.net.onBeforeRequest = { callback: onBeforeRequest }; +vAPI.net.onBeforeMaybeSpuriousCSPReport = { + callback: onBeforeMaybeSpuriousCSPReport +}; + vAPI.net.onHeadersReceived = { urls: [ 'http://*/*',