Bug 1560741 - Part 1: Disallow notification permission requests from cross-origin iframes; r=johannh
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 12 Aug 2019 21:38:58 +0000
changeset 487529 b7c91018f87ee89c7b209b76a0202be118cdff32
parent 487528 0836c998795bdbab60a6090b25197ddc2bd96587
child 487530 efe5dc48aa87d8b76a3b4a65653b00986343988b
push id36425
push userbtara@mozilla.com
push dateTue, 13 Aug 2019 09:54:32 +0000
treeherdermozilla-central@e29ba984dad2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh
bugs1560741
milestone70.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 1560741 - Part 1: Disallow notification permission requests from cross-origin iframes; r=johannh Differential Revision: https://phabricator.services.mozilla.com/D41305
dom/base/test/chrome/test_permission_isHandlingUserInput.xul
dom/locales/en-US/chrome/dom/dom.properties
dom/notification/Notification.cpp
dom/notification/test/mochitest/blank.html
dom/notification/test/mochitest/mochitest.ini
dom/notification/test/mochitest/test_notification_crossorigin_iframe.html
modules/libpref/init/StaticPrefList.yaml
--- a/dom/base/test/chrome/test_permission_isHandlingUserInput.xul
+++ b/dom/base/test/chrome/test_permission_isHandlingUserInput.xul
@@ -34,16 +34,18 @@
           return { prompt() {} };
         },
       });
       Integration.contentPermission.register(TestIntegration);
     });
   }
 
   async function runTest() {
+    await SpecialPowers.setBoolPref("dom.webnotifications.allowcrossoriginiframe", true);
+
     // Test programmatic request for persistent storage.
     let request = checkPermissionRequest("persistent-storage", false);
     navigator.storage.persist();
     await request;
 
     // Test user-initiated request for persistent storage.
     request = checkPermissionRequest("persistent-storage", true);
     E10SUtils.wrapHandlingUserInput(content, true, function() {
@@ -73,16 +75,18 @@
     await request;
 
     // Test user-initiated request for notifications.
     request = checkPermissionRequest("desktop-notification", true);
     E10SUtils.wrapHandlingUserInput(content, true, function() {
       frameWin.Notification.requestPermission();
     });
     await request;
+
+    await SpecialPowers.clearUserPref("dom.webnotifications.allowcrossoriginiframe");
   }
 
   frame.addEventListener("load", function() {
     runTest().then(() => SimpleTest.finish());
   });
   ]]>
   </script>
 </window>
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -319,16 +319,17 @@ LargeAllocationSuccess=This page was loa
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name. Do not translate GET.
 LargeAllocationNonGetRequest=A Large-Allocation header was ignored due to the load being triggered by a non-GET request.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name. Do not translate `window.opener`.
 LargeAllocationNotOnlyToplevelInTabGroup=A Large-Allocation header was ignored due to the presence of windows which have a reference to this browsing context through the frame hierarchy or window.opener.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name
 LargeAllocationNonE10S=A Large-Allocation header was ignored due to the document not being loaded out of process.
 GeolocationInsecureRequestIsForbidden=A Geolocation request can only be fulfilled in a secure context.
 NotificationsInsecureRequestIsForbidden=The Notification permission may only be requested in a secure context.
