From 7df867acf98ef69f276a5d696e9a0903b53623e2 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 14 Feb 2014 22:36:44 -0800 Subject: [PATCH] Bug 968335 - Implement GetCallerPrincipalOverride. r=bz --- dom/base/ScriptSettings.cpp | 39 +++++++++++++++++++++++++++++++++ dom/base/ScriptSettings.h | 25 +++++++++++++++++++++ dom/bindings/CallbackObject.cpp | 12 +++++++++- dom/bindings/CallbackObject.h | 5 +++-- dom/bindings/Codegen.py | 3 ++- 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp index 9e44fb34abf..c892c5aaa2c 100644 --- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -155,6 +155,45 @@ GetIncumbentGlobal() return ScriptSettingsStack::Ref().IncumbentGlobal(); } +nsIPrincipal* +GetWebIDLCallerPrincipal() +{ + MOZ_ASSERT(NS_IsMainThread()); + ScriptSettingsStackEntry *entry = ScriptSettingsStack::Ref().EntryPoint(); + + // If we have an entry point that is not the system singleton, we know it + // must be an AutoEntryScript. + if (!entry || entry->IsSystemSingleton()) { + return nullptr; + } + AutoEntryScript* aes = static_cast(entry); + + // We can't yet rely on the Script Settings Stack to properly determine the + // entry script, because there are still lots of places in the tree where we + // don't yet use an AutoEntryScript (bug 951991 tracks this work). In the + // mean time though, we can make some observations to hack around the + // problem: + // + // (1) All calls into JS-implemented WebIDL go through CallSetup, which goes + // through AutoEntryScript. + // (2) The top candidate entry point in the Script Settings Stack is the + // entry point if and only if no other JSContexts have been pushed on + // top of the push made by that entry's AutoEntryScript. + // + // Because of (1), all of the cases where we might return a non-null + // WebIDL Caller are guaranteed to have put an entry on the Script Settings + // Stack, so we can restrict our search to that. Moreover, (2) gives us a + // criterion to determine whether an entry in the Script Setting Stack means + // that we should return a non-null WebIDL Caller. + // + // Once we fix bug 951991, this can all be simplified. + if (!aes->mCxPusher.ref().IsStackTop()) { + return nullptr; + } + + return aes->mWebIDLCallerPrincipal; +} + AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, bool aIsMainThread, JSContext* aCx) diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h index 597043b7264..5e23bb14f5e 100644 --- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -12,6 +12,7 @@ #include "nsCxPusher.h" #include "MainThreadUtils.h" #include "nsIGlobalObject.h" +#include "nsIPrincipal.h" #include "mozilla/Maybe.h" @@ -38,6 +39,24 @@ nsIGlobalObject* BrokenGetEntryGlobal(); // can mostly be inferred from the JS stack. nsIGlobalObject* GetIncumbentGlobal(); +// JS-implemented WebIDL presents an interesting situation with respect to the +// subject principal. A regular C++-implemented API can simply examine the +// compartment of the most-recently-executed script, and use that to infer the +// responsible party. However, JS-implemented APIs are run with system +// principal, and thus clobber the subject principal of the script that +// invoked the API. So we have to do some extra work to keep track of this +// information. +// +// We therefore implement the following behavior: +// * Each Script Settings Object has an optional WebIDL Caller Principal field. +// This defaults to null. +// * When we push an Entry Point in preparation to run a JS-implemented WebIDL +// callback, we grab the subject principal at the time of invocation, and +// store that as the WebIDL Caller Principal. +// * When non-null, callers can query this principal from script via an API on +// Components.utils. +nsIPrincipal* GetWebIDLCallerPrincipal(); + class ScriptSettingsStack; struct ScriptSettingsStackEntry { nsCOMPtr mGlobalObject; @@ -79,11 +98,17 @@ public: JSContext* aCx = nullptr); ~AutoEntryScript(); + void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) { + mWebIDLCallerPrincipal = aPrincipal; + } + private: dom::ScriptSettingsStack& mStack; + nsCOMPtr mWebIDLCallerPrincipal; mozilla::Maybe mCxPusher; mozilla::Maybe mAc; // This can de-Maybe-fy when mCxPusher // goes away. + friend nsIPrincipal* GetWebIDLCallerPrincipal(); }; /* diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp index c612c0688f9..a5057f9860a 100644 --- a/dom/bindings/CallbackObject.cpp +++ b/dom/bindings/CallbackObject.cpp @@ -49,7 +49,8 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback, ErrorResult& aRv, ExceptionHandling aExceptionHandling, - JSCompartment* aCompartment) + JSCompartment* aCompartment, + bool aIsJSImplementedWebIDL) : mCx(nullptr) , mCompartment(aCompartment) , mErrorResult(aRv) @@ -59,6 +60,14 @@ CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback, if (mIsMainThread) { nsContentUtils::EnterMicroTask(); } + + // Compute the caller's subject principal (if necessary) early, before we + // do anything that might perturb the relevant state. + nsIPrincipal* webIDLCallerPrincipal = nullptr; + if (aIsJSImplementedWebIDL) { + webIDLCallerPrincipal = nsContentUtils::GetSubjectPrincipal(); + } + // We need to produce a useful JSContext here. Ideally one that the callback // is in some sense associated with, so that we can sort of treat it as a // "script entry point". Though once we actually have script entry points, @@ -112,6 +121,7 @@ CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback, } mAutoEntryScript.construct(globalObject, mIsMainThread, cx); + mAutoEntryScript.ref().SetWebIDLCallerPrincipal(webIDLCallerPrincipal); if (aCallback->IncumbentGlobalOrNull()) { mAutoIncumbentScript.construct(aCallback->IncumbentGlobalOrNull()); } diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h index 8f56e435d7d..9465094b453 100644 --- a/dom/bindings/CallbackObject.h +++ b/dom/bindings/CallbackObject.h @@ -154,10 +154,11 @@ protected: */ public: // If aExceptionHandling == eRethrowContentExceptions then aCompartment - // needs to be set to the caller's compartment. + // needs to be set to the compartment in which exceptions will be rethrown. CallSetup(CallbackObject* aCallback, ErrorResult& aRv, ExceptionHandling aExceptionHandling, - JSCompartment* aCompartment = nullptr); + JSCompartment* aCompartment = nullptr, + bool aIsJSImplementedWebIDL = false); ~CallSetup(); JSContext* GetContext() const diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index efe2819c377..b3616efc674 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -11289,7 +11289,8 @@ class CallbackMember(CGNativeMember): if self.rethrowContentException: # getArgs doesn't add the aExceptionHandling argument but does add # aCompartment for us. - callSetup += ", eRethrowContentExceptions, aCompartment" + callSetup += ", eRethrowContentExceptions, aCompartment, /* aIsJSImplementedWebIDL = */ " + callSetup += toStringBool(isJSImplementedDescriptor(self.descriptorProvider)) else: callSetup += ", aExceptionHandling" callSetup += ");"