Bug 949488 - postMessage's targetOrigin argument should accept /, r=bholley
☠☠ backed out by b359c08f9a9f ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 07 Jan 2014 00:05:01 +0100
changeset 179295 e451b39305f656f1f93a0d7faf4cf6ec5358b5b3
parent 179294 039ef62a57c030038641c8a3863d101061c8a6a9
child 179296 8c5e82bc3241bb2ab210909c28a3843127362ae3
push id462
push userraliiev@mozilla.com
push dateTue, 22 Apr 2014 00:22:30 +0000
treeherdermozilla-release@ac5db8c74ac0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs949488
milestone29.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 949488 - postMessage's targetOrigin argument should accept /, r=bholley
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
dom/base/moz.build
dom/base/nsGlobalWindow.cpp
dom/base/nsIGlobalObject.cpp
dom/base/nsIGlobalObject.h
dom/base/test/iframe_postMessage_solidus.html
dom/base/test/mochitest.ini
dom/base/test/test_postMessage_solidus.html
dom/tests/mochitest/localstorage/test_clear_browser_data.html
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -10,16 +10,17 @@
 
 #include "jsapi.h"
 #include "xpcpublic.h"
 #include "nsIGlobalObject.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsContentUtils.h"
 #include "nsTArray.h"
+#include "nsJSUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 class ScriptSettingsStack;
 static mozilla::ThreadLocal<ScriptSettingsStack*> sScriptSettingsTLS;
 
 ScriptSettingsStackEntry ScriptSettingsStackEntry::SystemSingleton;
@@ -87,16 +88,35 @@ InitScriptSettings()
 void DestroyScriptSettings()
 {
   ScriptSettingsStack* ptr = sScriptSettingsTLS.get();
   MOZ_ASSERT(ptr);
   sScriptSettingsTLS.set(nullptr);
   delete ptr;
 }
 
