From da78ac7b6aa87e0712e50a5a48f0e21eccc05c28 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Mon, 26 Dec 2011 11:31:07 -0500 Subject: [PATCH] Bug 707717. Don't dynamically mutate the proto chains of DOM prototypes. r=peterv --- dom/base/nsDOMClassInfo.cpp | 304 ++++++++++++--------- js/xpconnect/idl/nsIXPCScriptable.idl | 10 +- js/xpconnect/public/xpc_map_end.h | 9 + js/xpconnect/src/XPCWrappedNativeProto.cpp | 16 +- 4 files changed, 203 insertions(+), 136 deletions(-) diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index cb16a72b5925..cbc4e77f4e35 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -1679,25 +1679,6 @@ jsid nsDOMClassInfo::sMultiEntry_id = JSID_VOID; jsid nsDOMClassInfo::sOnload_id = JSID_VOID; jsid nsDOMClassInfo::sOnerror_id = JSID_VOID; -static const JSClass *sObjectClass = nsnull; - -/** - * Set our JSClass pointer for the Object class - */ -static void -FindObjectClass(JSObject* aGlobalObject) -{ - NS_ASSERTION(!sObjectClass, - "Double set of sObjectClass"); - JSObject *obj, *proto = aGlobalObject; - do { - obj = proto; - proto = js::GetObjectProto(obj); - } while (proto); - - sObjectClass = js::GetObjectJSClass(obj); -} - static void PrintWarningOnConsole(JSContext *cx, const char *stringBundleProperty) { @@ -4721,6 +4702,52 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx, nsScriptNameSpaceManager *nameSpaceManager, JSObject *dot_prototype, bool install, bool *did_resolve); +static nsresult +LookupPrototypeProto(JSContext *cx, JSObject *winobj, + const nsDOMClassInfoData *ci_data, + const nsGlobalNameStruct *name_struct, + JSObject **aProtoProto); + + +static nsGlobalWindow* +FindUsableInnerWindow(nsIXPConnect *xpc, JSContext *cx, JSObject *global) +{ + // Only do this if the global object is a window. + // XXX Is there a better way to check this? + nsISupports *globalNative = xpc->GetNativeOfWrapper(cx, global); + nsCOMPtr piwin = do_QueryInterface(globalNative); + if (!piwin) { + return nsnull; + } + + nsGlobalWindow *win = nsGlobalWindow::FromSupports(globalNative); + if (win->IsClosedOrClosing()) { + return nsnull; + } + + // If the window is in a different compartment than the global object, then + // it's likely that global is a sandbox object whose prototype is a window. + // Don't do anything in this case. + if (win->FastGetGlobalJSObject() && + js::GetObjectCompartment(global) != js::GetObjectCompartment(win->FastGetGlobalJSObject())) { + return nsnull; + } + + if (win->IsOuterWindow()) { + // XXXjst: Do security checks here when we remove the security + // checks on the inner window. + + win = win->GetCurrentInnerWindowInternal(); + + JSObject* global; + if (!win || !(global = win->GetGlobalJSObject()) || + win->IsClosedOrClosing()) { + return nsnull; + } + } + + return win; +} NS_IMETHODIMP nsDOMClassInfo::PostCreatePrototype(JSContext * cx, JSObject * proto) @@ -4739,19 +4766,6 @@ nsDOMClassInfo::PostCreatePrototype(JSContext * cx, JSObject * proto) JS_ClearPendingException(cx); } - // This is called before any other location that requires - // sObjectClass, so compute it here. We assume that nobody has had a - // chance to monkey around with proto's prototype chain before this. - if (!sObjectClass) { - FindObjectClass(proto); - NS_ASSERTION(sObjectClass && !strcmp(sObjectClass->name, "Object"), - "Incorrect object class!"); - } - - NS_ASSERTION(::JS_GetPrototype(cx, proto) && - JS_GET_CLASS(cx, ::JS_GetPrototype(cx, proto)) == sObjectClass, - "Hmm, somebody did something evil?"); - #ifdef DEBUG if (mData->mHasClassInterface && mData->mProtoChainInterface && mData->mProtoChainInterface != &NS_GET_IID(nsISupports)) { @@ -4779,38 +4793,12 @@ nsDOMClassInfo::PostCreatePrototype(JSContext * cx, JSObject * proto) // document.body's prototype will find the right function. JSObject *global = ::JS_GetGlobalForObject(cx, proto); - // Only do this if the global object is a window. - // XXX Is there a better way to check this? - nsISupports *globalNative = XPConnect()->GetNativeOfWrapper(cx, global); - nsCOMPtr piwin = do_QueryInterface(globalNative); - if (!piwin) { + nsGlobalWindow *win = FindUsableInnerWindow(XPConnect(), cx, global); + if (!win) { return NS_OK; } - nsGlobalWindow *win = nsGlobalWindow::FromSupports(globalNative); - if (win->IsClosedOrClosing()) { - return NS_OK; - } - - // If the window is in a different compartment than the global object, then - // it's likely that global is a sandbox object whose prototype is a window. - // Don't do anything in this case. - if (win->FastGetGlobalJSObject() && - js::GetObjectCompartment(global) != js::GetObjectCompartment(win->FastGetGlobalJSObject())) { - return NS_OK; - } - - if (win->IsOuterWindow()) { - // XXXjst: Do security checks here when we remove the security - // checks on the inner window. - - win = win->GetCurrentInnerWindowInternal(); - - if (!win || !(global = win->GetGlobalJSObject()) || - win->IsClosedOrClosing()) { - return NS_OK; - } - } + global = win->FastGetGlobalJSObject(); // Don't overwrite a property set by content. JSBool found; @@ -4829,6 +4817,23 @@ nsDOMClassInfo::PostCreatePrototype(JSContext * cx, JSObject * proto) &unused); } +NS_IMETHODIMP +nsDOMClassInfo::PreCreatePrototype(JSContext * cx, JSObject * global, + JSObject **protoProto) +{ + *protoProto = nsnull; + + nsGlobalWindow *win = FindUsableInnerWindow(XPConnect(), cx, global); + if (!win) { + return NS_OK; + } + + JSObject *winObj = win->FastGetGlobalJSObject(); + + return LookupPrototypeProto(cx, winObj, mData, nsnull, protoProto); +} + + // static nsIClassInfo * NS_GetDOMClassInfoInstance(nsDOMClassInfoID aID) @@ -5181,7 +5186,7 @@ nsWindowSH::InstallGlobalScopePolluter(JSContext *cx, JSObject *obj, // scope polluter (right before Object.prototype). while ((proto = ::JS_GetPrototype(cx, o))) { - if (JS_GET_CLASS(cx, proto) == sObjectClass) { + if (js::GetObjectClass(proto) == &js::ObjectClass) { // Set the global scope polluters prototype to Object.prototype ::JS_SplicePrototype(cx, gsp, proto); @@ -6003,6 +6008,105 @@ GetXPCProto(nsIXPConnect *aXPConnect, JSContext *cx, nsGlobalWindow *aWin, return aXPConnect->HoldObject(cx, proto_obj, aProto); } +static nsresult +LookupPrototypeProto(JSContext *cx, JSObject *winobj, + const nsDOMClassInfoData *ci_data, + const nsGlobalNameStruct *name_struct, + JSObject **aProtoProto) +{ + NS_ASSERTION(ci_data || + (name_struct && + name_struct->mType == nsGlobalNameStruct::eTypeClassProto), + "Wrong type or missing ci_data!"); + + const nsIID *primary_iid = &NS_GET_IID(nsISupports); + + if (!ci_data) { + primary_iid = &name_struct->mIID; + } else if (ci_data->mProtoChainInterface) { + primary_iid = ci_data->mProtoChainInterface; + } + + if (primary_iid->Equals(NS_GET_IID(nsISupports))) { + *aProtoProto = nsnull; + return NS_OK; + } + + nsCOMPtr + iim(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID)); + NS_ENSURE_TRUE(iim, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr if_info; + iim->GetInfoForIID(primary_iid, getter_AddRefs(if_info)); + NS_ENSURE_TRUE(if_info, NS_ERROR_UNEXPECTED); + + const nsIID *iid = nsnull; + + nsCOMPtr parent; + if (ci_data && !ci_data->mHasClassInterface) { + if_info->GetIIDShared(&iid); + } else { + if_info->GetParent(getter_AddRefs(parent)); + NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED); + + parent->GetIIDShared(&iid); + } + + if (!iid || iid->Equals(NS_GET_IID(nsISupports))) { + *aProtoProto = nsnull; + return NS_OK; + } + + const char *class_parent_name = nsnull; + if (ci_data && !ci_data->mHasClassInterface) { + // If the class doesn't have a class interface the primary + // interface is the interface that should be + // constructor.prototype.__proto__. + + if_info->GetNameShared(&class_parent_name); + } else { + // If the class does have a class interface (or there's no + // real class for this name) then the parent of the + // primary interface is what we want on + // constructor.prototype.__proto__. + + NS_ASSERTION(parent, "Whoa, this is bad, null parent here!"); + + parent->GetNameShared(&class_parent_name); + } + + JSObject *protoProto = nsnull; + + // Get class_parent_name here + if (class_parent_name) { + jsval val; + + JSAutoEnterCompartment ac; + if (!ac.enter(cx, winobj)) { + return NS_ERROR_UNEXPECTED; + } + + if (!::JS_LookupProperty(cx, winobj, CutPrefix(class_parent_name), &val)) { + return NS_ERROR_UNEXPECTED; + } + + JSObject *tmp = JSVAL_IS_OBJECT(val) ? JSVAL_TO_OBJECT(val) : nsnull; + + if (tmp) { + if (!::JS_LookupProperty(cx, tmp, "prototype", &val)) { + return NS_ERROR_UNEXPECTED; + } + + if (JSVAL_IS_OBJECT(val)) { + protoProto = JSVAL_TO_OBJECT(val); + } + } + } + + *aProtoProto = protoProto; + return NS_OK; +} + // Either ci_data must be non-null or name_struct must be non-null and of type // eTypeClassProto. static nsresult @@ -6048,10 +6152,6 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx, primary_iid = ci_data->mProtoChainInterface; } - nsCOMPtr if_info; - nsCOMPtr parent; - const char *class_parent_name = nsnull; - if (!primary_iid->Equals(NS_GET_IID(nsISupports))) { JSAutoEnterCompartment ac; @@ -6075,76 +6175,14 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx, !indexedDB::IDBKeyRange::DefineConstructors(cx, class_obj)) { return NS_ERROR_FAILURE; } - - nsCOMPtr - iim(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID)); - NS_ENSURE_TRUE(iim, NS_ERROR_NOT_AVAILABLE); - - iim->GetInfoForIID(primary_iid, getter_AddRefs(if_info)); - NS_ENSURE_TRUE(if_info, NS_ERROR_UNEXPECTED); - - const nsIID *iid = nsnull; - - if (ci_data && !ci_data->mHasClassInterface) { - if_info->GetIIDShared(&iid); - } else { - if_info->GetParent(getter_AddRefs(parent)); - NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED); - - parent->GetIIDShared(&iid); - } - - if (iid) { - if (!iid->Equals(NS_GET_IID(nsISupports))) { - if (ci_data && !ci_data->mHasClassInterface) { - // If the class doesn't have a class interface the primary - // interface is the interface that should be - // constructor.prototype.__proto__. - - if_info->GetNameShared(&class_parent_name); - } else { - // If the class does have a class interface (or there's no - // real class for this name) then the parent of the - // primary interface is what we want on - // constructor.prototype.__proto__. - - NS_ASSERTION(parent, "Whoa, this is bad, null parent here!"); - - parent->GetNameShared(&class_parent_name); - } - } - } } { JSObject *winobj = aWin->FastGetGlobalJSObject(); - JSObject *proto = nsnull; - - if (class_parent_name) { - jsval val; - - JSAutoEnterCompartment ac; - if (!ac.enter(cx, winobj)) { - return NS_ERROR_UNEXPECTED; - } - - if (!::JS_LookupProperty(cx, winobj, CutPrefix(class_parent_name), &val)) { - return NS_ERROR_UNEXPECTED; - } - - JSObject *tmp = JSVAL_IS_OBJECT(val) ? JSVAL_TO_OBJECT(val) : nsnull; - - if (tmp) { - if (!::JS_LookupProperty(cx, tmp, "prototype", &val)) { - return NS_ERROR_UNEXPECTED; - } - - if (JSVAL_IS_OBJECT(val)) { - proto = JSVAL_TO_OBJECT(val); - } - } - } + JSObject *proto; + rv = LookupPrototypeProto(cx, winobj, ci_data, name_struct, &proto); + NS_ENSURE_SUCCESS(rv, rv); if (dot_prototype) { JSAutoEnterCompartment ac; @@ -6156,7 +6194,7 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx, if (proto && (!xpc_proto_proto || - JS_GET_CLASS(cx, xpc_proto_proto) == sObjectClass)) { + js::GetObjectClass(xpc_proto_proto) == &js::ObjectClass)) { if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, dot_prototype, proto)) { return NS_ERROR_UNEXPECTED; @@ -9543,7 +9581,7 @@ nsHTMLPluginObjElementSH::SetupProtoChain(nsIXPConnectWrappedNative *wrapper, return NS_ERROR_UNEXPECTED; } - if (pi_proto && JS_GET_CLASS(cx, pi_proto) != sObjectClass) { + if (pi_proto && js::GetObjectClass(pi_proto) != &js::ObjectClass) { // The plugin wrapper has a proto that's not Object.prototype, set // 'pi.__proto__.__proto__' to the original 'this.__proto__' if (pi_proto != my_proto && !::JS_SetPrototype(cx, pi_proto, my_proto)) { diff --git a/js/xpconnect/idl/nsIXPCScriptable.idl b/js/xpconnect/idl/nsIXPCScriptable.idl index 6d623705c015..024ec292739f 100644 --- a/js/xpconnect/idl/nsIXPCScriptable.idl +++ b/js/xpconnect/idl/nsIXPCScriptable.idl @@ -73,7 +73,7 @@ * to *_retval unless they want to return PR_FALSE. */ -[uuid(a40ce52e-2d8c-400f-9af2-f8784a656070)] +[uuid(9cadb17b-9990-461a-8e01-cf50aeffc2c5)] interface nsIXPCScriptable : nsISupports { /* bitflags used for 'flags' (only 32 bits available!) */ @@ -191,6 +191,14 @@ interface nsIXPCScriptable : nsISupports in JSContextPtr cx, in JSObjectPtr obj); void postCreatePrototype(in JSContextPtr cx, in JSObjectPtr proto); + + /** + * Notify that we're about to create the prototype object for this class, + * and ask what prototype we should use for that. + */ + void preCreatePrototype(in JSContextPtr cx, + in JSObjectPtr globalObj, + out JSObjectPtr protoProtoObj); }; %{ C++ diff --git a/js/xpconnect/public/xpc_map_end.h b/js/xpconnect/public/xpc_map_end.h index f4f1649b497b..59bc9ca007c8 100644 --- a/js/xpconnect/public/xpc_map_end.h +++ b/js/xpconnect/public/xpc_map_end.h @@ -228,6 +228,11 @@ NS_IMETHODIMP XPC_MAP_CLASSNAME::PostCreatePrototype(JSContext *cx, JSObject *pr {return NS_OK;} #endif +#ifndef XPC_MAP_WANT_PRE_CREATE_PROTOTYPE +NS_IMETHODIMP XPC_MAP_CLASSNAME::PreCreatePrototype(JSContext *cx, JSObject *global, JSObject **protoProto) + {*protoProto = nsnull; return NS_OK;} +#endif + /**************************************************************/ #undef XPC_MAP_CLASSNAME @@ -313,6 +318,10 @@ NS_IMETHODIMP XPC_MAP_CLASSNAME::PostCreatePrototype(JSContext *cx, JSObject *pr #undef XPC_MAP_WANT_POST_CREATE_PROTOTYPE #endif +#ifdef XPC_MAP_WANT_PRE_CREATE_PROTOTYPE +#undef XPC_MAP_WANT_PRE_CREATE_PROTOTYPE +#endif + #ifdef XPC_MAP_FLAGS #undef XPC_MAP_FLAGS #endif diff --git a/js/xpconnect/src/XPCWrappedNativeProto.cpp b/js/xpconnect/src/XPCWrappedNativeProto.cpp index 8fe3ff66335d..23b0140ff551 100644 --- a/js/xpconnect/src/XPCWrappedNativeProto.cpp +++ b/js/xpconnect/src/XPCWrappedNativeProto.cpp @@ -123,10 +123,22 @@ XPCWrappedNativeProto::Init(XPCCallContext& ccx, JSObject *parent = mScope->GetGlobalJSObject(); + JSObject *protoProto = nsnull; + if (callback) { + nsresult rv = callback->PreCreatePrototype(ccx, parent, &protoProto); + if (NS_FAILED(rv)) { + mJSProtoObject = nsnull; + XPCThrower::Throw(rv, ccx); + return false; + } + } + if (!protoProto) { + protoProto = mScope->GetPrototypeJSObject(); + } + mJSProtoObject = xpc_NewSystemInheritingJSObject(ccx, js::Jsvalify(jsclazz), - mScope->GetPrototypeJSObject(), - true, parent); + protoProto, true, parent); JSBool ok = mJSProtoObject && JS_SetPrivate(ccx, mJSProtoObject, this);