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
☠☠ backed out by dc31941ced58 ☠ ☠
authorEhsan Akhgari <ehsan@mozilla.com>
Fri, 14 Sep 2018 19:20:09 -0400
changeset 436798 0fb2ac9ad5ec112fd128c648cca4b4b6d37f7c0a
parent 436797 4a88ff1074781172ae98833e5e7b191d019808b1
child 436799 43552fcae4a4bb4dccb7488b061b31c4927f858d
push id34660
push userbtara@mozilla.com
push dateMon, 17 Sep 2018 21:58:52 +0000
treeherdermozilla-central@87a95e1b7ec6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1469714
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
toolkit/components/antitracking/test/browser/browser.ini
toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -13559,16 +13559,39 @@ nsIDocument::HasStorageAccess(mozilla::E
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
   if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
     promise->MaybeResolve(true);
     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) {
     outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
     promise->MaybeResolve(outer->HasStorageAccess());
   } else {
     promise->MaybeRejectWithUndefined();
   }
@@ -13662,16 +13685,23 @@ nsIDocument::RequestStorageAccess(mozill
 
   bool granted = true;
   bool isTrackingWindow = false;
   if (StaticPrefs::browser_contentblocking_enabled() &&
       StaticPrefs::network_cookie_cookieBehavior() ==
         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<bool> isOnAllowList = false;
+      MOZ_ASSERT_IF(NS_SUCCEEDED(AntiTrackingCommon::IsOnContentBlockingAllowList(
+                                   parent->GetDocumentURI(), isOnAllowList)),
+                    !isOnAllowList);
+
       isTrackingWindow = true;
       // TODO: prompt for permission
     }
   }
 
   // Step 10. Grant the document access to cookies and store that fact for
   //          the purposes of future calls to hasStorageAccess() and
   //          requestStorageAccess().
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -35,8 +35,9 @@ support-files = server.sjs
 [browser_permissionInNormalWindows.js]
 [browser_permissionInPrivateWindows.js]
 [browser_subResources.js]
 support-files = subResources.sjs
 [browser_script.js]
 support-files = tracker.js
 [browser_storageAccessPrivateWindow.js]
 [browser_storageAccessSandboxed.js]
+[browser_storageAccessWithHeuristics.js]
new file mode 100644
--- /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());
+  });
+});
+