Bug 1137910 part 2. Give chrome callers that are indirectly (e.g. via nsIStackFrame) poking at content-captured stacks the content view of the stack. r=fitzgen
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 03 Mar 2015 07:12:01 -0500
changeset 247020 5816dfd514af9be89d5d1e601b6a63e8b02a7975
parent 247019 5d359ff807ef5cfa7956d145d2f2242208495de8
child 247021 bae236f4b8c129154a4b7e1315d064bdc76ad476
push id884
push userdburns@mozilla.com
push dateTue, 03 Mar 2015 15:29:12 +0000
reviewersfitzgen
bugs1137910
milestone39.0a1
Bug 1137910 part 2. Give chrome callers that are indirectly (e.g. via nsIStackFrame) poking at content-captured stacks the content view of the stack. r=fitzgen
js/src/vm/SavedStacks.cpp
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -3,16 +3,17 @@
  * 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 "vm/SavedStacks.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
 
 #include <math.h>
 
 #include "jsapi.h"
 #include "jscompartment.h"
 #include "jsfriendapi.h"
 #include "jshashutil.h"
 #include "jsmath.h"
@@ -28,16 +29,17 @@
 
 #include "jscntxtinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using mozilla::AddToHash;
 using mozilla::DebugOnly;
 using mozilla::HashString;
+using mozilla::Maybe;
 
 namespace js {
 
 struct SavedFrame::Lookup {
     Lookup(JSAtom *source, uint32_t line, uint32_t column, JSAtom *functionDisplayName,
            SavedFrame *parent, JSPrincipals *principals)
       : source(source),
         line(line),
@@ -387,94 +389,142 @@ SavedFrame::checkThis(JSContext *cx, Cal
     RootedObject frame(cx);                                            \
     if (!checkThis(cx, args, fnName, &frame))                          \
         return false;
 
 } /* namespace js */
 
 namespace JS {
 
+namespace {
+
+// It's possible that our caller is privileged (and hence would see the entire
+// stack) but we're working with an SavedFrame object that was captured in
+// unprivileged code.  If so, drop privileges down to its level.  The idea is
+// that this way devtools code that's asking an exception object for a stack to
+// display will end up with the stack the web developer would see via doing
+// .stack in a web page, with Firefox implementation details excluded.
+//
+// We want callers to pass us the object they were actually passed, not an
+// unwrapped form of it.  That way Xray access to SavedFrame objects should not
+// be affected by AutoMaybeEnterFrameCompartment and the only things that will
+// be affected will be cases in which privileged code works with some C++ object
+// that then pokes at an unprivileged StackFrame it has on hand.
+class MOZ_STACK_CLASS AutoMaybeEnterFrameCompartment
+{
+public:
+    AutoMaybeEnterFrameCompartment(JSContext *cx,
+                                   HandleObject obj
+                                   MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+    {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+        // Note that obj might be null here, since we're doing this
+        // before UnwrapSavedFrame.
+        if (obj && cx->compartment() != obj->compartment())
+        {
+            JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes;
+            if (subsumes && subsumes(cx->compartment()->principals,
+                                     obj->compartment()->principals))
+            {
+                ac_.emplace(cx, obj);
+            }
+        }
+    }
+
+ private:
+    Maybe<JSAutoCompartment> ac_;
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+} // anonymous namespace
+
 static inline js::SavedFrame *
 UnwrapSavedFrame(JSContext *cx, HandleObject obj)
 {
     if (!obj)
         return nullptr;
     RootedObject savedFrameObj(cx, CheckedUnwrap(obj));
     MOZ_ASSERT(savedFrameObj);
     MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj));
     js::RootedSavedFrame frame(cx, &savedFrameObj->as<js::SavedFrame>());
     return GetFirstSubsumedFrame(cx, frame);
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameSource(JSContext *cx, HandleObject savedFrame, MutableHandleString sourcep)
 {
+    AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
     if (!frame) {
         sourcep.set(cx->runtime()->emptyString);
         return SavedFrameResult::AccessDenied;
     }
     sourcep.set(frame->getSource());
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameLine(JSContext *cx, HandleObject savedFrame, uint32_t *linep)
 {
     MOZ_ASSERT(linep);
+    AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
     if (!frame) {
         *linep = 0;
         return SavedFrameResult::AccessDenied;
     }
     *linep = frame->getLine();
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameColumn(JSContext *cx, HandleObject savedFrame, uint32_t *columnp)
 {
     MOZ_ASSERT(columnp);
+    AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
     if (!frame) {
         *columnp = 0;
         return SavedFrameResult::AccessDenied;
     }
     *columnp = frame->getColumn();
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameFunctionDisplayName(JSContext *cx, HandleObject savedFrame, MutableHandleString namep)
 {
+    AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
     if (!frame) {
         namep.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     namep.set(frame->getFunctionDisplayName());
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
 GetSavedFrameParent(JSContext *cx, HandleObject savedFrame, MutableHandleObject parentp)
 {
+    AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
     if (!frame) {
         parentp.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     js::RootedSavedFrame parent(cx, frame->getParent());
     parentp.set(js::GetFirstSubsumedFrame(cx, parent));
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(bool)
 StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString stringp)
 {
+    AutoMaybeEnterFrameCompartment ac(cx, stack);
     js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack));
     if (!frame) {
         stringp.set(cx->runtime()->emptyString);
         return true;
     }
 
     js::StringBuffer sb(cx);
     DebugOnly<JSSubsumesOp> subsumes = cx->runtime()->securityCallbacks->subsumes;