Bug 739825 - Push principals when entering compartments in structured clone. r=mrbkap
authorBobby Holley <bobbyholley@gmail.com>
Thu, 05 Apr 2012 14:02:34 -0700
changeset 91112 6120308406f3824d69315c9b6b75ef432e7b9f2d
parent 91111 522fb722cc0960a94c223300c7d9830ffcafc5f3
child 91113 3780b09790a08f1eb1c2d0d25a24154b7b889e17
push id667
push usertim.taubert@gmx.de
push dateTue, 10 Apr 2012 10:56:50 +0000
treeherderfx-team@6fe5b0271cd1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs739825
milestone14.0a1
Bug 739825 - Push principals when entering compartments in structured clone. r=mrbkap
caps/src/nsScriptSecurityManager.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsclone.cpp
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -193,16 +193,54 @@ GetPrincipalDomainOrigin(nsIPrincipal* a
 }
 
 static nsIScriptContext *
 GetScriptContext(JSContext *cx)
 {
     return GetScriptContextFromJSContext(cx);
 }
 
+// Callbacks for the JS engine to use to push/pop context principals.
+static JSBool
+PushPrincipalCallback(JSContext *cx, JSPrincipals *principals)
+{
+    // We should already be in the compartment of the given principal.
+    MOZ_ASSERT(principals ==
+               JS_GetCompartmentPrincipals((js::GetContextCompartment(cx))));
+
+    // Get the security manager.
+    nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
+    if (!ssm) {
+        return true;
+    }
+
+    // Push the principal.
+    JSStackFrame *fp = NULL;
+    nsresult rv = ssm->PushContextPrincipal(cx, JS_FrameIterator(cx, &fp),
+                                            nsJSPrincipals::get(principals));
+    if (NS_FAILED(rv)) {
+        JS_ReportOutOfMemory(cx);
+        return false;
+    }
+
+    return true;
+}
+
+static JSBool
+PopPrincipalCallback(JSContext *cx)
+{
+    nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
+    if (ssm) {
+        ssm->PopContextPrincipal(cx);
+    }
+
+    return true;
+}
+
+
 inline void SetPendingException(JSContext *cx, const char *aMsg)
 {
     JSAutoRequest ar(cx);
     JS_ReportError(cx, "%s", aMsg);
 }
 
 inline void SetPendingException(JSContext *cx, const PRUnichar *aMsg)
 {
@@ -3386,17 +3424,19 @@ nsresult nsScriptSecurityManager::Init()
 
     rv = runtimeService->GetRuntime(&sRuntime);
     NS_ENSURE_SUCCESS(rv, rv);
 
     static const JSSecurityCallbacks securityCallbacks = {
         CheckObjectAccess,
         nsJSPrincipals::Subsume,
         ObjectPrincipalFinder,
-        ContentSecurityPolicyPermitsJSAction
+        ContentSecurityPolicyPermitsJSAction,
+        PushPrincipalCallback,
+        PopPrincipalCallback
     };
 
     MOZ_ASSERT(!JS_GetSecurityCallbacks(sRuntime));
     JS_SetSecurityCallbacks(sRuntime, &securityCallbacks);
     JS_InitDestroyPrincipalsCallback(sRuntime, nsJSPrincipals::Destroy);
 
     JS_SetTrustedPrincipals(sRuntime, system);
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1432,17 +1432,17 @@ void
 JSAutoEnterCompartment::enterAndIgnoreErrors(JSContext *cx, JSObject *target)
 {
     (void) enter(cx, target);
 }
 
 JSAutoEnterCompartment::~JSAutoEnterCompartment()
 {
     if (state == STATE_OTHER_COMPARTMENT) {
-        AutoCompartment* ac = reinterpret_cast<AutoCompartment*>(bytes);
+        AutoCompartment* ac = getAutoCompartment();
         CHECK_REQUEST(ac->context);
         ac->~AutoCompartment();
     }
 }
 
 namespace JS {
 
 bool
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1576,16 +1576,26 @@ typedef JSPrincipals *
 /*
  * Used to check if a CSP instance wants to disable eval() and friends.
  * See js_CheckCSPPermitsJSAction() in jsobj.
  */
 typedef JSBool
 (* JSCSPEvalChecker)(JSContext *cx);
 
 /*
+ * Security callbacks for pushing and popping context principals. These are only
+ * temporarily necessary and will hopefully be gone again in a matter of weeks.
+ */
+typedef JSBool
+(* JSPushContextPrincipalOp)(JSContext *cx, JSPrincipals *principals);
+
+typedef JSBool
+(* JSPopContextPrincipalOp)(JSContext *cx);
+
+/*
  * Callback used to ask the embedding for the cross compartment wrapper handler
  * that implements the desired prolicy for this kind of object in the
  * destination compartment.
  */
 typedef JSObject *
 (* JSWrapObjectCallback)(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent,
                          unsigned flags);
 
@@ -2718,28 +2728,38 @@ js_TransplantObjectWithWrapper(JSContext
                                JSObject *origobj,
                                JSObject *origwrapper,
                                JSObject *targetobj,
                                JSObject *targetwrapper);
 
 #ifdef __cplusplus
 JS_END_EXTERN_C
 
+namespace js {
+struct AutoCompartment;
+}
+
 class JS_PUBLIC_API(JSAutoEnterCompartment)
 {
     /*
      * This is a poor man's Maybe<AutoCompartment>, because we don't have
      * access to the AutoCompartment definition here.  We statically assert in
      * jsapi.cpp that we have the right size here.
      *
      * In practice, 32-bit Windows and Android get 16-word |bytes|, while
      * other platforms get 13-word |bytes|.
      */
     void* bytes[sizeof(void*) == 4 && MOZ_ALIGNOF(uint64_t) == 8 ? 16 : 13];
 
+  protected:
+    js::AutoCompartment *getAutoCompartment() {
+        JS_ASSERT(state == STATE_OTHER_COMPARTMENT);
+        return reinterpret_cast<js::AutoCompartment*>(bytes);
+    }
+
     /*
      * This object may be in one of three states.  If enter() or
      * enterAndIgnoreErrors() hasn't been called, it's in STATE_UNENTERED.
      * Otherwise, if we were asked to enter into the current compartment, our
      * state is STATE_SAME_COMPARTMENT.  If we actually created an
      * AutoCompartment and entered another compartment, our state is
      * STATE_OTHER_COMPARTMENT.
      */
@@ -4229,16 +4249,18 @@ JS_HoldPrincipals(JSPrincipals *principa
 extern JS_PUBLIC_API(void)
 JS_DropPrincipals(JSRuntime *rt, JSPrincipals *principals);
 
 struct JSSecurityCallbacks {
     JSCheckAccessOp            checkObjectAccess;
     JSSubsumePrincipalsOp      subsumePrincipals;
     JSObjectPrincipalsFinder   findObjectPrincipals;
     JSCSPEvalChecker           contentSecurityPolicyAllows;
+    JSPushContextPrincipalOp   pushContextPrincipal;
+    JSPopContextPrincipalOp    popContextPrincipal;
 };
 
 extern JS_PUBLIC_API(void)
 JS_SetSecurityCallbacks(JSRuntime *rt, const JSSecurityCallbacks *callbacks);
 
 extern JS_PUBLIC_API(const JSSecurityCallbacks *)
 JS_GetSecurityCallbacks(JSRuntime *rt);
 
--- a/js/src/jsclone.cpp
+++ b/js/src/jsclone.cpp
@@ -505,16 +505,44 @@ JSStructuredCloneWriter::startObject(JSO
     if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
         return false;
     checkStack();
 
     /* Write the header for obj. */
     return out.writePair(obj->isArray() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
 }
 
+class AutoEnterCompartmentAndPushPrincipal : public JSAutoEnterCompartment
+{
+  public:
+    bool enter(JSContext *cx, JSObject *target) {
+        // First, enter the compartment.
+        if (!JSAutoEnterCompartment::enter(cx, target))
+            return false;
+
+        // We only need to push a principal if we changed compartments.
+        if (state != STATE_OTHER_COMPARTMENT)
+            return true;
+
+        // Push.
+        const JSSecurityCallbacks *cb = cx->runtime->securityCallbacks;
+        return cb->pushContextPrincipal(cx, target->principals(cx));
+    };
+
+    ~AutoEnterCompartmentAndPushPrincipal() {
+        // Pop the principal if necessary.
+        if (state == STATE_OTHER_COMPARTMENT) {
+            AutoCompartment *ac = getAutoCompartment();
+            const JSSecurityCallbacks *cb = ac->context->runtime->securityCallbacks;
+            cb->popContextPrincipal(ac->context);
+        }
+    };
+};
+
+
 bool
 JSStructuredCloneWriter::startWrite(const js::Value &v)
 {
     assertSameCompartment(context(), v);
 
     if (v.isString()) {
         return writeString(SCTAG_STRING, v.toString());
     } else if (v.isNumber()) {
@@ -531,17 +559,17 @@ JSStructuredCloneWriter::startWrite(cons
         // The object might be a security wrapper. See if we can clone what's
         // behind it. If we can, unwrap the object.
         obj = UnwrapObjectChecked(context(), obj);
         if (!obj)
             return false;
 
         // If we unwrapped above, we'll need to enter the underlying compartment.
         // Let the AutoEnterCompartment do the right thing for us.
-        JSAutoEnterCompartment ac;
+        AutoEnterCompartmentAndPushPrincipal ac;
         if (!ac.enter(context(), obj))
             return false;
 
         if (obj->isRegExp()) {
             RegExpObject &reobj = obj->asRegExp();
             return out.writePair(SCTAG_REGEXP_OBJECT, reobj.getFlags()) &&
                    writeString(SCTAG_STRING, reobj.getSource());
         } else if (obj->isDate()) {
@@ -576,17 +604,17 @@ JSStructuredCloneWriter::write(const Val
 {
     if (!startWrite(v))
         return false;
 
     while (!counts.empty()) {
         JSObject *obj = &objs.back().toObject();
 
         // The objects in |obj| can live in other compartments.
-        JSAutoEnterCompartment ac;
+        AutoEnterCompartmentAndPushPrincipal ac;
         if (!ac.enter(context(), obj))
             return false;
 
         if (counts.back()) {
             counts.back()--;
             jsid id = ids.back();
             ids.popBack();
             checkStack();