+NotificationsCrossOriginIframeRequestIsForbidden=The Notification permission may only be requested in a top-level document or same-origin iframe.
 NotificationsRequireUserGesture=The Notification permission may only be requested from inside a short running user-generated event handler.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name.
 LargeAllocationNonWin32=This page would be loaded in a new process due to a Large-Allocation header, however Large-Allocation process creation is disabled on non-Win32 platforms.
 # LOCALIZATION NOTE: Do not translate "content", "Window", and "window.top"
 WindowContentUntrustedWarning=The ‘content’ attribute of Window objects is deprecated.  Please use ‘window.top’ instead.
 # LOCALIZATION NOTE: The first %S is the tag name of the element that starts the loop, the second %S is the element's ID.
 SVGRefLoopWarning=The SVG <%S> with ID “%S” has a reference loop.
 # LOCALIZATION NOTE: The first %S is the tag name of the element in the chain where the chain was broken, the second %S is the element's ID.
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -465,28 +465,31 @@ NS_IMPL_RELEASE_INHERITED(NotificationPe
                           ContentPermissionRequestBase)
 
 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
     NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable,
     nsINamed)
 
 NS_IMETHODIMP
 NotificationPermissionRequest::Run() {
-  if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
+  bool isSystem = nsContentUtils::IsSystemPrincipal(mPrincipal);
+  bool blocked = false;
+  if (isSystem) {
     mPermission = NotificationPermission::Granted;
   } else {
     // File are automatically granted permission.
     nsCOMPtr<nsIURI> uri;
     mPrincipal->GetURI(getter_AddRefs(uri));
 
     if (uri && uri->SchemeIs("file")) {
       mPermission = NotificationPermission::Granted;
     } else if (!StaticPrefs::dom_webnotifications_allowinsecure() &&
                !mWindow->IsSecureContext()) {
       mPermission = NotificationPermission::Denied;
+      blocked = true;
       nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
       if (doc) {
         nsContentUtils::ReportToConsole(
             nsIScriptError::errorFlag, NS_LITERAL_CSTRING("DOM"), doc,
             nsContentUtils::eDOM_PROPERTIES,
             "NotificationsInsecureRequestIsForbidden");
       }
     }
@@ -504,16 +507,32 @@ NotificationPermissionRequest::Run() {
     case PromptResult::Denied:
       mPermission = NotificationPermission::Denied;
       break;
     default:
       // ignore
       break;
   }
 
+  // Check this after checking the prompt prefs to make sure this pref overrides
+  // those.  We rely on this for testing purposes.
+  if (!isSystem && !blocked &&
+      !StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
+      !mPrincipal->Subsumes(mTopLevelPrincipal)) {
+    mPermission = NotificationPermission::Denied;
+    blocked = true;
+    nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+    if (doc) {
+      nsContentUtils::ReportToConsole(
+          nsIScriptError::errorFlag, NS_LITERAL_CSTRING("DOM"), doc,
+          nsContentUtils::eDOM_PROPERTIES,
+          "NotificationsCrossOriginIframeRequestIsForbidden");
+    }
+  }
+
   if (mPermission != NotificationPermission::Default) {
     return DispatchResolvePromise();
   }
 
   return nsContentPermissionUtils::AskPermission(this, mWindow);
 }
 
 NS_IMETHODIMP
new file mode 100644
--- /dev/null
+++ b/dom/notification/test/mochitest/blank.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+  <body></body>
+</html>
--- a/dom/notification/test/mochitest/mochitest.ini
+++ b/dom/notification/test/mochitest/mochitest.ini
@@ -1,16 +1,18 @@
 [DEFAULT]
 
 support-files =
+  blank.html
   create_notification.html
   MockServices.js
   NotificationTest.js
 skip-if = toolkit == 'android' && !is_fennec # Bug 1531097
 
 [test_notification_basics.html]
+[test_notification_crossorigin_iframe.html]
 # This test needs to be run on HTTP (not HTTPS).
 [test_notification_insecure_context.html]
 [test_notification_storage.html]
 [test_bug931307.html]
 skip-if = (os == 'android') # Bug 1258975 on android.
 [test_notification_tag.html]
 fail-if = fission
new file mode 100644
--- /dev/null
+++ b/dom/notification/test/mochitest/test_notification_crossorigin_iframe.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests that Notification permissions are denied in cross-origin iframes.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1560741
+-->
+<head>
+  <title>Notification permission in cross-origin iframes</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+  <p id="display"></p>
+  <div id="content" style="display: none">
+  </div>
+  <pre id="test">
+  <script class="testbody" type="text/javascript">
+  SimpleTest.waitForExplicitFinish();
+
+  const kBlankURL = "https://example.com/tests/dom/notification/test/mochitest/blank.html";
+
+  (async function runTest() {
+    await SpecialPowers.setBoolPref("notification.prompt.testing", true);
+    await SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
+    await SpecialPowers.setBoolPref("dom.webnotifications.allowinsecure", true);
+
+    let iframe = document.createElement("iframe");
+    iframe.src = kBlankURL;
+    document.body.appendChild(iframe);
+    await new Promise(resolve => {
+      iframe.onload = resolve;
+    });
+
+    const Notif = SpecialPowers.wrap(iframe.contentWindow).Notification;
+    let response = await Notif.requestPermission();
+    is(response, "denied", "Denied permission in cross-origin iframe");
+
+    await SpecialPowers.pushPrefEnv({"set": [["dom.webnotifications.allowcrossoriginiframe", true]]});
+
+    response = await Notif.requestPermission();
+    is(response, "granted", "Granted permission in cross-origin iframe with pref set");
+
+    await SpecialPowers.clearUserPref("notification.prompt.testing");
+    await SpecialPowers.clearUserPref("notification.prompt.testing.allow");
+    await SpecialPowers.clearUserPref("dom.webnotifications.allowinsecure");
+
+    SimpleTest.finish();
+  })();
+  </script>
+  </pre>
+</body>
+</html>
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -2332,16 +2332,21 @@
   value: false
   mirror: always
 
 - name: dom.webnotifications.allowinsecure
   type: RelaxedAtomicBool
   value: false
   mirror: always
 
+- name: dom.webnotifications.allowcrossoriginiframe
+  type: RelaxedAtomicBool
+  value: false
+  mirror: always
+
 - name: dom.webnotifications.enabled
   type: RelaxedAtomicBool
   value: true
   mirror: always
 
 - name: dom.webnotifications.requireuserinteraction
   type: RelaxedAtomicBool
   value: @IS_EARLY_BETA_OR_EARLIER@