Bug 1051224 - Add an opt-out for cross-origin argument checking. r=gabor

This commit is contained in:
Bobby Holley 2014-09-03 13:02:36 -07:00
parent eee8b5ab2e
commit 89f2ba9b62
4 changed files with 82 additions and 10 deletions

View File

@ -83,8 +83,12 @@ StackScopedCloneRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t ta
if (!JS_WrapObject(cx, &obj))
return nullptr;
if (!xpc::NewFunctionForwarder(cx, JSID_VOIDHANDLE, obj, &functionValue))
FunctionForwarderOptions forwarderOptions;
if (!xpc::NewFunctionForwarder(cx, JSID_VOIDHANDLE, obj, forwarderOptions,
&functionValue))
{
return nullptr;
}
return &functionValue.toObject();
}
@ -200,8 +204,13 @@ StackScopedClone(JSContext *cx, StackScopedCloneOptions &options,
// Note - This function mirrors the logic of CheckPassToChrome in
// ChromeObjectWrapper.cpp.
static bool
CheckSameOriginArg(JSContext *cx, HandleValue v)
CheckSameOriginArg(JSContext *cx, FunctionForwarderOptions &options, HandleValue v)
{
// Consumers can explicitly opt out of this security check. This is used in
// the web console to allow the utility functions to accept cross-origin Windows.
if (options.allowCrossOriginArguments)
return true;
// Primitives are fine.
if (!v.isObject())
return true;
@ -232,6 +241,13 @@ FunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Grab the options from the reserved slot.
RootedObject optionsObj(cx, &js::GetFunctionNativeReserved(&args.callee(), 1).toObject());
FunctionForwarderOptions options(cx, optionsObj);
if (!options.Parse())
return false;
// Grab and unwrap the underlying callable.
RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
RootedObject unwrappedFun(cx, js::UncheckedUnwrap(&v.toObject()));
@ -247,11 +263,11 @@ FunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
JSAutoCompartment ac(cx, unwrappedFun);
RootedValue thisVal(cx, ObjectValue(*thisObj));
if (!CheckSameOriginArg(cx, thisVal) || !JS_WrapObject(cx, &thisObj))
if (!CheckSameOriginArg(cx, options, thisVal) || !JS_WrapObject(cx, &thisObj))
return false;
for (size_t n = 0; n < args.length(); ++n) {
if (!CheckSameOriginArg(cx, args[n]) || !JS_WrapValue(cx, args[n]))
if (!CheckSameOriginArg(cx, options, args[n]) || !JS_WrapValue(cx, args[n]))
return false;
}
@ -266,7 +282,7 @@ FunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
bool
NewFunctionForwarder(JSContext *cx, HandleId idArg, HandleObject callable,
MutableHandleValue vp)
FunctionForwarderOptions &options, MutableHandleValue vp)
{
RootedId id(cx, idArg);
if (id == JSID_VOIDHANDLE)
@ -277,8 +293,17 @@ NewFunctionForwarder(JSContext *cx, HandleId idArg, HandleObject callable,
if (!fun)
return false;
JSObject *funobj = JS_GetFunctionObject(fun);
// Stash the callable in slot 0.
AssertSameCompartment(cx, callable);
RootedObject funobj(cx, JS_GetFunctionObject(fun));
js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable));
// Stash the options in slot 1.
RootedObject optionsObj(cx, options.ToJSObject(cx));
if (!optionsObj)
return false;
js::SetFunctionNativeReserved(funobj, 1, ObjectValue(*optionsObj));
vp.setObject(*funobj);
return true;
}
@ -349,7 +374,9 @@ ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleV
// And now, let's create the forwarder function in the target compartment
// for the function the be exported.
if (!NewFunctionForwarder(cx, id, funObj, rval)) {
FunctionForwarderOptions forwarderOptions;
forwarderOptions.allowCrossOriginArguments = options.allowCrossOriginArguments;
if (!NewFunctionForwarder(cx, id, funObj, forwarderOptions, rval)) {
JS_ReportError(cx, "Exporting function failed");
return false;
}

View File

@ -3071,7 +3071,8 @@ nsXPCComponents_Utils::MakeObjectPropsNormal(HandleValue vobj, JSContext *cx)
if (!js::IsWrapper(propobj) || !JS_ObjectIsCallable(cx, propobj))
continue;
if (!NewFunctionForwarder(cx, id, propobj, &v) ||
FunctionForwarderOptions forwarderOptions;
if (!NewFunctionForwarder(cx, id, propobj, forwarderOptions, &v) ||
!JS_SetPropertyById(cx, obj, id, v))
return NS_ERROR_FAILURE;
}

View File

@ -3363,9 +3363,10 @@ Btoa(JSContext *cx, unsigned argc, jsval *vp);
// Helper function that creates a JSFunction that wraps a native function that
// forwards the call to the original 'callable'.
class FunctionForwarderOptions;
bool
NewFunctionForwarder(JSContext *cx, JS::HandleId id, JS::HandleObject callable,
JS::MutableHandleValue vp);
FunctionForwarderOptions &options, JS::MutableHandleValue vp);
// Old fashioned xpc error reporter. Try to use JS_ReportError instead.
nsresult
@ -3479,13 +3480,46 @@ public:
JSObject* options = nullptr)
: OptionsBase(cx, options)
, defineAs(cx, JSID_VOID)
, allowCrossOriginArguments(false)
{ }
virtual bool Parse() {
return ParseId("defineAs", &defineAs);
return ParseId("defineAs", &defineAs) &&
ParseBoolean("allowCrossOriginArguments", &allowCrossOriginArguments);
}
JS::RootedId defineAs;
bool allowCrossOriginArguments;
};
class MOZ_STACK_CLASS FunctionForwarderOptions : public OptionsBase {
public:
explicit FunctionForwarderOptions(JSContext *cx = xpc_GetSafeJSContext(),
JSObject* options = nullptr)
: OptionsBase(cx, options)
, allowCrossOriginArguments(false)
{ }
JSObject *ToJSObject(JSContext *cx) {
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), global));
if (!obj)
return nullptr;
JS::RootedValue val(cx);
unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
val = JS::BooleanValue(allowCrossOriginArguments);
if (!JS_DefineProperty(cx, obj, "allowCrossOriginArguments", val, attrs))
return nullptr;
return obj;
}
virtual bool Parse() {
return ParseBoolean("allowCrossOriginArguments", &allowCrossOriginArguments);
}
bool allowCrossOriginArguments;
};
class MOZ_STACK_CLASS StackScopedCloneOptions : public OptionsBase {

View File

@ -35,6 +35,7 @@ function run_test() {
wasCalled = false;
}
exportFunction(funToExport, subsb, { defineAs: "imported", allowCallbacks: true });
exportFunction((x) => x, subsb, { defineAs: "echoAllowXO", allowCallbacks: true, allowCrossOriginArguments: true });
}.toSource() + ")()", epsb);
subsb.xrayed = Cu.evalInSandbox("(" + function () {
@ -64,6 +65,15 @@ function run_test() {
do_check_true(/denied|insecure/.test(e));
}
// Callers can opt-out of the above.
subsb.xoNative = Cu.evalInSandbox('new XMLHttpRequest()', xorigsb);
try {
do_check_eq(Cu.evalInSandbox('echoAllowXO(xoNative)', subsb), subsb.xoNative);
do_check_true(true);
} catch (e) {
do_check_true(false);
}
// Apply should work and |this| should carry over appropriately.
Cu.evalInSandbox("(" + function() {
var someThis = {};