From 202e31a90e2c8198874a0264f0da839b012a0c05 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Tue, 7 Jan 2014 19:53:31 +0100 Subject: [PATCH] Bug 949488 - postMessage's targetOrigin argument should accept /, r=bholley --- dom/base/ScriptSettings.cpp | 20 ++++ dom/base/ScriptSettings.h | 5 + dom/base/moz.build | 1 + dom/base/nsGlobalWindow.cpp | 68 +++++++++----- dom/base/nsIGlobalObject.cpp | 18 ++++ dom/base/nsIGlobalObject.h | 5 + dom/base/test/iframe_postMessage_solidus.html | 15 +++ dom/base/test/mochitest.ini | 2 + dom/base/test/test_postMessage_solidus.html | 93 +++++++++++++++++++ .../localstorage/test_clear_browser_data.html | 2 +- .../storageevent/interOriginTest2.js | 6 +- 11 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 dom/base/nsIGlobalObject.cpp create mode 100644 dom/base/test/iframe_postMessage_solidus.html create mode 100644 dom/base/test/test_postMessage_solidus.html diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp index c4ebb9aa888e..748a9a6e7811 100644 --- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -15,6 +15,7 @@ #include "nsIScriptContext.h" #include "nsContentUtils.h" #include "nsTArray.h" +#include "nsJSUtils.h" namespace mozilla { namespace dom { @@ -92,6 +93,25 @@ void DestroyScriptSettings() 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. diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h index fd0f44b4e97b..f9e1645df4d4 100644 --- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -27,6 +27,11 @@ namespace dom { 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 diff --git a/dom/base/moz.build b/dom/base/moz.build index b6c0ee9cd98a..bceedb651985 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -84,6 +84,7 @@ UNIFIED_SOURCES += [ 'nsFocusManager.cpp', 'nsGlobalWindowCommands.cpp', 'nsHistory.cpp', + 'nsIGlobalObject.cpp', 'nsJSTimeoutHandler.cpp', 'nsJSUtils.cpp', 'nsLocation.cpp', diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 891504f4dc2f..6c667c867c96 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -7493,14 +7493,14 @@ class PostMessageEvent : public nsRunnable 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); @@ -7530,7 +7530,7 @@ class PostMessageEvent : public nsRunnable uint64_t* mMessage; size_t mMessageLen; nsRefPtr mTargetWindow; - nsCOMPtr mProvidedOrigin; + nsCOMPtr mProvidedPrincipal; bool mTrustedCaller; nsTArray > mSupportsArray; }; @@ -7699,32 +7699,22 @@ PostMessageEvent::Run() // 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) + if (NS_WARN_IF(!targetPrin)) return NS_OK; - nsCOMPtr targetURI; - if (NS_FAILED(targetPrin->GetURI(getter_AddRefs(targetURI)))) - 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 @@ -7857,15 +7847,47 @@ nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, } // Convert the provided origin string into a URI for comparison purposes. + nsCOMPtr providedPrincipal; + + if (aTargetOrigin.EqualsASCII("/")) { + providedPrincipal = BrokenGetEntryGlobal()->PrincipalOrNull(); + if (NS_WARN_IF(!providedPrincipal)) + return; + } + // "*" indicates no specific origin is required. - nsCOMPtr providedOrigin; - if (!aTargetOrigin.EqualsASCII("*")) { - if (NS_FAILED(NS_NewURI(getter_AddRefs(providedOrigin), aTargetOrigin))) { + else if (!aTargetOrigin.EqualsASCII("*")) { + nsCOMPtr 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 ssm = + nsContentUtils::GetSecurityManager(); + MOZ_ASSERT(ssm); + + nsCOMPtr 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; } } @@ -7878,7 +7900,7 @@ nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, : callerInnerWin->GetOuterWindowInternal(), origin, this, - providedOrigin, + providedPrincipal, nsContentUtils::IsCallerChrome()); // We *must* clone the data here, or the JS::Value could be modified diff --git a/dom/base/nsIGlobalObject.cpp b/dom/base/nsIGlobalObject.cpp new file mode 100644 index 000000000000..0c0f1d92a9b0 --- /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); +} diff --git a/dom/base/nsIGlobalObject.h b/dom/base/nsIGlobalObject.h index 6acb86d0c0ff..c5b28041acca 100644 --- a/dom/base/nsIGlobalObject.h +++ b/dom/base/nsIGlobalObject.h @@ -13,12 +13,17 @@ { 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, diff --git a/dom/base/test/iframe_postMessage_solidus.html b/dom/base/test/iframe_postMessage_solidus.html new file mode 100644 index 000000000000..b5cf33b40278 --- /dev/null +++ b/dom/base/test/iframe_postMessage_solidus.html @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index ab46010005f5..61a915c49f9c 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -4,6 +4,7 @@ support-files = iframe_messageChannel_pingpong.html iframe_messageChannel_post.html file_empty.html + iframe_postMessage_solidus.html [test_Image_constructor.html] [test_appname_override.html] @@ -45,4 +46,5 @@ support-files = [test_openDialogChromeOnly.html] [test_messagemanager_targetchain.html] [test_url_empty_port.html] +[test_postMessage_solidus.html] [test_urlSearchParams.html] diff --git a/dom/base/test/test_postMessage_solidus.html b/dom/base/test/test_postMessage_solidus.html new file mode 100644 index 000000000000..ab3e4ecec469 --- /dev/null +++ b/dom/base/test/test_postMessage_solidus.html @@ -0,0 +1,93 @@ + + + + + + + Test for Bug 949488 - basic support + + + + + Mozilla Bug 949488 +
+ + + diff --git a/dom/tests/mochitest/localstorage/test_clear_browser_data.html b/dom/tests/mochitest/localstorage/test_clear_browser_data.html index 26a48a06872a..3444b0dc8d27 100644 --- a/dom/tests/mochitest/localstorage/test_clear_browser_data.html +++ b/dom/tests/mochitest/localstorage/test_clear_browser_data.html @@ -191,7 +191,7 @@ function browserLoadEvent() { setupStorage(gBrowserStorage.localStorage); setupStorage(gBrowserStorage.sessionStorage); - frames[1].postMessage("clear", "http://www.example.com"); + frames[1].postMessage("clear", "*"); waitForClearBrowserData(); }; diff --git a/dom/tests/mochitest/storageevent/interOriginTest2.js b/dom/tests/mochitest/storageevent/interOriginTest2.js index 73b04b0fdd3f..939462a4c690 100644 --- a/dom/tests/mochitest/storageevent/interOriginTest2.js +++ b/dom/tests/mochitest/storageevent/interOriginTest2.js @@ -28,11 +28,11 @@ function onMessageReceived(event) // Indication of successfully finished step of a test case "perf": if (callMasterFrame) - masterFrame.postMessage("step", masterFrameOrigin); + masterFrame.postMessage("step", "*"); else if (slaveFrame) - slaveFrame.postMessage("step", slaveFrameOrigin); + slaveFrame.postMessage("step", "*"); else if (SpecialPowers.wrap(masterFrame).slaveFrame) - SpecialPowers.wrap(masterFrame).slaveFrame.postMessage("step", slaveFrameOrigin); + SpecialPowers.wrap(masterFrame).slaveFrame.postMessage("step", "*"); callMasterFrame = !callMasterFrame; break;