Bug 924905 - Do a subsumes check inside the stack iterator. r=jandem, a=lsblakk
authorLuke Wagner <luke@mozilla.com>
Fri, 03 Jan 2014 10:32:56 -0600
changeset 175602 c79b3ec59dff04c3a6f48be7d66f1d96fb1b7f34
parent 175601 d92422d01e6d035a1872d4da7b62773d87a244fa
child 175603 fac124f2dbb4a001e1ab4e4cec7276dae7379faa
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, lsblakk
bugs924905
milestone28.0a2
Bug 924905 - Do a subsumes check inside the stack iterator. r=jandem, a=lsblakk
caps/include/nsScriptSecurityManager.h
caps/src/nsScriptSecurityManager.cpp
js/public/Class.h
js/src/jsapi.h
js/src/vm/OldDebugAPI.cpp
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -371,21 +371,24 @@ private:
     virtual ~nsScriptSecurityManager();
 
     bool SubjectIsPrivileged();
 
     static bool
     CheckObjectAccess(JSContext *cx, JS::Handle<JSObject*> obj,
                       JS::Handle<jsid> id, JSAccessMode mode,
                       JS::MutableHandle<JS::Value> vp);
-    
+
     // Decides, based on CSP, whether or not eval() and stuff can be executed.
     static bool
     ContentSecurityPolicyPermitsJSAction(JSContext *cx);
 
+    static bool
+    JSPrincipalsSubsume(JSPrincipals *first, JSPrincipals *second);
+
     // Returns null if a principal cannot be found; generally callers
     // should error out at that point.
     static nsIPrincipal* doGetObjectPrincipal(JSObject* obj);
 
     // Returns null if a principal cannot be found.  Note that rv can be NS_OK
     // when this happens -- this means that there was no JS running.
     nsIPrincipal*
     doGetSubjectPrincipal(nsresult* rv);
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -487,17 +487,16 @@ nsScriptSecurityManager::ContentSecurity
                                  scriptSample,
                                  lineNum,
                                  EmptyString());
     }
 
     return evalOK;
 }
 
-
 bool
 nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JS::Handle<JSObject*> obj,
                                            JS::Handle<jsid> id, JSAccessMode mode,
                                            JS::MutableHandle<JS::Value> vp)
 {
     // Get the security manager
     nsScriptSecurityManager *ssm =
         nsScriptSecurityManager::GetScriptSecurityManager();
@@ -525,16 +524,25 @@ nsScriptSecurityManager::CheckObjectAcce
                                  (int32_t)nsIXPCSecurityManager::ACCESS_GET_PROPERTY);
 
     if (NS_FAILED(rv))
         return false; // Security check failed (XXX was an error reported?)
 
     return true;
 }
 
