webNavigation.onCommitted + tabs.executeScript to deliver DOM policies earlier whenever possible.

This commit is contained in:
hackademix 2020-10-03 21:08:53 +02:00
parent 6a07c055e0
commit 9954bc1ec8
4 changed files with 121 additions and 60 deletions

View File

@ -549,6 +549,40 @@ var RequestGuard = (() => {
}
return ABORT;
}
async function onNavCommitted(details) {
debug("onNavCommitted", details);
let {url, tabId, frameId} = details;
try {
let policy = ns.computeChildPolicy({url}, {tab: {tabId}, frameId});
policy.navigationURL = url;
let ret = await browser.tabs.executeScript(details.tabId, {
code:
`{
let domPolicy = ${JSON.stringify(policy)};
if (this.ns) {
ns.domPolicy = domPolicy;
if (ns.setup) {
if (ns.syncSetup) ns.syncSetup(domPolicy);
else if (!ns.pendingSyncFetchPolicy) {
ns.setup(domPolicy);
}
} ;
} else {
ns = {domPolicy}
}
console.debug("domPolicy", domPolicy);
}
ns;`,
runAt: "document_start",
frameId,
});
debug("onNavCommitted return: ", ret);
} catch(e) {
console.error(e);
}
}
const RequestGuard = {
async start() {
Messages.addHandler(messageHandler);
@ -600,9 +634,11 @@ var RequestGuard = (() => {
wr.onBeforeRequest.addListener(onViolationReport,
{urls: [csp.reportURI], types: ["csp_report"]}, ["blocking", "requestBody"]);
}
browser.webNavigation.onCommitted.addListener(onNavCommitted);
TabStatus.probe();
},
stop() {
browser.webNavigation.onCommitted.removeListener(onNavCommitted);
let wr = browser.webRequest;
for (let [name, listener] of Object.entries(listeners)) {
if (typeof listener === "function") {

View File

@ -158,40 +158,7 @@
async fetchChildPolicy({url, contextUrl}, sender) {
await ns.initializing;
return (messageHandler.fetchChildPolicy =
messageHandler.fetchChildPolicySync)(...arguments);
},
fetchChildPolicySync({url, contextUrl}, sender) {
let {tab, frameId} = sender;
let policy = ns.policy;
if (!policy) {
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
return {
permissions: new Permissions(Permissions.DEFAULT).dry(),
unrestricted: false,
cascaded: false,
fallback: true
};
}
let topUrl = frameId === 0 ? contextUrl : tab && (tab.url || TabCache.get(tab.id));
if (Sites.isInternal(url) || !ns.isEnforced(tab ? tab.id : -1)) {
policy = null;
}
let permissions, unrestricted, cascaded;
if (policy) {
let perms = policy.get(url, contextUrl).perms;
cascaded = topUrl && ns.sync.cascadeRestrictions;
if (cascaded) {
perms = policy.cascadeRestrictions(perms, topUrl);
}
permissions = perms.dry();
} else {
// otherwise either internal URL or unrestricted
permissions = new Permissions(Permissions.ALL).dry();
unrestricted = true;
cascaded = false;
}
return {permissions, unrestricted, cascaded};
ns.computeChildPolicy)(...arguments);
},
async openStandalonePopup() {
@ -239,6 +206,40 @@
return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, request.documentURL);
},
computeChildPolicy({url, contextUrl}, sender) {
let {tab, frameId} = sender;
let policy = ns.policy;
if (!policy) {
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
return {
permissions: new Permissions(Permissions.DEFAULT).dry(),
unrestricted: false,
cascaded: false,
fallback: true
};
}
let topUrl = frameId === 0 ? contextUrl : tab && (tab.url || TabCache.get(tab.id));
if (Sites.isInternal(url) || !ns.isEnforced(tab ? tab.id : -1)) {
policy = null;
}
let permissions, unrestricted, cascaded;
if (policy) {
let perms = policy.get(url, contextUrl).perms;
cascaded = topUrl && ns.sync.cascadeRestrictions;
if (cascaded) {
perms = policy.cascadeRestrictions(perms, topUrl);
}
permissions = perms.dry();
} else {
// otherwise either internal URL or unrestricted
permissions = new Permissions(Permissions.ALL).dry();
unrestricted = true;
cascaded = false;
}
return {permissions, unrestricted, cascaded};
},
start() {
if (this.running) return;
this.running = true;

View File

@ -34,6 +34,7 @@
},
fetchPolicy() {
if (this.policy) return;
let url = document.URL;
debug(`Fetching policy from document %s, readyState %s`,
@ -41,6 +42,7 @@
//, document.domain, document.baseURI, window.isSecureContext // DEV_ONLY
);
if (/^(ftp|file):/.test(url)) { // ftp: or file: - no CSP headers yet
if (this.syncFetchPolicy) {
this.syncFetchPolicy();
@ -50,6 +52,15 @@
return;
}
} else {
if (this.domPolicy) {
debug("File policy set in webNavigation found!");
try {
this.setup(this.domPolicy);
return;
} catch(e) {
error(e);
}
}
// CSP headers have been already provided by webRequest, we are not in a hurry...
if (/^(javascript|about):/.test(url)) {
url = document.readyState === "loading"
@ -77,7 +88,8 @@
},
setup(policy) {
debug("%s, %s, %o", document.URL, document.readyState, policy);
if (this.policy) return false;
debug("%s, %s, fetched %o", document.URL, document.readyState, policy);
if (!policy) {
policy = {permissions: {capabilities: []}, localFallback: true};
}
@ -97,6 +109,7 @@
}
this.canScript = this.allows("script");
this.fire("capabilities");
return true;
},
policy: null,
@ -105,6 +118,6 @@
return this.capabilities && this.capabilities.has(cap);
},
};
this.ns = this.ns ? Object.assign(this.ns, ns) : ns;
this.ns = this.ns ? Object.assign(ns, this.ns) : ns;
debug("StaticNS", JSON.stringify(this.ns)); // DEV_ONLY
}

View File

@ -6,19 +6,43 @@
// Here we've got no CSP header yet (file: or ftp: URL), we need one
// injected in the DOM as soon as possible.
debug("No CSP yet for non-HTTP document load: fetching policy synchronously...");
debug("No CSP yet for non-HTTP document load: fetching policy synchronously...", ns);
ns.syncSetup = ns.setup.bind(ns);
if (top !== window) {
if (top.wrappedJSObject._noScriptPolicy) {
try {
ns.setup(JSON.parse(top.wrappedJSObject._noScriptPolicy));
} catch(e) {
error(e);
if (window.wrappedJSObject) {
if (top === window) {
ns.syncSetup = policy => {
if (!ns.setup(policy)) return;
if (top === window && window.wrappedJSObject) {
let persistentPolicy = JSON.stringify(policy);
Object.freeze(persistentPolicy);
try {
Object.defineProperty(window.wrappedJSObject, "_noScriptPolicy", {value: cloneInto(persistentPolicy, window)});
} catch(e) {
error(e);
}
}
ns.syncSetup = () => {};
};
} else try {
if (top.wrappedJSObject._noScriptPolicy) {
debug("Policy set in parent frame found!")
try {
ns.setup(JSON.parse(top.wrappedJSObject._noScriptPolicy));
return;
} catch(e) {
error(e);
}
}
return;
} catch (e) {
// cross-origin accesss violation, ignore
}
}
if (ns.domPolicy) {
ns.syncSetup(ns.domPolicy);
return;
}
let syncFetch = callback => {
browser.runtime.sendSyncMessage(
{id: "fetchPolicy", url, contextUrl: url},
@ -175,23 +199,10 @@
}
}
let setup = policy => {
debug("Fetched %o, readyState %s", policy, document.readyState); // DEV_ONLY
if (top === window) {
let persistentPolicy = JSON.stringify(policy);
Object.freeze(persistentPolicy);
try {
Object.defineProperty(window.wrappedJSObject, "_noScriptPolicy", {value: cloneInto(persistentPolicy, window)});
} catch(e) {
error(e);
}
}
ns.setup(policy);
}
for (let attempts = 3; attempts-- > 0;) {
try {
syncFetch(setup);
if (ns.policy) break;
syncFetch(ns.syncSetup);
break;
} catch (e) {
if (!Messages.isMissingEndpoint(e) || document.readyState === "complete") {