diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 2af889443b78..5c7df16e14ab 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -967,9 +967,30 @@ SaveStack(JSContext *cx, unsigned argc, jsval *vp) maxFrameCount = d; } - Rooted stack(cx); - if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount)) + JSCompartment *targetCompartment = cx->compartment(); + if (args.length() >= 2) { + if (!args[1].isObject()) { + js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, + JSDVG_SEARCH_STACK, args[0], JS::NullPtr(), + "not an object", NULL); + return false; + } + RootedObject obj(cx, UncheckedUnwrap(&args[1].toObject())); + if (!obj) + return false; + targetCompartment = obj->compartment(); + } + + RootedObject stack(cx); + { + AutoCompartment ac(cx, targetCompartment); + if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount)) + return false; + } + + if (stack && !cx->compartment()->wrap(cx, &stack)) return false; + args.rval().setObjectOrNull(stack); return true; } @@ -2396,13 +2417,15 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = { " SavedStacks cache."), JS_FN_HELP("saveStack", SaveStack, 0, 0, -"saveStack()", -" Capture a stack.\n"), +"saveStack([maxDepth [, compartment]])", +" Capture a stack. If 'maxDepth' is given, capture at most 'maxDepth' number\n" +" of frames. If 'compartment' is given, allocate the js::SavedFrame instances\n" +" with the given object's compartment."), JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0, "enableTrackAllocations()", -" Start capturing the JS stack at every allocation. Note that this sets an " -" object metadata callback that will override any other object metadata " +" Start capturing the JS stack at every allocation. Note that this sets an\n" +" object metadata callback that will override any other object metadata\n" " callback that may be set."), JS_FN_HELP("disableTrackAllocations", DisableTrackAllocations, 0, 0, diff --git a/js/src/jit-test/tests/saved-stacks/principals-03.js b/js/src/jit-test/tests/saved-stacks/principals-03.js new file mode 100644 index 000000000000..006b4477cd67 --- /dev/null +++ b/js/src/jit-test/tests/saved-stacks/principals-03.js @@ -0,0 +1,23 @@ +// With arrows representing child-to-parent links, create a SavedFrame stack +// like this: +// +// high.a -> low.b +// +// in `low`'s compartment and give `low` a reference to this stack. Assert the +// stack's youngest frame's properties doesn't leak information about `high.a` +// that `low` shouldn't have access to, and instead returns information about +// `low.b`. + +var low = newGlobal({ principal: 0 }); +var high = newGlobal({ principal: 0xfffff }); + +low.high = high; +high.low = low; + +high.eval("function a() { return saveStack(0, low); }"); +low.eval("function b() { return high.a(); }") + +var stack = low.b(); + +assertEq(stack.functionDisplayName, "b"); +assertEq(stack.parent, null); diff --git a/js/src/jit-test/tests/saved-stacks/principals-04.js b/js/src/jit-test/tests/saved-stacks/principals-04.js new file mode 100644 index 000000000000..3a9b57800584 --- /dev/null +++ b/js/src/jit-test/tests/saved-stacks/principals-04.js @@ -0,0 +1,15 @@ +// Test what happens when a compartment gets a SavedFrame that it doesn't have +// the principals to access any of its frames. + +var low = newGlobal({ principal: 0 }); +var high = newGlobal({ principal: 0xfffff }); + +low.high = high; +high.low = low; + +high.eval("function a() { return saveStack(1, low); }"); +var stack = low.eval("high.a();") + +assertEq(stack.functionDisplayName, null); +assertEq(stack.parent, null); +assertEq(stack.toString(), ""); diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index 0e2a9cb82e8d..bb3d233395f4 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -309,21 +309,42 @@ SavedFrame::construct(JSContext *cx, unsigned argc, Value *vp) return false; } -/* static */ SavedFrame * -SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName) +// Return the first SavedFrame in the chain that starts with |frame| whose +// principals are subsumed by |principals|, according to |subsumes|. If there is +// no such frame, return nullptr. +static SavedFrame * +GetFirstSubsumedFrame(JSContext *cx, SavedFrame *frame) +{ + JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; + if (!subsumes) + return frame; + + JSPrincipals *principals = cx->compartment()->principals; + if (!principals) + return frame; + + while (frame && !subsumes(principals, frame->getPrincipals())) + frame = frame->getParent(); + + return frame; +} + +/* static */ bool +SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName, + MutableHandleSavedFrame frame) { const Value &thisValue = args.thisv(); if (!thisValue.isObject()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); - return nullptr; + return false; } JSObject &thisObject = thisValue.toObject(); if (!thisObject.is()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, SavedFrame::class_.name, fnName, thisObject.getClass()->name); - return nullptr; + return false; } // Check for SavedFrame.prototype, which has the same class as SavedFrame @@ -332,10 +353,13 @@ SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName) if (thisObject.as().getReservedSlot(JSSLOT_SOURCE).isNull()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, SavedFrame::class_.name, fnName, "prototype object"); - return nullptr; + return false; } - return &thisObject.as(); + // The caller might not have the principals to see this frame's data, so get + // the first one they _do_ have access to. + frame.set(GetFirstSubsumedFrame(cx, &thisObject.as())); + return true; } // Get the SavedFrame * from the current this value and handle any errors that @@ -346,19 +370,24 @@ SavedFrame::checkThis(JSContext *cx, CallArgs &args, const char *fnName) // - unsigned argc // - Value *vp // - const char *fnName +// - Value defaultVal // These parameters will be defined after calling this macro: // - CallArgs args // - Rooted frame (will be non-null) -#define THIS_SAVEDFRAME(cx, argc, vp, fnName, args, frame) \ - CallArgs args = CallArgsFromVp(argc, vp); \ - RootedSavedFrame frame(cx, checkThis(cx, args, fnName)); \ - if (!frame) \ - return false +#define THIS_SAVEDFRAME(cx, argc, vp, fnName, defaultVal, args, frame) \ + CallArgs args = CallArgsFromVp(argc, vp); \ + RootedSavedFrame frame(cx); \ + if (!checkThis(cx, args, fnName, &frame)) \ + return false; \ + if (!frame) { \ + args.rval().set(defaultVal); \ + return true; \ + } /* static */ bool SavedFrame::sourceProperty(JSContext *cx, unsigned argc, Value *vp) { - THIS_SAVEDFRAME(cx, argc, vp, "(get source)", args, frame); + THIS_SAVEDFRAME(cx, argc, vp, "(get source)", NullValue(), args, frame); args.rval().setString(frame->getSource()); return true; } @@ -366,7 +395,7 @@ SavedFrame::sourceProperty(JSContext *cx, unsigned argc, Value *vp) /* static */ bool SavedFrame::lineProperty(JSContext *cx, unsigned argc, Value *vp) { - THIS_SAVEDFRAME(cx, argc, vp, "(get line)", args, frame); + THIS_SAVEDFRAME(cx, argc, vp, "(get line)", NullValue(), args, frame); uint32_t line = frame->getLine(); args.rval().setNumber(line); return true; @@ -375,7 +404,7 @@ SavedFrame::lineProperty(JSContext *cx, unsigned argc, Value *vp) /* static */ bool SavedFrame::columnProperty(JSContext *cx, unsigned argc, Value *vp) { - THIS_SAVEDFRAME(cx, argc, vp, "(get column)", args, frame); + THIS_SAVEDFRAME(cx, argc, vp, "(get column)", NullValue(), args, frame); uint32_t column = frame->getColumn(); args.rval().setNumber(column); return true; @@ -384,7 +413,7 @@ SavedFrame::columnProperty(JSContext *cx, unsigned argc, Value *vp) /* static */ bool SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp) { - THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", args, frame); + THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", NullValue(), args, frame); RootedAtom name(cx, frame->getFunctionDisplayName()); if (name) args.rval().setString(name); @@ -396,30 +425,21 @@ SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp) /* static */ bool SavedFrame::parentProperty(JSContext *cx, unsigned argc, Value *vp) { - THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", args, frame); - JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; - JSPrincipals *principals = cx->compartment()->principals; - - do - frame = frame->getParent(); - while (frame && principals && subsumes && - !subsumes(principals, frame->getPrincipals())); - - args.rval().setObjectOrNull(frame); + THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", NullValue(), args, frame); + args.rval().setObjectOrNull(GetFirstSubsumedFrame(cx, frame->getParent())); return true; } /* static */ bool SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp) { - THIS_SAVEDFRAME(cx, argc, vp, "toString", args, frame); + THIS_SAVEDFRAME(cx, argc, vp, "toString", StringValue(cx->runtime()->emptyString), args, frame); StringBuffer sb(cx); - JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; - JSPrincipals *principals = cx->compartment()->principals; + DebugOnly subsumes = cx->runtime()->securityCallbacks->subsumes; + DebugOnly principals = cx->compartment()->principals; do { - if (principals && subsumes && !subsumes(principals, frame->getPrincipals())) - continue; + MOZ_ASSERT_IF(principals && subsumes, (*subsumes)(principals, frame->getPrincipals())); if (frame->isSelfHosted()) continue; @@ -435,7 +455,7 @@ SavedFrame::toStringMethod(JSContext *cx, unsigned argc, Value *vp) { return false; } - } while ((frame = frame->getParent())); + } while ((frame = GetFirstSubsumedFrame(cx, frame->getParent()))); JSString *str = sb.finishString(); if (!str) diff --git a/js/src/vm/SavedStacks.h b/js/src/vm/SavedStacks.h index 73e10465cbb7..a73fa5d3f1cf 100644 --- a/js/src/vm/SavedStacks.h +++ b/js/src/vm/SavedStacks.h @@ -14,6 +14,11 @@ namespace js { +class SavedFrame; +typedef JS::Handle HandleSavedFrame; +typedef JS::MutableHandle MutableHandleSavedFrame; +typedef JS::Rooted RootedSavedFrame; + class SavedFrame : public NativeObject { friend class SavedStacks; @@ -82,13 +87,10 @@ class SavedFrame : public NativeObject { bool parentMoved(); void updatePrivateParent(); - static SavedFrame *checkThis(JSContext *cx, CallArgs &args, const char *fnName); + static bool checkThis(JSContext *cx, CallArgs &args, const char *fnName, + MutableHandleSavedFrame frame); }; -typedef JS::Handle HandleSavedFrame; -typedef JS::MutableHandle MutableHandleSavedFrame; -typedef JS::Rooted RootedSavedFrame; - struct SavedFrame::HashPolicy { typedef SavedFrame::Lookup Lookup;