Bug 968335 - Implement GetCallerPrincipalOverride. r=bz
authorBobby Holley <bobbyholley@gmail.com>
Fri, 14 Feb 2014 16:13:38 -0800
changeset 186076 5360c2573b1124f85b24abb822ada6a0ddf70194
parent 186075 cac7844c804d4cc288215751d8ddfffa9e217c53
child 186077 4503fdf32a315e57acf9f5408e488a121d8264b5
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs968335
milestone30.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 968335 - Implement GetCallerPrincipalOverride. r=bz
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
dom/bindings/CallbackObject.cpp
dom/bindings/CallbackObject.h
dom/bindings/Codegen.py
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -150,16 +150,55 @@ GetIncumbentGlobal()
     return xpc::GetNativeForGlobal(global);
   }
 
   // Ok, nothing from the JS engine. Let's use whatever's on the
   // explicit stack.
   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<AutoEntryScript*>(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)
   : ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
   , mStack(ScriptSettingsStack::Ref())
 {
   MOZ_ASSERT(aGlobalObject);
   if (!aCx) {
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -7,16 +7,17 @@
 /* Utilities for managing the script settings object stack defined in webapps */
 
 #ifndef mozilla_dom_ScriptSettings_h
 #define mozilla_dom_ScriptSettings_h
 
 #include "nsCxPusher.h"
 #include "MainThreadUtils.h"
 #include "nsIGlobalObject.h"
+#include "nsIPrincipal.h"
 
 #include "mozilla/Maybe.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
@@ -33,16 +34,34 @@ void DestroyScriptSettings();
 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();
 
+// 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<nsIGlobalObject> mGlobalObject;
   bool mIsCandidateEntryPoint;
 
   ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, bool aCandidate)
     : mGlobalObject(aGlobal)
     , mIsCandidateEntryPoint(aCandidate)
@@ -74,21 +93,27 @@ private:
 class AutoEntryScript : protected ScriptSettingsStackEntry {
 public:
   AutoEntryScript(nsIGlobalObject* aGlobalObject,
                   bool aIsMainThread = NS_IsMainThread(),
                   // Note: aCx is mandatory off-main-thread.
                   JSContext* aCx = nullptr);
   ~AutoEntryScript();
 
+  void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) {
+    mWebIDLCallerPrincipal = aPrincipal;
+  }
+
 private:
   dom::ScriptSettingsStack& mStack;
+  nsCOMPtr<nsIPrincipal> mWebIDLCallerPrincipal;
   mozilla::Maybe<AutoCxPusher> mCxPusher;
   mozilla::Maybe<JSAutoCompartment> mAc; // This can de-Maybe-fy when mCxPusher
                                          // goes away.
+  friend nsIPrincipal* GetWebIDLCallerPrincipal();
 };
 
 /*
  * A class that can be used to force a particular incumbent script on the stack.
  */
 class AutoIncumbentScript : protected ScriptSettingsStackEntry {
 public:
   AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -44,26 +44,35 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
 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)
   , mExceptionHandling(aExceptionHandling)
   , mIsMainThread(NS_IsMainThread())
 {
   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,
   // we'll need to do the script entry point bits once we have an actual
   // callable.
 
   // First, find the real underlying callback.
   JSObject* realCallback = js::UncheckedUnwrap(aCallback->CallbackPreserveColor());
@@ -107,16 +116,17 @@ CallbackObject::CallSetup::CallSetup(Cal
     // Bail out if there's no useful global. This seems to happen intermittently
     // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning
     // null in some kind of teardown state.
     if (!globalObject->GetGlobalJSObject()) {
       return;
     }
 
     mAutoEntryScript.construct(globalObject, mIsMainThread, cx);
+    mAutoEntryScript.ref().SetWebIDLCallerPrincipal(webIDLCallerPrincipal);
     if (aCallback->IncumbentGlobalOrNull()) {
       mAutoIncumbentScript.construct(aCallback->IncumbentGlobalOrNull());
     }
 
     // Unmark the callable (by invoking Callback() and not the CallbackPreserveColor()
     // variant), and stick it in a Rooted before it can go gray again.
     // Nothing before us in this function can trigger a CC, so it's safe to wait
     // until here it do the unmark. This allows us to order the following two
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -149,20 +149,21 @@ protected:
     /**
      * A class that performs whatever setup we need to safely make a
      * call while this class is on the stack, After the constructor
      * returns, the call is safe to make if GetContext() returns
      * non-null.
      */
   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
     {
       return mCx;
     }
 
   private:
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -11284,17 +11284,18 @@ class CallbackMember(CGNativeMember):
     def getCallSetup(self):
         if self.needThisHandling:
             # It's been done for us already
             return ""
         callSetup = "CallSetup s(this, aRv"
         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 += ");"
         return string.Template(
             "${callSetup}\n"
             "JSContext* cx = s.GetContext();\n"
             "if (!cx) {\n"
             "  aRv.Throw(NS_ERROR_UNEXPECTED);\n"