Bug 1117242 - Part 2: SavedFrame accessors should always check principals. r=jandem

This commit is contained in:
Nick Fitzgerald 2015-02-06 09:15:01 -08:00
parent 61188901b5
commit 6ae499301c
5 changed files with 125 additions and 42 deletions

View File

@ -967,9 +967,30 @@ SaveStack(JSContext *cx, unsigned argc, jsval *vp)
maxFrameCount = d;
}
Rooted<JSObject*> 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,

View File

@ -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);

View File

@ -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(), "");

View File

@ -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<SavedFrame>()) {
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<SavedFrame>().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<SavedFrame>();
// 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<SavedFrame>()));
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<SavedFrame *> 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<JSSubsumesOp> subsumes = cx->runtime()->securityCallbacks->subsumes;
DebugOnly<JSPrincipals *> 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)

View File

@ -14,6 +14,11 @@
namespace js {
class SavedFrame;
typedef JS::Handle<SavedFrame*> HandleSavedFrame;
typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
typedef JS::Rooted<SavedFrame*> 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<SavedFrame*> HandleSavedFrame;
typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
typedef JS::Rooted<SavedFrame*> RootedSavedFrame;
struct SavedFrame::HashPolicy
{
typedef SavedFrame::Lookup Lookup;