+// static
+bool
+nsScriptSecurityManager::JSPrincipalsSubsume(JSPrincipals *first,
+                                             JSPrincipals *second)
+{
+    return nsJSPrincipals::get(first)->Subsumes(nsJSPrincipals::get(second));
+}
+
+
 NS_IMETHODIMP
 nsScriptSecurityManager::CheckPropertyAccess(JSContext* cx,
                                              JSObject* aJSObject,
                                              const char* aClassName,
                                              jsid aProperty,
                                              uint32_t aAction)
 {
     return CheckPropertyAccessImpl(aAction, nullptr, cx, aJSObject,
@@ -2132,17 +2140,18 @@ nsresult nsScriptSecurityManager::Init()
 
     //-- Register security check callback in the JS engine
     //   Currently this is used to control access to function.caller
     rv = nsXPConnect::XPConnect()->GetRuntime(&sRuntime);
     NS_ENSURE_SUCCESS(rv, rv);
 
     static const JSSecurityCallbacks securityCallbacks = {
         CheckObjectAccess,
-        ContentSecurityPolicyPermitsJSAction
+        ContentSecurityPolicyPermitsJSAction,
+        JSPrincipalsSubsume,
     };
 
     MOZ_ASSERT(!JS_GetSecurityCallbacks(sRuntime));
     JS_SetSecurityCallbacks(sRuntime, &securityCallbacks);
     JS_InitDestroyPrincipalsCallback(sRuntime, nsJSPrincipals::Destroy);
 
     JS_SetTrustedPrincipals(sRuntime, system);
 
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -267,16 +267,22 @@ struct JSStringFinalizer {
 // JSClass.checkAccess type: check whether obj[id] may be accessed per mode,
 // returning false on error/exception, true on success with obj[id]'s last-got
 // value in *vp, and its attributes in *attrsp.  As for JSPropertyOp above, id
 // is either a string or an int jsval.
 typedef bool
 (* JSCheckAccessOp)(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                     JSAccessMode mode, JS::MutableHandle<JS::Value> vp);
 
+// Return whether the first principal subsumes the second. The exact meaning of
+// 'subsumes' is left up to the browser. Subsumption is checked inside the JS
+// engine when determining, e.g., which stack frames to display in a backtrace.
+typedef bool
+(* JSSubsumesOp)(JSPrincipals *first, JSPrincipals *second);
+
 // Check whether v is an instance of obj.  Return false on error or exception,
 // true on success with true in *bp if v is an instance of obj, false in
 // *bp otherwise.
 typedef bool
 (* JSHasInstanceOp)(JSContext *cx, JS::Handle<JSObject*> obj, JS::MutableHandle<JS::Value> vp,
                     bool *bp);
 
 // Function type for trace operation of the class called to enumerate all
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3177,16 +3177,17 @@ extern JS_PUBLIC_API(void)
 JS_HoldPrincipals(JSPrincipals *principals);
 
 extern JS_PUBLIC_API(void)
 JS_DropPrincipals(JSRuntime *rt, JSPrincipals *principals);
 
 struct JSSecurityCallbacks {
     JSCheckAccessOp            checkObjectAccess;
     JSCSPEvalChecker           contentSecurityPolicyAllows;
+    JSSubsumesOp               subsumes;
 };
 
 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/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -1347,17 +1347,21 @@ JSAbstractFramePtr::evaluateUCInStackFra
 
     js::AutoCompartment ac(cx, env);
     return EvaluateInEnv(cx, env, thisv, frame, StableCharPtr(chars, length), length,
                          filename, lineno, rval);
 }
 
 JSBrokenFrameIterator::JSBrokenFrameIterator(JSContext *cx)
 {
-    NonBuiltinScriptFrameIter iter(cx);
+    // Show all frames on the stack whose principal is subsumed by the current principal.
+    NonBuiltinScriptFrameIter iter(cx,
+                                   ScriptFrameIter::ALL_CONTEXTS,
+                                   ScriptFrameIter::GO_THROUGH_SAVED,
+                                   cx->compartment()->principals);
     data_ = iter.copyData();
 }
 
 JSBrokenFrameIterator::~JSBrokenFrameIterator()
 {
     js_free((ScriptFrameIter::Data *)data_);
 }
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -558,16 +558,28 @@ ScriptFrameIter::settleOnActivation()
         // Skip activations from another context if needed.
         JS_ASSERT(activation->cx());
         JS_ASSERT(data_.cx_);
         if (data_.contextOption_ == CURRENT_CONTEXT && activation->cx() != data_.cx_) {
             ++data_.activations_;
             continue;
         }
 
+        // If the caller supplied principals, only show activations which are subsumed (of the same
+        // origin or of an origin accessible) by these principals.
+        if (data_.principals_) {
+            if (JSSubsumesOp subsumes = data_.cx_->runtime()->securityCallbacks->subsumes) {
+                JS::AutoAssertNoGC nogc;
+                if (!subsumes(data_.principals_, activation->compartment()->principals)) {
+                    ++data_.activations_;
+                    continue;
+                }
+            }
+        }
+
 #ifdef JS_ION
         if (activation->isJit()) {
             data_.ionFrames_ = jit::IonFrameIterator(data_.activations_);
 
             // Stop at the first scripted frame.
             while (!data_.ionFrames_.isScripted() && !data_.ionFrames_.done())
                 ++data_.ionFrames_;
 
@@ -602,56 +614,59 @@ ScriptFrameIter::settleOnActivation()
         JS_ASSERT(!data_.interpFrames_.frame()->runningInJit());
         data_.pc_ = data_.interpFrames_.pc();
         data_.state_ = SCRIPTED;
         return;
     }
 }
 
 ScriptFrameIter::Data::Data(JSContext *cx, PerThreadData *perThread, SavedOption savedOption,
-                            ContextOption contextOption)
+                            ContextOption contextOption, JSPrincipals *principals)
   : perThread_(perThread),
     cx_(cx),
     savedOption_(savedOption),
     contextOption_(contextOption),
+    principals_(principals),
     pc_(nullptr),
     interpFrames_(nullptr),
     activations_(cx->runtime())
 #ifdef JS_ION
   , ionFrames_((uint8_t *)nullptr, SequentialExecution)
 #endif
 {
 }
 
 ScriptFrameIter::Data::Data(const ScriptFrameIter::Data &other)
   : perThread_(other.perThread_),
     cx_(other.cx_),
     savedOption_(other.savedOption_),
     contextOption_(other.contextOption_),
