Replace cookie-based hacks with synchronous messaging (currently shimmed) to retrieve fallback and per-tab restriction policies.

This commit is contained in:
hackademix 2019-09-29 00:50:43 +02:00
parent c3dcf300a6
commit fcd7c4aef0
4 changed files with 36 additions and 83 deletions

View File

@ -287,8 +287,12 @@ var RequestGuard = (() => {
if (policyType) { if (policyType) {
let {url, originUrl, documentUrl} = request; let {url, originUrl, documentUrl} = request;
let isFetch = "fetch" === policyType; let isFetch = "fetch" === policyType;
if ((isFetch || "frame" === policyType) && if ((isFetch || "frame" === policyType) &&
(((isFetch && !originUrl || url === originUrl) && originUrl === documentUrl (((isFetch && (!originUrl ||
browser.runtime.onSyncMessage &&
url.includes(browser.runtime.onSyncMessage.ENDPOINT_PREFIX)
) || url === originUrl) && originUrl === documentUrl
// some extensions make them both undefined, // some extensions make them both undefined,
// see https://github.com/eight04/image-picka/issues/150 // see https://github.com/eight04/image-picka/issues/150
) || ) ||

View File

@ -141,24 +141,26 @@
}, },
fetchChildPolicy({url, contextUrl}, sender) { fetchChildPolicy({url, contextUrl}, sender) {
if (!url) url = sender.url;
let {tab} = sender; let {tab} = sender;
let tabUrl = tab.url; let topUrl = tab.url || TabCache.get(tab.id);
if (!contextUrl) contextUrl = tabUrl;
let policy = !Sites.isInternal(url) && ns.isEnforced(tab.id) let policy = !Sites.isInternal(url) && ns.isEnforced(tab.id)
? ns.policy : null; ? ns.policy : null;
let permissions = Permissions.ALL; let permissions, unrestricted, cascaded;
if (policy) { if (policy) {
let perms = policy.get(url, contextUrl).perms; let perms = policy.get(url, contextUrl).perms;
if (tabUrl && ns.sync.cascadeRestrictions) { cascaded = ns.sync.cascadeRestrictions;
perms = policy.cascadeRestrictions(perms, tabUrl); if (topUrl && cascaded) {
perms = policy.cascadeRestrictions(perms, topUrl);
} }
permissions = perms.dry(); permissions = perms.dry();
} // otherwise either internal URL or unrestricted } else {
// otherwise either internal URL or unrestricted
return {permissions}; permissions = new Permissions(Permissions.ALL);
unrestricted = true;
cascaded = false;
}
return {permissions, unrestricted, cascaded};
}, },
async openStandalonePopup() { async openStandalonePopup() {

View File

@ -58,10 +58,6 @@ var notifyPage = async () => {
debug("Page %s shown, %s", document.URL, document.readyState); debug("Page %s shown, %s", document.URL, document.readyState);
if (document.readyState === "complete") { if (document.readyState === "complete") {
try { try {
if (!("canScript" in ns)) {
ns.fetchPolicy();
return;
}
await Messages.send("pageshow", {seen: seen.list, canScript: ns.canScript}); await Messages.send("pageshow", {seen: seen.list, canScript: ns.canScript});
return true; return true;
} catch (e) { } catch (e) {
@ -74,8 +70,6 @@ var notifyPage = async () => {
return false; return false;
} }
notifyPage();
window.addEventListener("pageshow", notifyPage); window.addEventListener("pageshow", notifyPage);
ns.on("capabilities", () => { ns.on("capabilities", () => {
@ -105,3 +99,6 @@ ns.on("capabilities", () => {
notifyPage(); notifyPage();
}); });
ns.fetchPolicy();
notifyPage();

View File

@ -33,78 +33,27 @@
backlog.add(eventName); backlog.add(eventName);
}, },
async fetchPolicy() { fetchPolicy() {
let policy = await Messages.send("fetchChildPolicy", {url: document.URL}); let url = document.URL;
let policy = browser.runtime.sendSyncMessage(
{id: "fetchPolicy", url, contextUrl: document.URL});
if (!policy) { if (!policy) {
debug(`No answer to fetchChildPolicy message. This should not be happening.`); debug(`No answer to fetchPolicy message. This should not be happening.`);
return false; return false;
} }
this.setup(policy.permissions, policy.MARKER, true); this.setup(policy);
return true; return true;
}, },
setup(permissions, MARKER, fetched = false) { setup(policy) {
this.config.permissions = permissions; this.policy = policy;
// ugly hack: since now we use registerContentScript instead of the if (!policy.permissions || policy.unrestricted) {
// filterRequest dynamic script injection hack, we use a session cookie
// to store per-tab information, erasing it as soon as we see it
// (before any content can access it)
let checkUnrestricted = challenge => sha256(`${MARKER}:${challenge}`);
if ((this.config.MARKER = MARKER) && permissions) {
let cookieRx = new RegExp(`(?:^|;\\s*)(${MARKER}(?:_\\d+){2})=([^;]*)`);
let match = document.cookie.match(cookieRx);
if (match) {
let [cookie, cookieName, cookieValue] = match;
// delete cookie NOW
document.cookie = `${cookieName}=;expires=${new Date(Date.now() - 31536000000).toGMTString()}`;
try {
this.config.tabInfo = JSON.parse(decodeURIComponent(cookieValue));
} catch (e) {
error(e);
}
} else if (UA.isMozilla && window !== window.top) {
// The cookie hack won't work for non-HTTP subframes (issue #48),
// or the cookie might have been deleted in a race condition,
// so here we try to check the parent
let checkParent = null;
try {
checkParent = parent.wrappedJSObject.checkNoScriptUnrestricted;
} catch (e) {
// may throw a SecurityException for cross-origin wrappedJSObject access
}
if (typeof checkParent === "function") {
try {
let challenge = uuid();
let unrestricted = checkParent(challenge) === checkUnrestricted(challenge);
this.config.tabInfo = {unrestricted, inherited: true};
} catch (e) {
debug("Exception thrown while checking parent unrestricted tab marker. Something fishy going on...")
error(e);
}
}
}
}
if (!this.config.permissions || this.config.tabInfo.unrestricted) {
exportFunction(checkUnrestricted, window, {defineAs: "checkNoScriptUnrestricted"});
debug("%s is loading unrestricted by user's choice (%o).", document.URL, this.config);
this.allows = () => true; this.allows = () => true;
this.capabilities = Object.assign( this.capabilities = Object.assign(
new Set(["script"]), { has() { return true; } }); new Set(["script"]), { has() { return true; } });
} else { } else {
if (!fetched) { let perms = policy.permissions;
let hostname = window.location.hostname;
if (hostname && hostname.startsWith("[")) {
// WebExt match patterns don't seem to support IPV6 (Firefox 63)...
debug("Ignoring child policy setup parameters for IPV6 address %s, forcing IPC...", hostname);
this.fetchPolicy();
return;
}
}
let perms = this.config.permissions;
this.capabilities = new Set(perms.capabilities); this.capabilities = new Set(perms.capabilities);
new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument); new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument);
} }
@ -112,7 +61,8 @@
this.canScript = this.allows("script"); this.canScript = this.allows("script");
this.fire("capabilities"); this.fire("capabilities");
}, },
config: { permissions: null, tabInfo: {}, MARKER: "" },
policy: null,
allows(cap) { allows(cap) {
return this.capabilities && this.capabilities.has(cap); return this.capabilities && this.capabilities.has(cap);