From d4a411dc54ba4202a92c3571a5ea7738bec58902 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 14 Sep 2018 19:20:09 -0400 Subject: [PATCH] Bug 1469714 - Part 11: Add a storage permission check to Document.hasStorageAccess() to ensure that it always returns the correct value even in the presence of dynamic heuristics grantic storage access permission; r=baku Differential Revision: https://phabricator.services.mozilla.com/D5923 --- dom/base/nsDocument.cpp | 30 +++ .../antitracking/test/browser/browser.ini | 1 + .../browser_storageAccessWithHeuristics.js | 198 ++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index cb18213bedcb..3a364c43ac0a 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -13564,6 +13564,29 @@ nsIDocument::HasStorageAccess(mozilla::ErrorResult& aRv) return promise.forget(); } + if (AntiTrackingCommon::ShouldHonorContentBlockingCookieRestrictions() && + StaticPrefs::network_cookie_cookieBehavior() == + nsICookieService::BEHAVIOR_REJECT_TRACKER) { + // If we need to abide by Content Blocking cookie restrictions, ensure to + // first do all of our storage access checks. If storage access isn't + // disabled in our document, given that we're a third-party, we must either + // not be a tracker, or be whitelisted for some reason (e.g. a storage + // access permission being granted). In that case, resolve the promise and + // say we have obtained storage access. + if (!nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) { + // Note, storage might be allowed because the top-level document is on + // the content blocking allowlist! In that case, don't provide special + // treatment here. + bool isOnAllowList = false; + if (NS_SUCCEEDED(AntiTrackingCommon::IsOnContentBlockingAllowList( + topLevelDoc->GetDocumentURI(), isOnAllowList)) && + !isOnAllowList) { + promise->MaybeResolve(true); + return promise.forget(); + } + } + } + nsPIDOMWindowInner* inner = GetInnerWindow(); nsGlobalWindowOuter* outer = nullptr; if (inner) { @@ -13667,6 +13690,13 @@ nsIDocument::RequestStorageAccess(mozilla::ErrorResult& aRv) nsICookieService::BEHAVIOR_REJECT_TRACKER) { // Only do something special for third-party tracking content. if (nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) { + // Note: If this has returned true, the top-level document is guaranteed + // to not be on the Content Blocking allow list. + DebugOnly isOnAllowList = false; + MOZ_ASSERT_IF(NS_SUCCEEDED(AntiTrackingCommon::IsOnContentBlockingAllowList( + parent->GetDocumentURI(), isOnAllowList)), + !isOnAllowList); + isTrackingWindow = true; // TODO: prompt for permission } diff --git a/toolkit/components/antitracking/test/browser/browser.ini b/toolkit/components/antitracking/test/browser/browser.ini index ed046884fd2c..b92f53506a0f 100644 --- a/toolkit/components/antitracking/test/browser/browser.ini +++ b/toolkit/components/antitracking/test/browser/browser.ini @@ -40,3 +40,4 @@ support-files = subResources.sjs support-files = tracker.js [browser_storageAccessPrivateWindow.js] [browser_storageAccessSandboxed.js] +[browser_storageAccessWithHeuristics.js] diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js b/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js new file mode 100644 index 000000000000..0df337d0f6ee --- /dev/null +++ b/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js @@ -0,0 +1,198 @@ +ChromeUtils.import("resource://gre/modules/Services.jsm"); + +add_task(async function() { + info("Starting subResources test"); + + await SpecialPowers.flushPrefEnv(); + await SpecialPowers.pushPrefEnv({"set": [ + ["dom.storage_access.enabled", true], + ["browser.contentblocking.enabled", true], + ["browser.contentblocking.ui.enabled", true], + ["browser.contentblocking.rejecttrackers.ui.enabled", true], + ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.pbmode.enabled", false], + ["privacy.trackingprotection.annotate_channels", true], + ]}); + + await UrlClassifierTestUtils.addTestTrackers(); +}); + +add_task(async function testWindowOpenHeuristic() { + info("Starting window.open() heuristic test..."); + + info("Creating a new tab"); + let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + info("Loading tracking scripts"); + await ContentTask.spawn(browser, { + page: TEST_3RD_PARTY_PAGE_WO, + }, async obj => { + let msg = {}; + msg.blockingCallback = (async _ => { + let hasAccess = await document.hasStorageAccess(); + ok(!hasAccess, "Doesn't yet have storage access"); + }).toString(); + + msg.nonBlockingCallback = (async _ => { + let hasAccess = await document.hasStorageAccess(); + ok(hasAccess, "Now obtained storage access"); + }).toString(); + + info("Checking if storage access is denied"); + await new content.Promise(resolve => { + let ifr = content.document.createElement("iframe"); + ifr.onload = function() { + info("Sending code to the 3rd party content"); + ifr.contentWindow.postMessage(msg, "*"); + }; + + content.addEventListener("message", function msg(event) { + if (event.data.type == "finish") { + content.removeEventListener("message", msg); + resolve(); + return; + } + + if (event.data.type == "ok") { + ok(event.data.what, event.data.msg); + return; + } + + if (event.data.type == "info") { + info(event.data.msg); + return; + } + + ok(false, "Unknown message"); + }); + + content.document.body.appendChild(ifr); + ifr.src = obj.page; + }); + }); + + info("Removing the tab"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function() { + info("Cleaning up."); + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve()); + }); +}); + +add_task(async function testUserInteractionHeuristic() { + info("Starting user interaction heuristic test..."); + + info("Creating a new tab"); + let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + info("Loading tracking scripts"); + await ContentTask.spawn(browser, { + page: TEST_3RD_PARTY_PAGE_UI, + popup: TEST_POPUP_PAGE, + }, async obj => { + let msg = {}; + msg.blockingCallback = (async _ => { + let hasAccess = await document.hasStorageAccess(); + ok(!hasAccess, "Doesn't yet have storage access"); + }).toString(); + + msg.nonBlockingCallback = (async _ => { + let hasAccess = await document.hasStorageAccess(); + ok(hasAccess, "Now obtained storage access"); + }).toString(); + + info("Checking if storage access is denied"); + + let ifr = content.document.createElement("iframe"); + let loading = new content.Promise(resolve => { ifr.onload = resolve; }); + content.document.body.appendChild(ifr); + ifr.src = obj.page; + await loading; + + info("The 3rd party content should not have access to first party storage."); + await new content.Promise(resolve => { + content.addEventListener("message", function msg(event) { + if (event.data.type == "finish") { + content.removeEventListener("message", msg); + resolve(); + return; + } + + if (event.data.type == "ok") { + ok(event.data.what, event.data.msg); + return; + } + + if (event.data.type == "info") { + info(event.data.msg); + return; + } + + ok(false, "Unknown message"); + }); + ifr.contentWindow.postMessage({ callback: msg.blockingCallback }, "*"); + }); + + let windowClosed = new content.Promise(resolve => { + Services.ww.registerNotification(function notification(aSubject, aTopic, aData) { + if (aTopic == "domwindowclosed") { + Services.ww.unregisterNotification(notification); + resolve(); + } + }); + }); + + info("Opening a window from the iframe."); + ifr.contentWindow.open(obj.popup); + + info("Let's wait for the window to be closed"); + await windowClosed; + + info("The 3rd party content should have access to first party storage."); + await new content.Promise(resolve => { + content.addEventListener("message", function msg(event) { + if (event.data.type == "finish") { + content.removeEventListener("message", msg); + resolve(); + return; + } + + if (event.data.type == "ok") { + ok(event.data.what, event.data.msg); + return; + } + + if (event.data.type == "info") { + info(event.data.msg); + return; + } + + ok(false, "Unknown message"); + }); + ifr.contentWindow.postMessage({ callback: msg.nonBlockingCallback }, "*"); + }); + }); + + info("Removing the tab"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function() { + info("Cleaning up."); + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve()); + }); +}); +