+// This mostly gets the entry global, but doesn't entirely match the spec in
+// certain edge cases. It's good enough for some purposes, but not others. If
+// you want to call this function, ping bholley and describe your use-case.
+nsIGlobalObject*
+BrokenGetEntryGlobal()
+{
+  // We need the current JSContext in order to check the JS for
+  // scripted frames that may have appeared since anyone last
+  // manipulated the stack. If it's null, that means that there
+  // must be no entry point on the stack.
+  JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
+  if (!cx) {
+    MOZ_ASSERT(ScriptSettingsStack::Ref().EntryPoint() == nullptr);
+    return nullptr;
+  }
+
+  return nsJSUtils::GetDynamicScriptGlobal(cx);
+}
+
 // Note: When we're ready to expose it, GetEntryGlobal will look similar to
 // GetIncumbentGlobal below.
 
 nsIGlobalObject*
 GetIncumbentGlobal()
 {
   // We need the current JSContext in order to check the JS for
   // scripted frames that may have appeared since anyone last
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -22,16 +22,21 @@ namespace dom {
 
 /*
  * System-wide setup/teardown routines. Init and Destroy should be invoked
  * once each, at startup and shutdown (respectively).
  */
 void InitScriptSettings();
 void DestroyScriptSettings();
 
+// This mostly gets the entry global, but doesn't entirely match the spec in
+// certain edge cases. It's good enough for some purposes, but not others. If
+// you want to call this function, ping bholley and describe your use-case.
+nsIGlobalObject* BrokenGetEntryGlobal();
+
 // Note: We don't yet expose GetEntryGlobal, because in order for it to be
 // correct, we first need to replace a bunch of explicit cx pushing in the
 // browser with AutoEntryScript. But GetIncumbentGlobal is simpler, because it
 // can mostly be inferred from the JS stack.
 nsIGlobalObject* GetIncumbentGlobal();
 
 class ScriptSettingsStack;
 struct ScriptSettingsStackEntry {
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -79,16 +79,17 @@ UNIFIED_SOURCES += [
     'nsContentPermissionHelper.cpp',
     'nsDOMClassInfo.cpp',
     'nsDOMNavigationTiming.cpp',
     'nsDOMScriptObjectFactory.cpp',
     'nsDOMWindowList.cpp',
     'nsFocusManager.cpp',
     'nsGlobalWindowCommands.cpp',
     'nsHistory.cpp',
+    'nsIGlobalObject.cpp',
     'nsJSTimeoutHandler.cpp',
     'nsJSUtils.cpp',
     'nsLocation.cpp',
     'nsMimeTypeArray.cpp',
     'nsPerformance.cpp',
     'nsQueryContentEventResult.cpp',
     'nsScreen.cpp',
     'nsScriptNameSpaceManager.cpp',
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7480,24 +7480,24 @@ nsGlobalWindow::CallerInnerWindow()
 class PostMessageEvent : public nsRunnable
 {
   public:
     NS_DECL_NSIRUNNABLE
 
     PostMessageEvent(nsGlobalWindow* aSource,
                      const nsAString& aCallerOrigin,
                      nsGlobalWindow* aTargetWindow,
-                     nsIURI* aProvidedOrigin,
+                     nsIPrincipal* aProvidedPrincipal,
                      bool aTrustedCaller)
     : mSource(aSource),
       mCallerOrigin(aCallerOrigin),
       mMessage(nullptr),
       mMessageLen(0),
       mTargetWindow(aTargetWindow),
-      mProvidedOrigin(aProvidedOrigin),
+      mProvidedPrincipal(aProvidedPrincipal),
       mTrustedCaller(aTrustedCaller)
     {
       MOZ_COUNT_CTOR(PostMessageEvent);
     }
     
     ~PostMessageEvent()
     {
       NS_ASSERTION(!mMessage, "Message should have been deserialized!");
@@ -7517,17 +7517,17 @@ class PostMessageEvent : public nsRunnab
     }
 
   private:
     nsRefPtr<nsGlobalWindow> mSource;
     nsString mCallerOrigin;
     uint64_t* mMessage;
     size_t mMessageLen;
     nsRefPtr<nsGlobalWindow> mTargetWindow;
-    nsCOMPtr<nsIURI> mProvidedOrigin;
+    nsCOMPtr<nsIPrincipal> mProvidedPrincipal;
     bool mTrustedCaller;
     nsTArray<nsCOMPtr<nsISupports> > mSupportsArray;
 };
 
 namespace {
 
 struct StructuredCloneInfo {
   PostMessageEvent* event;
@@ -7686,42 +7686,32 @@ PostMessageEvent::Run()
   // Ensure that any origin which might have been provided is the origin of this
   // window's document.  Note that we do this *now* instead of when postMessage
   // is called because the target window might have been navigated to a
   // different location between then and now.  If this check happened when
   // postMessage was called, it would be fairly easy for a malicious webpage to
   // intercept messages intended for another site by carefully timing navigation
   // of the target window so it changed location after postMessage but before
   // now.
-  if (mProvidedOrigin) {
+  if (mProvidedPrincipal) {
     // Get the target's origin either from its principal or, in the case the
     // principal doesn't carry a URI (e.g. the system principal), the target's
     // document.
     nsIPrincipal* targetPrin = targetWindow->GetPrincipal();
-    if (!targetPrin)
-      return NS_OK;
-    nsCOMPtr<nsIURI> targetURI;
-    if (NS_FAILED(targetPrin->GetURI(getter_AddRefs(targetURI))))
+    if (NS_WARN_IF(!targetPrin))
       return NS_OK;
-    if (!targetURI) {
-      targetURI = targetWindow->mDoc->GetDocumentURI();
-      if (!targetURI)
-        return NS_OK;
-    }
 
     // Note: This is contrary to the spec with respect to file: URLs, which
     //       the spec groups into a single origin, but given we intentionally
     //       don't do that in other places it seems better to hold the line for
     //       now.  Long-term, we want HTML5 to address this so that we can
     //       be compliant while being safer.
-    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
-    nsresult rv =
-      ssm->CheckSameOriginURI(mProvidedOrigin, targetURI, true);
-    if (NS_FAILED(rv))
+    if (!targetPrin->EqualsIgnoringDomain(mProvidedPrincipal)) {
       return NS_OK;
+    }
   }
 
   // Deserialize the structured clone data
   JS::Rooted<JS::Value> messageData(cx);
   {
     StructuredCloneInfo scInfo;
     scInfo.event = this;
     scInfo.window = targetWindow;
@@ -7844,38 +7834,70 @@ nsGlobalWindow::PostMessageMoz(JSContext
   else {
     // in case of a sandbox with a system principal origin can be empty
     if (!nsContentUtils::IsSystemPrincipal(callerPrin)) {
       return;
     }
   }
 
   // Convert the provided origin string into a URI for comparison purposes.
+  nsCOMPtr<nsIPrincipal> providedPrincipal;
+
+  if (aTargetOrigin.EqualsASCII("/")) {
+    providedPrincipal = BrokenGetEntryGlobal()->PrincipalOrNull();
+    if (NS_WARN_IF(!providedPrincipal))
+      return;
+  }
+
   // "*" indicates no specific origin is required.
-  nsCOMPtr<nsIURI> providedOrigin;
-  if (!aTargetOrigin.EqualsASCII("*")) {
-    if (NS_FAILED(NS_NewURI(getter_AddRefs(providedOrigin), aTargetOrigin))) {
+  else if (!aTargetOrigin.EqualsASCII("*")) {
+    nsCOMPtr<nsIURI> originURI;
+    if (NS_FAILED(NS_NewURI(getter_AddRefs(originURI), aTargetOrigin))) {
       aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
       return;
     }
-    if (NS_FAILED(providedOrigin->SetUserPass(EmptyCString())) ||
-        NS_FAILED(providedOrigin->SetPath(EmptyCString()))) {
+
+    if (NS_FAILED(originURI->SetUserPass(EmptyCString())) ||
+        NS_FAILED(originURI->SetPath(EmptyCString()))) {
+      return;
+    }
+
+    nsCOMPtr<nsIScriptSecurityManager> ssm =
+      nsContentUtils::GetSecurityManager();
+    MOZ_ASSERT(ssm);
+
+    nsCOMPtr<nsIPrincipal> principal = nsContentUtils::GetSubjectPrincipal();
+    MOZ_ASSERT(principal);
+
+    uint32_t appId;
+    if (NS_WARN_IF(NS_FAILED(principal->GetAppId(&appId))))
+      return;
+
+    bool isInBrowser;
+    if (NS_WARN_IF(NS_FAILED(principal->GetIsInBrowserElement(&isInBrowser))))
+      return;
+
+    // Create a nsIPrincipal inheriting the app/browser attributes from the
+    // caller.
+    nsresult rv = ssm->GetAppCodebasePrincipal(originURI, appId, isInBrowser,
+                                             getter_AddRefs(providedPrincipal));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
   }
 
   // Create and asynchronously dispatch a runnable which will handle actual DOM
   // event creation and dispatch.
   nsRefPtr<PostMessageEvent> event =
     new PostMessageEvent(nsContentUtils::IsCallerChrome() || !callerInnerWin
                          ? nullptr
                          : callerInnerWin->GetOuterWindowInternal(),
                          origin,
                          this,
-                         providedOrigin,
+                         providedPrincipal,
                          nsContentUtils::IsCallerChrome());
 
   // We *must* clone the data here, or the JS::Value could be modified
   // by script
   JSAutoStructuredCloneBuffer buffer;
   StructuredCloneInfo scInfo;
   scInfo.event = event;
   scInfo.window = this;
new file mode 100644
--- /dev/null
+++ b/dom/base/nsIGlobalObject.cpp
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIGlobalObject.h"
+#include "nsContentUtils.h"
+
+nsIPrincipal*
+nsIGlobalObject::PrincipalOrNull()
+{
+  JSObject *global = GetGlobalJSObject();
+  if (NS_WARN_IF(!global))
+    return nullptr;
+
+  return nsContentUtils::GetObjectPrincipal(global);
+}
--- a/dom/base/nsIGlobalObject.h
+++ b/dom/base/nsIGlobalObject.h
@@ -8,20 +8,25 @@
 
 #include "nsISupports.h"
 #include "js/TypeDecls.h"
 
 #define NS_IGLOBALOBJECT_IID \
 { 0xe2538ded, 0x13ef, 0x4f4d, \
 { 0x94, 0x6b, 0x65, 0xd3, 0x33, 0xb4, 0xf0, 0x3c } }
 
+class nsIPrincipal;
+
 class nsIGlobalObject : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IGLOBALOBJECT_IID)
 
   virtual JSObject* GetGlobalJSObject() = 0;
+
+  // This method is not meant to be overridden.
+  nsIPrincipal* PrincipalOrNull();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIGlobalObject,
                               NS_IGLOBALOBJECT_IID)
 
 #endif // nsIGlobalObject_h__
new file mode 100644
--- /dev/null
+++ b/dom/base/test/iframe_postMessage_solidus.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <script type="application/javascript">
+
+  window.addEventListener('message', receiveMessage, false);
+  function receiveMessage(evt) {
+    window.parent.postMessage(evt.data, '*');
+  }
+
+  </script>
+</body>
+</html>
+
+
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   iframe_messageChannel_cloning.html
   iframe_messageChannel_pingpong.html
   iframe_messageChannel_post.html
   file_empty.html
+  iframe_postMessage_solidus.html
 
 [test_Image_constructor.html]
 [test_appname_override.html]
 [test_bug913761.html]
 [test_constructor-assignment.html]
 [test_constructor.html]
 [test_document.all_unqualified.html]
 [test_domcursor.html]
@@ -40,9 +41,10 @@ support-files =
 [test_window_enumeration.html]
 [test_window_extensible.html]
 [test_window_indexing.html]
 [test_writable-replaceable.html]
 [test_urlExceptions.html]
 [test_openDialogChromeOnly.html]
 [test_messagemanager_targetchain.html]
 [test_url_empty_port.html]
+[test_postMessage_solidus.html]
 [test_urlSearchParams.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_postMessage_solidus.html
@@ -0,0 +1,93 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=949488
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 949488 - basic support</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949488">Mozilla Bug 949488</a>
+  <div id="content"></div>
+  <script type="application/javascript">
+
+  function selfMessage() {
+    addEventListener('message', receiveMessage);
+    function receiveMessage(evt) {
+      is(evt.data, 1, "Message received");
+      removeEventListener('message', receiveMessage);
+      runTest();
+    }
+
+    postMessage(1, '/');
+  }
+
+  function frameOk() {
+    var ifr = document.createElement("iframe");
+    ifr.addEventListener("load", iframeLoaded, false);
+    ifr.setAttribute('src', "iframe_postMessage_solidus.html");
+
+    var div = document.getElementById("content");
+    div.appendChild(ifr);
+
+    function iframeLoaded() {
+      addEventListener('message', receiveMessage);
+      function receiveMessage(evt) {
+        is(evt.data, 2, "Message received");
+        removeEventListener('message', receiveMessage);
+        runTest();
+      }
+
+      ifr.contentWindow.postMessage(2, '/');
+    }
+  }
+
+  function frameWrong() {
+    var ifr = document.createElement("iframe");
+    ifr.addEventListener("load", iframeLoaded, false);
+    ifr.setAttribute('src', "http://www.example.com/tests/dom/base/test/iframe_postMessage_solidus.html");
+
+    var div = document.getElementById("content");
+    div.appendChild(ifr);
+
+    function iframeLoaded() {
+      addEventListener('message', receiveMessage);
+      function receiveMessage(evt) {
+        ok(evt.data, 3, "Message received");
+        removeEventListener('message', receiveMessage);
+        runTest();
+      }
+
+      ifr.contentWindow.postMessage(4, '/');
+      SimpleTest.executeSoon(function() {
+        ifr.contentWindow.postMessage(3, '*');
+      });
+    }
+  }
+
+  var tests = [
+    selfMessage,
+    frameOk,
+    frameWrong
+  ];
+
+  function runTest() {
+    if (!tests.length) {
+      SimpleTest.finish();
+      return;
+    }
+
+    var test = tests.shift();
+    test();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  runTest();
+
+  </script>
+</body>
+</html>
--- a/dom/tests/mochitest/localstorage/test_clear_browser_data.html
+++ b/dom/tests/mochitest/localstorage/test_clear_browser_data.html
@@ -186,17 +186,17 @@ function browserLoadEvent() {
   gBrowserStorage.localStorage = window.frames[1].frames[0].localStorage;
   gBrowserStorage.sessionStorage = window.frames[1].frames[0].sessionStorage;
 
   setupStorage(gAppStorage.localStorage);
   setupStorage(gAppStorage.sessionStorage);
   setupStorage(gBrowserStorage.localStorage);
   setupStorage(gBrowserStorage.sessionStorage);
 
-  frames[1].postMessage("clear", "http://www.example.com");
+  frames[1].postMessage("clear", "*");
 
   waitForClearBrowserData();
 };
 
 function waitForClearBrowserData() {
   SimpleTest.executeSoon(function() {
     if (frames[1].document.getElementsByTagName('done').length == 0) {
       waitForClearBrowserData();