mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 990729 - Add writeToGlobalPrototype option for sandboxes (r=bholley)
This commit is contained in:
parent
014dd71102
commit
b3a35170a9
@ -1025,6 +1025,12 @@ JS::StringOfAddonId(JSAddonId *id)
|
||||
return id;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSAddonId *)
|
||||
JS::AddonIdOfObject(JSObject *obj)
|
||||
{
|
||||
return obj->compartment()->addonId;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetZoneUserData(JS::Zone *zone, void *data)
|
||||
{
|
||||
|
@ -2568,6 +2568,7 @@ class JS_PUBLIC_API(CompartmentOptions)
|
||||
, discardSource_(false)
|
||||
, traceGlobal_(nullptr)
|
||||
, singletonsAsTemplates_(true)
|
||||
, addonId_(nullptr)
|
||||
{
|
||||
zone_.spec = JS::FreshZone;
|
||||
}
|
||||
@ -2626,6 +2627,14 @@ class JS_PUBLIC_API(CompartmentOptions)
|
||||
return singletonsAsTemplates_;
|
||||
};
|
||||
|
||||
// A null add-on ID means that the compartment is not associated with an
|
||||
// add-on.
|
||||
JSAddonId *addonIdOrNull() const { return addonId_; }
|
||||
CompartmentOptions &setAddonId(JSAddonId *id) {
|
||||
addonId_ = id;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CompartmentOptions &setTrace(JSTraceOp op) {
|
||||
traceGlobal_ = op;
|
||||
return *this;
|
||||
@ -2650,6 +2659,8 @@ class JS_PUBLIC_API(CompartmentOptions)
|
||||
// templates, by making JSOP_OBJECT return a clone of the JSScript
|
||||
// singleton, instead of returning the value which is baked in the JSScript.
|
||||
bool singletonsAsTemplates_;
|
||||
|
||||
JSAddonId *addonId_;
|
||||
};
|
||||
|
||||
JS_PUBLIC_API(CompartmentOptions &)
|
||||
@ -4335,6 +4346,9 @@ CharsZOfAddonId(JSAddonId *id);
|
||||
extern JS_PUBLIC_API(JSString *)
|
||||
StringOfAddonId(JSAddonId *id);
|
||||
|
||||
extern JS_PUBLIC_API(JSAddonId *)
|
||||
AddonIdOfObject(JSObject *obj);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
/************************************************************************/
|
||||
|
@ -44,6 +44,7 @@ JSCompartment::JSCompartment(Zone *zone, const JS::CompartmentOptions &options =
|
||||
isSystem(false),
|
||||
isSelfHosting(false),
|
||||
marked(true),
|
||||
addonId(options.addonIdOrNull()),
|
||||
#ifdef DEBUG
|
||||
firedOnNewGlobalObject(false),
|
||||
#endif
|
||||
|
@ -132,6 +132,10 @@ struct JSCompartment
|
||||
bool isSelfHosting;
|
||||
bool marked;
|
||||
|
||||
// A null add-on ID means that the compartment is not associated with an
|
||||
// add-on.
|
||||
JSAddonId *addonId;
|
||||
|
||||
#ifdef DEBUG
|
||||
bool firedOnNewGlobalObject;
|
||||
#endif
|
||||
|
@ -5371,6 +5371,8 @@ gc::MergeCompartments(JSCompartment *source, JSCompartment *target)
|
||||
// also implies that the compartment is not visible to the debugger.
|
||||
JS_ASSERT(source->options_.mergeable());
|
||||
|
||||
JS_ASSERT(source->addonId == target->addonId);
|
||||
|
||||
JSRuntime *rt = source->runtimeFromMainThread();
|
||||
|
||||
AutoPrepareForTracing prepare(rt, SkipAtoms);
|
||||
|
@ -676,6 +676,91 @@ sandbox_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue
|
||||
return JS_ConvertStub(cx, obj, type, vp);
|
||||
}
|
||||
|
||||
static bool
|
||||
writeToProto_setProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
|
||||
bool strict, JS::MutableHandleValue vp)
|
||||
{
|
||||
RootedObject proto(cx);
|
||||
if (!JS_GetPrototype(cx, obj, &proto))
|
||||
return false;
|
||||
|
||||
return JS_SetPropertyById(cx, proto, id, vp);
|
||||
}
|
||||
|
||||
static bool
|
||||
writeToProto_getProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
|
||||
JS::MutableHandleValue vp)
|
||||
{
|
||||
RootedObject proto(cx);
|
||||
if (!JS_GetPrototype(cx, obj, &proto))
|
||||
return false;
|
||||
|
||||
return JS_GetPropertyById(cx, proto, id, vp);
|
||||
}
|
||||
|
||||
struct AutoSkipPropertyMirroring
|
||||
{
|
||||
AutoSkipPropertyMirroring(CompartmentPrivate *priv) : priv(priv) {
|
||||
MOZ_ASSERT(!priv->skipWriteToGlobalPrototype);
|
||||
priv->skipWriteToGlobalPrototype = true;
|
||||
}
|
||||
~AutoSkipPropertyMirroring() {
|
||||
MOZ_ASSERT(priv->skipWriteToGlobalPrototype);
|
||||
priv->skipWriteToGlobalPrototype = false;
|
||||
}
|
||||
|
||||
private:
|
||||
CompartmentPrivate *priv;
|
||||
};
|
||||
|
||||
// This hook handles the case when writeToGlobalPrototype is set on the
|
||||
// sandbox. This flag asks that any properties defined on the sandbox global
|
||||
// also be defined on the sandbox global's prototype. Whenever one of these
|
||||
// properties is changed (on either side), the change should be reflected on
|
||||
// both sides. We use this functionality to create sandboxes that are
|
||||
// essentially "sub-globals" of another global. This is useful for running
|
||||
// add-ons in a separate compartment while still giving them access to the
|
||||
// chrome window.
|
||||
static bool
|
||||
sandbox_addProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp)
|
||||
{
|
||||
CompartmentPrivate *priv = GetCompartmentPrivate(obj);
|
||||
MOZ_ASSERT(priv->writeToGlobalPrototype);
|
||||
|
||||
// Whenever JS_EnumerateStandardClasses is called (by sandbox_enumerate for
|
||||
// example), it defines the "undefined" property, even if it's already
|
||||
// defined. We don't want to do anything in that case.
|
||||
if (id == XPCJSRuntime::Get()->GetStringID(XPCJSRuntime::IDX_UNDEFINED))
|
||||
return true;
|
||||
|
||||
// Avoid recursively triggering sandbox_addProperty in the
|
||||
// JS_DefinePropertyById call below.
|
||||
if (priv->skipWriteToGlobalPrototype)
|
||||
return true;
|
||||
|
||||
AutoSkipPropertyMirroring askip(priv);
|
||||
|
||||
RootedObject proto(cx);
|
||||
if (!JS_GetPrototype(cx, obj, &proto))
|
||||
return false;
|
||||
|
||||
// After bug 1015790 is fixed, we should be able to remove this unwrapping.
|
||||
RootedObject unwrappedProto(cx, js::UncheckedUnwrap(proto, /* stopAtOuter = */ false));
|
||||
|
||||
if (!JS_CopyPropertyFrom(cx, id, unwrappedProto, obj))
|
||||
return false;
|
||||
|
||||
Rooted<JSPropertyDescriptor> pd(cx);
|
||||
if (!JS_GetPropertyDescriptorById(cx, obj, id, &pd))
|
||||
return false;
|
||||
unsigned attrs = pd.attributes() & ~(JSPROP_GETTER | JSPROP_SETTER);
|
||||
if (!JS_DefinePropertyById(cx, obj, id, vp, attrs,
|
||||
writeToProto_getProperty, writeToProto_setProperty))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
|
||||
|
||||
static const JSClass SandboxClass = {
|
||||
@ -686,6 +771,16 @@ static const JSClass SandboxClass = {
|
||||
nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook
|
||||
};
|
||||
|
||||
// Note to whomever comes here to remove addProperty hooks: billm has promised
|
||||
// to do the work for this class.
|
||||
static const JSClass SandboxWriteToProtoClass = {
|
||||
"Sandbox",
|
||||
XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
|
||||
sandbox_addProperty, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
|
||||
sandbox_enumerate, sandbox_resolve, sandbox_convert, sandbox_finalize,
|
||||
nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook
|
||||
};
|
||||
|
||||
static const JSFunctionSpec SandboxFunctions[] = {
|
||||
JS_FS("dump", SandboxDump, 1,0),
|
||||
JS_FS("debug", SandboxDebug, 1,0),
|
||||
@ -696,7 +791,8 @@ static const JSFunctionSpec SandboxFunctions[] = {
|
||||
bool
|
||||
xpc::IsSandbox(JSObject *obj)
|
||||
{
|
||||
return GetObjectJSClass(obj) == &SandboxClass;
|
||||
const JSClass *clasp = GetObjectJSClass(obj);
|
||||
return clasp == &SandboxClass || clasp == &SandboxWriteToProtoClass;
|
||||
}
|
||||
|
||||
/***************************************************************************/
|
||||
@ -748,7 +844,7 @@ xpc::SandboxCallableProxyHandler::call(JSContext *cx, JS::Handle<JSObject*> prox
|
||||
// The parent of the sandboxProxy is the sandbox global, and the
|
||||
// target object is the original proto.
|
||||
RootedObject sandboxGlobal(cx, JS_GetParent(sandboxProxy));
|
||||
MOZ_ASSERT(js::GetObjectJSClass(sandboxGlobal) == &SandboxClass);
|
||||
MOZ_ASSERT(IsSandbox(sandboxGlobal));
|
||||
|
||||
// If our this object is the sandbox global, we call with this set to the
|
||||
// original proto instead.
|
||||
@ -1081,11 +1177,32 @@ xpc::CreateSandboxObject(JSContext *cx, MutableHandleValue vp, nsISupports *prin
|
||||
.setDiscardSource(options.discardSource)
|
||||
.setTrace(TraceXPCGlobal);
|
||||
|
||||
RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, &SandboxClass,
|
||||
// Try to figure out any addon this sandbox should be associated with.
|
||||
// The addon could have been passed in directly, as part of the metadata,
|
||||
// or by being constructed from an addon's code.
|
||||
JSAddonId *addonId = nullptr;
|
||||
if (options.addonId) {
|
||||
addonId = JS::NewAddonId(cx, options.addonId);
|
||||
NS_ENSURE_TRUE(addonId, NS_ERROR_FAILURE);
|
||||
} else if (JSObject *obj = JS::CurrentGlobalOrNull(cx)) {
|
||||
if (JSAddonId *id = JS::AddonIdOfObject(obj))
|
||||
addonId = id;
|
||||
}
|
||||
|
||||
compartmentOptions.setAddonId(addonId);
|
||||
|
||||
const JSClass *clasp = options.writeToGlobalPrototype
|
||||
? &SandboxWriteToProtoClass
|
||||
: &SandboxClass;
|
||||
|
||||
RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, clasp,
|
||||
principal, compartmentOptions));
|
||||
if (!sandbox)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
xpc::GetCompartmentPrivate(sandbox)->writeToGlobalPrototype =
|
||||
options.writeToGlobalPrototype;
|
||||
|
||||
// Set up the wantXrays flag, which indicates whether xrays are desired even
|
||||
// for same-origin access.
|
||||
//
|
||||
@ -1138,6 +1255,9 @@ xpc::CreateSandboxObject(JSContext *cx, MutableHandleValue vp, nsISupports *prin
|
||||
// Pass on ownership of sbp to |sandbox|.
|
||||
JS_SetPrivate(sandbox, sbp.forget().take());
|
||||
|
||||
// Don't try to mirror the properties that are set below.
|
||||
AutoSkipPropertyMirroring askip(GetCompartmentPrivate(sandbox));
|
||||
|
||||
bool allowComponents = nsContentUtils::IsSystemPrincipal(principal) ||
|
||||
nsContentUtils::IsExpandedPrincipal(principal);
|
||||
if (options.wantComponents && allowComponents &&
|
||||
@ -1160,8 +1280,11 @@ xpc::CreateSandboxObject(JSContext *cx, MutableHandleValue vp, nsISupports *prin
|
||||
|
||||
if (!options.globalProperties.Define(cx, sandbox))
|
||||
return NS_ERROR_XPC_UNEXPECTED;
|
||||
}
|
||||
|
||||
// Resolve standard classes eagerly to avoid triggering mirroring hooks for them.
|
||||
if (options.writeToGlobalPrototype && !JS_EnumerateStandardClasses(cx, sandbox))
|
||||
return NS_ERROR_XPC_UNEXPECTED;
|
||||
}
|
||||
|
||||
// We have this crazy behavior where wantXrays=false also implies that the
|
||||
// returned sandbox is implicitly waived. We've stopped advertising it, but
|
||||
@ -1403,6 +1526,28 @@ OptionsBase::ParseObject(const char *name, MutableHandleObject prop)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper that tries to get an object property from the options object.
|
||||
*/
|
||||
bool
|
||||
OptionsBase::ParseJSString(const char *name, MutableHandleString prop)
|
||||
{
|
||||
RootedValue value(mCx);
|
||||
bool found;
|
||||
bool ok = ParseValue(name, &value, &found);
|
||||
NS_ENSURE_TRUE(ok, false);
|
||||
|
||||
if (!found)
|
||||
return true;
|
||||
|
||||
if (!value.isString()) {
|
||||
JS_ReportError(mCx, "Expected a string value for property %s", name);
|
||||
return false;
|
||||
}
|
||||
prop.set(value.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper that tries to get a string property from the options object.
|
||||
*/
|
||||
@ -1511,6 +1656,8 @@ SandboxOptions::Parse()
|
||||
ParseObject("sameZoneAs", &sameZoneAs) &&
|
||||
ParseBoolean("invisibleToDebugger", &invisibleToDebugger) &&
|
||||
ParseBoolean("discardSource", &discardSource) &&
|
||||
ParseJSString("addonId", &addonId) &&
|
||||
ParseBoolean("writeToGlobalPrototype", &writeToGlobalPrototype) &&
|
||||
ParseGlobalProperties() &&
|
||||
ParseValue("metadata", &metadata);
|
||||
}
|
||||
@ -1673,7 +1820,7 @@ xpc::EvalInSandbox(JSContext *cx, HandleObject sandboxArg, const nsAString& sour
|
||||
|
||||
bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg);
|
||||
RootedObject sandbox(cx, js::CheckedUnwrap(sandboxArg));
|
||||
if (!sandbox || js::GetObjectJSClass(sandbox) != &SandboxClass) {
|
||||
if (!sandbox || !IsSandbox(sandbox)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
|
@ -84,6 +84,7 @@ const char* const XPCJSRuntime::mStrings[] = {
|
||||
"realFrameElement", // IDX_REALFRAMEELEMENT
|
||||
"length", // IDX_LENGTH
|
||||
"name", // IDX_NAME
|
||||
"undefined", // IDX_UNDEFINED
|
||||
};
|
||||
|
||||
/***************************************************************************/
|
||||
|
@ -482,6 +482,7 @@ public:
|
||||
IDX_REALFRAMEELEMENT ,
|
||||
IDX_LENGTH ,
|
||||
IDX_NAME ,
|
||||
IDX_UNDEFINED ,
|
||||
IDX_TOTAL_COUNT // just a count of the above
|
||||
};
|
||||
|
||||
@ -3321,6 +3322,7 @@ protected:
|
||||
bool ParseValue(const char *name, JS::MutableHandleValue prop, bool *found = nullptr);
|
||||
bool ParseBoolean(const char *name, bool *prop);
|
||||
bool ParseObject(const char *name, JS::MutableHandleObject prop);
|
||||
bool ParseJSString(const char *name, JS::MutableHandleString prop);
|
||||
bool ParseString(const char *name, nsCString &prop);
|
||||
bool ParseString(const char *name, nsString &prop);
|
||||
bool ParseId(const char* name, JS::MutableHandleId id);
|
||||
@ -3338,6 +3340,8 @@ public:
|
||||
, wantComponents(true)
|
||||
, wantExportHelpers(false)
|
||||
, proto(cx)
|
||||
, addonId(cx)
|
||||
, writeToGlobalPrototype(false)
|
||||
, sameZoneAs(cx)
|
||||
, invisibleToDebugger(false)
|
||||
, discardSource(false)
|
||||
@ -3352,6 +3356,8 @@ public:
|
||||
bool wantExportHelpers;
|
||||
JS::RootedObject proto;
|
||||
nsCString sandboxName;
|
||||
JS::RootedString addonId;
|
||||
bool writeToGlobalPrototype;
|
||||
JS::RootedObject sameZoneAs;
|
||||
bool invisibleToDebugger;
|
||||
bool discardSource;
|
||||
@ -3476,6 +3482,8 @@ public:
|
||||
|
||||
CompartmentPrivate(JSCompartment *c)
|
||||
: wantXrays(false)
|
||||
, writeToGlobalPrototype(false)
|
||||
, skipWriteToGlobalPrototype(false)
|
||||
, universalXPConnectEnabled(false)
|
||||
, adoptedNode(false)
|
||||
, donatedNode(false)
|
||||
@ -3489,6 +3497,17 @@ public:
|
||||
|
||||
bool wantXrays;
|
||||
|
||||
// This flag is intended for a very specific use, internal to Gecko. It may
|
||||
// go away or change behavior at any time. It should not be added to any
|
||||
// documentation and it should not be used without consulting the XPConnect
|
||||
// module owner.
|
||||
bool writeToGlobalPrototype;
|
||||
|
||||
// When writeToGlobalPrototype is true, we use this flag to temporarily
|
||||
// disable the writeToGlobalPrototype behavior (when resolving standard
|
||||
// classes, for example).
|
||||
bool skipWriteToGlobalPrototype;
|
||||
|
||||
// This is only ever set during mochitest runs when enablePrivilege is called.
|
||||
// It's intended as a temporary stopgap measure until we can finish ripping out
|
||||
// enablePrivilege. Once set, this value is never unset (i.e., it doesn't follow
|
||||
|
Loading…
Reference in New Issue
Block a user