+    principals_(other.principals_),
     state_(other.state_),
     pc_(other.pc_),
     interpFrames_(other.interpFrames_),
     activations_(other.activations_)
 #ifdef JS_ION
   , ionFrames_(other.ionFrames_)
 #endif
 {
 }
 
 ScriptFrameIter::ScriptFrameIter(JSContext *cx, SavedOption savedOption)
-  : data_(cx, &cx->runtime()->mainThread, savedOption, CURRENT_CONTEXT)
+  : data_(cx, &cx->runtime()->mainThread, savedOption, CURRENT_CONTEXT, nullptr)
 #ifdef JS_ION
     , ionInlineFrames_(cx, (js::jit::IonFrameIterator*) nullptr)
 #endif
 {
     settleOnActivation();
 }
 
-ScriptFrameIter::ScriptFrameIter(JSContext *cx, ContextOption contextOption, SavedOption savedOption)
-  : data_(cx, &cx->runtime()->mainThread, savedOption, contextOption)
+ScriptFrameIter::ScriptFrameIter(JSContext *cx, ContextOption contextOption,
+                                 SavedOption savedOption, JSPrincipals *principals)
+  : data_(cx, &cx->runtime()->mainThread, savedOption, contextOption, principals)
 #ifdef JS_ION
     , ionInlineFrames_(cx, (js::jit::IonFrameIterator*) nullptr)
 #endif
 {
     settleOnActivation();
 }
 
 ScriptFrameIter::ScriptFrameIter(const ScriptFrameIter &other)
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1456,34 +1456,35 @@ class ScriptFrameIter
     enum State { DONE, SCRIPTED, JIT };
 
     /*
      * Unlike ScriptFrameIter itself, ScriptFrameIter::Data can be allocated on
      * the heap, so this structure should not contain any GC things.
      */
     struct Data
     {
-        PerThreadData *perThread_;
-        JSContext    *cx_;
-        SavedOption  savedOption_;
-        ContextOption contextOption_;
+        PerThreadData * perThread_;
+        JSContext *     cx_;
+        SavedOption     savedOption_;
+        ContextOption   contextOption_;
+        JSPrincipals *  principals_;
 
-        State        state_;
+        State           state_;
 
-        jsbytecode   *pc_;
+        jsbytecode *    pc_;
 
         InterpreterFrameIterator interpFrames_;
         ActivationIterator activations_;
 
 #ifdef JS_ION
         jit::IonFrameIterator ionFrames_;
 #endif
 
         Data(JSContext *cx, PerThreadData *perThread, SavedOption savedOption,
-             ContextOption contextOption);
+             ContextOption contextOption, JSPrincipals *principals);
         Data(const Data &other);
     };
 
     friend class ::JSBrokenFrameIterator;
   private:
     Data data_;
 #ifdef JS_ION
     jit::InlineFrameIterator ionInlineFrames_;
@@ -1494,17 +1495,17 @@ class ScriptFrameIter
 #ifdef JS_ION
     void nextJitFrame();
     void popJitFrame();
 #endif
     void settleOnActivation();
 
   public:
     ScriptFrameIter(JSContext *cx, SavedOption = STOP_AT_SAVED);
-    ScriptFrameIter(JSContext *cx, ContextOption, SavedOption);
+    ScriptFrameIter(JSContext *cx, ContextOption, SavedOption, JSPrincipals* = nullptr);
     ScriptFrameIter(const ScriptFrameIter &iter);
     ScriptFrameIter(const Data &data);
     ScriptFrameIter(AbstractFramePtr frame);
 
     bool done() const { return data_.state_ == DONE; }
     ScriptFrameIter &operator++();
 
     Data *copyData() const;
@@ -1619,16 +1620,22 @@ class NonBuiltinScriptFrameIter : public
             while (!done() && script()->selfHosted)
                 ScriptFrameIter::operator++();
     }
 
   public:
     NonBuiltinScriptFrameIter(JSContext *cx, ScriptFrameIter::SavedOption opt = ScriptFrameIter::STOP_AT_SAVED)
       : ScriptFrameIter(cx, opt) { settle(); }
 
+    NonBuiltinScriptFrameIter(JSContext *cx,
+                              ScriptFrameIter::ContextOption contextOption,
+                              ScriptFrameIter::SavedOption savedOption,
+                              JSPrincipals *principals = nullptr)
+      : ScriptFrameIter(cx, contextOption, savedOption, principals) { settle(); }
+
     NonBuiltinScriptFrameIter(const ScriptFrameIter::Data &data)
       : ScriptFrameIter(data)
     {}
 
     NonBuiltinScriptFrameIter &operator++() { ScriptFrameIter::operator++(); settle(); return *this; }
 };
 
 /*