зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1032457 - Implement the |allowCallbacks| parameter to exportFunction. r=gabor
This commit is contained in:
Родитель
e99f0db620
Коммит
2d531b7c33
|
@ -79,7 +79,8 @@ StackScopedCloneRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t ta
|
||||||
if (!JS_WrapObject(cx, &obj))
|
if (!JS_WrapObject(cx, &obj))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if (!xpc::NewFunctionForwarder(cx, JSID_VOIDHANDLE, obj, &functionValue))
|
FunctionForwarderOptions forwarderOptions(cx);
|
||||||
|
if (!xpc::NewFunctionForwarder(cx, JSID_VOIDHANDLE, obj, forwarderOptions, &functionValue))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return &functionValue.toObject();
|
return &functionValue.toObject();
|
||||||
|
@ -139,9 +140,14 @@ StackScopedCloneWrite(JSContext *cx, JSStructuredCloneWriter *writer,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cloneData->mOptions->cloneFunctions && JS_ObjectIsCallable(cx, obj)) {
|
if (JS_ObjectIsCallable(cx, obj)) {
|
||||||
|
if (cloneData->mOptions->cloneFunctions) {
|
||||||
cloneData->mFunctions.append(obj);
|
cloneData->mFunctions.append(obj);
|
||||||
return JS_WriteUint32Pair(writer, SCTAG_FUNCTION, cloneData->mFunctions.length() - 1);
|
return JS_WriteUint32Pair(writer, SCTAG_FUNCTION, cloneData->mFunctions.length() - 1);
|
||||||
|
} else {
|
||||||
|
JS_ReportError(cx, "Permission denied to pass a Function via structured clone");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_ReportError(cx, "Encountered unsupported value type writing stack-scoped structured clone");
|
JS_ReportError(cx, "Encountered unsupported value type writing stack-scoped structured clone");
|
||||||
|
@ -201,17 +207,30 @@ CloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
|
||||||
{
|
{
|
||||||
CallArgs args = CallArgsFromVp(argc, vp);
|
CallArgs args = CallArgsFromVp(argc, vp);
|
||||||
|
|
||||||
RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
|
// Grab the options from the reserved slot.
|
||||||
NS_ASSERTION(v.isObject(), "weird function");
|
RootedObject optionsObj(cx, &js::GetFunctionNativeReserved(&args.callee(), 1).toObject());
|
||||||
RootedObject origFunObj(cx, UncheckedUnwrap(&v.toObject()));
|
FunctionForwarderOptions options(cx, optionsObj);
|
||||||
|
if (!options.Parse())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Grab and unwrap the underlying callable.
|
||||||
|
RootedObject forwarderObj(cx, &js::GetFunctionNativeReserved(&args.callee(), 0).toObject());
|
||||||
|
RootedObject origFunObj(cx, UncheckedUnwrap(forwarderObj));
|
||||||
{
|
{
|
||||||
JSAutoCompartment ac(cx, origFunObj);
|
JSAutoCompartment ac(cx, origFunObj);
|
||||||
// Note: only the arguments are cloned not the |this| or the |callee|.
|
// Note: only the arguments are cloned not the |this| or the |callee|.
|
||||||
// Function forwarder does not use those.
|
// Function forwarder does not use those.
|
||||||
StackScopedCloneOptions options;
|
StackScopedCloneOptions cloneOptions;
|
||||||
options.wrapReflectors = true;
|
cloneOptions.wrapReflectors = true;
|
||||||
for (unsigned i = 0; i < args.length(); i++) {
|
for (unsigned i = 0; i < args.length(); i++) {
|
||||||
if (!StackScopedClone(cx, options, args[i])) {
|
RootedObject argObj(cx, args[i].isObject() ? &args[i].toObject() : nullptr);
|
||||||
|
if (options.allowCallbacks && argObj && JS_ObjectIsCallable(cx, argObj)) {
|
||||||
|
FunctionForwarderOptions innerOptions(cx);
|
||||||
|
if (!JS_WrapObject(cx, &argObj))
|
||||||
|
return false;
|
||||||
|
if (!xpc::NewFunctionForwarder(cx, JSID_VOIDHANDLE, argObj, innerOptions, args[i]))
|
||||||
|
return nullptr;
|
||||||
|
} else if (!StackScopedClone(cx, cloneOptions, args[i])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,7 +263,7 @@ NonCloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
|
||||||
}
|
}
|
||||||
bool
|
bool
|
||||||
NewFunctionForwarder(JSContext *cx, HandleId idArg, HandleObject callable,
|
NewFunctionForwarder(JSContext *cx, HandleId idArg, HandleObject callable,
|
||||||
MutableHandleValue vp)
|
FunctionForwarderOptions &options, MutableHandleValue vp)
|
||||||
{
|
{
|
||||||
RootedId id(cx, idArg);
|
RootedId id(cx, idArg);
|
||||||
if (id == JSID_VOIDHANDLE)
|
if (id == JSID_VOIDHANDLE)
|
||||||
|
@ -255,9 +274,18 @@ NewFunctionForwarder(JSContext *cx, HandleId idArg, HandleObject callable,
|
||||||
if (!fun)
|
if (!fun)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
JSObject *funobj = JS_GetFunctionObject(fun);
|
// Stash the callable in slot 0.
|
||||||
js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable));
|
AssertSameCompartment(cx, callable);
|
||||||
vp.setObject(*funobj);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,7 +367,9 @@ ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleV
|
||||||
|
|
||||||
// And now, let's create the forwarder function in the target compartment
|
// And now, let's create the forwarder function in the target compartment
|
||||||
// for the function the be exported.
|
// for the function the be exported.
|
||||||
if (!NewFunctionForwarder(cx, id, funObj, rval)) {
|
FunctionForwarderOptions forwarderOptions(cx);
|
||||||
|
forwarderOptions.allowCallbacks = options.allowCallbacks;
|
||||||
|
if (!NewFunctionForwarder(cx, id, funObj, forwarderOptions, rval)) {
|
||||||
JS_ReportError(cx, "Exporting function failed");
|
JS_ReportError(cx, "Exporting function failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3281,13 +3281,17 @@ Atob(JSContext *cx, unsigned argc, jsval *vp);
|
||||||
bool
|
bool
|
||||||
Btoa(JSContext *cx, unsigned argc, jsval *vp);
|
Btoa(JSContext *cx, unsigned argc, jsval *vp);
|
||||||
|
|
||||||
|
class FunctionForwarderOptions;
|
||||||
|
|
||||||
// Helper function that creates a JSFunction that wraps a native function that
|
// Helper function that creates a JSFunction that wraps a native function that
|
||||||
// forwards the call to the original 'callable'. Any object-valued arguments are
|
// forwards the call to the original 'callable'. For improved security, any
|
||||||
// cloned at call time for improved security.
|
// object-valued arguments are cloned at call time, unless either:
|
||||||
|
//
|
||||||
|
// * The object is a function and FunctionForwarderOptions::allowCallbacks is set
|
||||||
|
// * The object is a reflector, in which case it is wrapped.
|
||||||
bool
|
bool
|
||||||
NewFunctionForwarder(JSContext *cx, JS::HandleId id, JS::HandleObject callable,
|
NewFunctionForwarder(JSContext *cx, JS::HandleId id, JS::HandleObject callable,
|
||||||
JS::MutableHandleValue vp);
|
FunctionForwarderOptions &options, JS::MutableHandleValue vp);
|
||||||
|
|
||||||
// Old-style function forwarding without structured-cloning for arguments. This
|
// Old-style function forwarding without structured-cloning for arguments. This
|
||||||
// is deprecated.
|
// is deprecated.
|
||||||
|
@ -3404,11 +3408,16 @@ public:
|
||||||
JSObject* options = nullptr)
|
JSObject* options = nullptr)
|
||||||
: OptionsBase(cx, options)
|
: OptionsBase(cx, options)
|
||||||
, defineAs(cx, JSID_VOID)
|
, defineAs(cx, JSID_VOID)
|
||||||
|
, allowCallbacks(false)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
virtual bool Parse() { return ParseId("defineAs", &defineAs); };
|
virtual bool Parse() {
|
||||||
|
return ParseId("defineAs", &defineAs) &&
|
||||||
|
ParseBoolean("allowCallbacks", &allowCallbacks);
|
||||||
|
};
|
||||||
|
|
||||||
JS::RootedId defineAs;
|
JS::RootedId defineAs;
|
||||||
|
bool allowCallbacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MOZ_STACK_CLASS StackScopedCloneOptions : public OptionsBase {
|
class MOZ_STACK_CLASS StackScopedCloneOptions : public OptionsBase {
|
||||||
|
@ -3425,10 +3434,51 @@ public:
|
||||||
ParseBoolean("cloneFunctions", &cloneFunctions);
|
ParseBoolean("cloneFunctions", &cloneFunctions);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// When a reflector is encountered, wrap it rather than aborting the clone.
|
||||||
bool wrapReflectors;
|
bool wrapReflectors;
|
||||||
|
|
||||||
|
// When a function is encountered, clone it (exportFunction-style) rather than
|
||||||
|
// aborting the clone.
|
||||||
bool cloneFunctions;
|
bool cloneFunctions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MOZ_STACK_CLASS FunctionForwarderOptions : public OptionsBase {
|
||||||
|
public:
|
||||||
|
FunctionForwarderOptions(JSContext *cx = xpc_GetSafeJSContext(),
|
||||||
|
JSObject* options = nullptr)
|
||||||
|
: OptionsBase(cx, options)
|
||||||
|
, allowCallbacks(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(allowCallbacks);
|
||||||
|
if (!JS_DefineProperty(cx, obj, "allowCallbacks", val, attrs))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool Parse() {
|
||||||
|
return ParseBoolean("allowCallbacks", &allowCallbacks);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allow callback arguments. This is similar to setting cloneFunctions in
|
||||||
|
// StackScopedCloneOptions, except that cloneFunctions will clone any Function
|
||||||
|
// encountered in the object graph, whereas this option only allows the base
|
||||||
|
// object to be supported.
|
||||||
|
//
|
||||||
|
// So invoking: |forwardedFunction(callback)| will work, but
|
||||||
|
// |forwardedFunction({ cb: callback })| will not.
|
||||||
|
bool allowCallbacks;
|
||||||
|
};
|
||||||
|
|
||||||
JSObject *
|
JSObject *
|
||||||
CreateGlobalObject(JSContext *cx, const JSClass *clasp, nsIPrincipal *principal,
|
CreateGlobalObject(JSContext *cx, const JSClass *clasp, nsIPrincipal *principal,
|
||||||
JS::CompartmentOptions& aOptions);
|
JS::CompartmentOptions& aOptions);
|
||||||
|
|
|
@ -10,6 +10,7 @@ function run_test() {
|
||||||
epsb.do_check_true = do_check_true;
|
epsb.do_check_true = do_check_true;
|
||||||
epsb.do_check_eq = do_check_eq;
|
epsb.do_check_eq = do_check_eq;
|
||||||
epsb.do_check_neq = do_check_neq;
|
epsb.do_check_neq = do_check_neq;
|
||||||
|
subsb.do_check_true = do_check_true;
|
||||||
|
|
||||||
// Exporting should work if prinicipal of the source sandbox
|
// Exporting should work if prinicipal of the source sandbox
|
||||||
// subsumes the principal of the target sandbox.
|
// subsumes the principal of the target sandbox.
|
||||||
|
@ -17,7 +18,7 @@ function run_test() {
|
||||||
Object.prototype.protoProp = "common";
|
Object.prototype.protoProp = "common";
|
||||||
var wasCalled = false;
|
var wasCalled = false;
|
||||||
var _this = this;
|
var _this = this;
|
||||||
this.funToExport = function(a, obj, native, mixed) {
|
this.funToExport = function(a, obj, native, mixed, callback) {
|
||||||
do_check_eq(a, 42);
|
do_check_eq(a, 42);
|
||||||
do_check_neq(obj, subsb.tobecloned);
|
do_check_neq(obj, subsb.tobecloned);
|
||||||
do_check_eq(obj.cloned, "cloned");
|
do_check_eq(obj.cloned, "cloned");
|
||||||
|
@ -26,13 +27,18 @@ function run_test() {
|
||||||
do_check_eq(_this, this);
|
do_check_eq(_this, this);
|
||||||
do_check_eq(mixed.xrayed, subsb.xrayed);
|
do_check_eq(mixed.xrayed, subsb.xrayed);
|
||||||
do_check_eq(mixed.xrayed2, subsb.xrayed2);
|
do_check_eq(mixed.xrayed2, subsb.xrayed2);
|
||||||
|
if (typeof callback == 'function') {
|
||||||
|
do_check_eq(typeof subsb.callback, 'function');
|
||||||
|
do_check_neq(callback, subsb.callback);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
wasCalled = true;
|
wasCalled = true;
|
||||||
};
|
};
|
||||||
this.checkIfCalled = function() {
|
this.checkIfCalled = function() {
|
||||||
do_check_true(wasCalled);
|
do_check_true(wasCalled);
|
||||||
wasCalled = false;
|
wasCalled = false;
|
||||||
}
|
}
|
||||||
exportFunction(funToExport, subsb, { defineAs: "imported" });
|
exportFunction(funToExport, subsb, { defineAs: "imported", allowCallbacks: true });
|
||||||
}.toSource() + ")()", epsb);
|
}.toSource() + ")()", epsb);
|
||||||
|
|
||||||
subsb.xrayed = Cu.evalInSandbox("(" + function () {
|
subsb.xrayed = Cu.evalInSandbox("(" + function () {
|
||||||
|
@ -47,7 +53,17 @@ function run_test() {
|
||||||
xrayed2 = XPCNativeWrapper(new XMLHttpRequest());
|
xrayed2 = XPCNativeWrapper(new XMLHttpRequest());
|
||||||
mixed = { xrayed: xrayed, xrayed2: xrayed2 };
|
mixed = { xrayed: xrayed, xrayed2: xrayed2 };
|
||||||
tobecloned = { cloned: "cloned" };
|
tobecloned = { cloned: "cloned" };
|
||||||
imported(42,tobecloned, native, mixed);
|
invokedCallback = false;
|
||||||
|
callback = function() { invokedCallback = true; };
|
||||||
|
imported(42, tobecloned, native, mixed, callback);
|
||||||
|
do_check_true(invokedCallback);
|
||||||
|
try {
|
||||||
|
// Callbacks must be functions, not objects leading to functions.
|
||||||
|
imported(42, tobecloned, native, mixed, { cb: callback });
|
||||||
|
do_check_true(false);
|
||||||
|
} catch (e) {
|
||||||
|
do_check_true(/denied/.test(e) && /Function/.test(e));
|
||||||
|
}
|
||||||
}.toSource() + ")()", subsb);
|
}.toSource() + ")()", subsb);
|
||||||
|
|
||||||
// Invoking an exported function with cross-origin arguments should throw.
|
// Invoking an exported function with cross-origin arguments should throw.
|
||||||
|
@ -114,6 +130,16 @@ function run_test() {
|
||||||
imported2(42, tobecloned, native, mixed);
|
imported2(42, tobecloned, native, mixed);
|
||||||
}.toSource() + ")()", subsb);
|
}.toSource() + ")()", subsb);
|
||||||
|
|
||||||
|
// Make sure that functions may not be passed when allowCallbacks is not set.
|
||||||
|
try {
|
||||||
|
Cu.evalInSandbox("(" + function () {
|
||||||
|
imported2(42, tobecloned, native, mixed, callback);
|
||||||
|
}.toSource() + ")()", subsb);
|
||||||
|
do_check_true(false);
|
||||||
|
} catch (e) {
|
||||||
|
do_check_true(/denied/.test(e) && /Function/.test(e));
|
||||||
|
}
|
||||||
|
|
||||||
Cu.evalInSandbox("(" + function() {
|
Cu.evalInSandbox("(" + function() {
|
||||||
checkIfCalled();
|
checkIfCalled();
|
||||||
}.toSource() + ")()", epsb);
|
}.toSource() + ")()", epsb);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче