1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef XrayWrapper_h 8 #define XrayWrapper_h 9 10 #include "mozilla/Attributes.h" 11 #include "mozilla/Maybe.h" 12 13 #include "WrapperFactory.h" 14 15 #include "jsapi.h" 16 #include "jsfriendapi.h" 17 #include "js/friend/XrayJitInfo.h" // JS::XrayJitInfo 18 #include "js/Object.h" // JS::GetReservedSlot 19 #include "js/Proxy.h" 20 #include "js/Wrapper.h" 21 22 // Slot where Xray functions for Web IDL methods store a pointer to 23 // the Xray wrapper they're associated with. 24 #define XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT 0 25 // Slot where in debug builds Xray functions for Web IDL methods store 26 // a pointer to their themselves, just so we can assert that they're the 27 // sort of functions we expect. 28 #define XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF 1 29 30 // Xray wrappers re-resolve the original native properties on the native 31 // object and always directly access to those properties. 32 // Because they work so differently from the rest of the wrapper hierarchy, 33 // we pull them out of the Wrapper inheritance hierarchy and create a 34 // little world around them. 35 36 class nsIPrincipal; 37 38 namespace xpc { 39 40 enum XrayType { 41 XrayForDOMObject, 42 XrayForJSObject, 43 XrayForOpaqueObject, 44 NotXray 45 }; 46 47 class XrayTraits { 48 public: 49 constexpr XrayTraits() = default; 50 getTargetObject(JSObject * wrapper)51 static JSObject* getTargetObject(JSObject* wrapper) { 52 JSObject* target = 53 js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false); 54 if (target) { 55 JS::ExposeObjectToActiveJS(target); 56 } 57 return target; 58 } 59 60 // NB: resolveOwnProperty may decide whether or not to cache what it finds 61 // on the holder. If the result is not cached, the lookup will happen afresh 62 // for each access, which is the right thing for things like dynamic NodeList 63 // properties. 64 virtual bool resolveOwnProperty( 65 JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, 66 JS::HandleObject holder, JS::HandleId id, 67 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); 68 delete_(JSContext * cx,JS::HandleObject wrapper,JS::HandleId id,JS::ObjectOpResult & result)69 bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 70 JS::ObjectOpResult& result) { 71 return result.succeed(); 72 } 73 getBuiltinClass(JSContext * cx,JS::HandleObject wrapper,const js::Wrapper & baseInstance,js::ESClass * cls)74 static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, 75 const js::Wrapper& baseInstance, 76 js::ESClass* cls) { 77 return baseInstance.getBuiltinClass(cx, wrapper, cls); 78 } 79 className(JSContext * cx,JS::HandleObject wrapper,const js::Wrapper & baseInstance)80 static const char* className(JSContext* cx, JS::HandleObject wrapper, 81 const js::Wrapper& baseInstance) { 82 return baseInstance.className(cx, wrapper); 83 } 84 85 virtual void preserveWrapper(JSObject* target) = 0; 86 87 bool getExpandoObject(JSContext* cx, JS::HandleObject target, 88 JS::HandleObject consumer, 89 JS::MutableHandleObject expandObject); 90 JSObject* ensureExpandoObject(JSContext* cx, JS::HandleObject wrapper, 91 JS::HandleObject target); 92 93 // Slots for holder objects. 94 enum { 95 HOLDER_SLOT_CACHED_PROTO = 0, 96 HOLDER_SLOT_EXPANDO = 1, 97 HOLDER_SHARED_SLOT_COUNT 98 }; 99 100 static JSObject* getHolder(JSObject* wrapper); 101 JSObject* ensureHolder(JSContext* cx, JS::HandleObject wrapper); 102 virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) = 0; 103 104 JSObject* getExpandoChain(JS::HandleObject obj); 105 JSObject* detachExpandoChain(JS::HandleObject obj); 106 bool setExpandoChain(JSContext* cx, JS::HandleObject obj, 107 JS::HandleObject chain); 108 bool cloneExpandoChain(JSContext* cx, JS::HandleObject dst, 109 JS::HandleObject srcChain); 110 111 protected: 112 static const JSClass HolderClass; 113 114 // Get the JSClass we should use for our expando object. 115 virtual const JSClass* getExpandoClass(JSContext* cx, 116 JS::HandleObject target) const; 117 118 private: 119 bool expandoObjectMatchesConsumer(JSContext* cx, 120 JS::HandleObject expandoObject, 121 nsIPrincipal* consumerOrigin); 122 123 // |expandoChain| is the expando chain in the wrapped object's compartment. 124 // |exclusiveWrapper| is any xray that has exclusive use of the expando. 125 // |cx| may be in any compartment. 126 bool getExpandoObjectInternal(JSContext* cx, JSObject* expandoChain, 127 JS::HandleObject exclusiveWrapper, 128 nsIPrincipal* origin, 129 JS::MutableHandleObject expandoObject); 130 131 // |cx| is in the target's compartment, and |exclusiveWrapper| is any xray 132 // that has exclusive use of the expando. |exclusiveWrapperGlobal| is the 133 // caller's global and must be same-compartment with |exclusiveWrapper|. 134 JSObject* attachExpandoObject(JSContext* cx, JS::HandleObject target, 135 JS::HandleObject exclusiveWrapper, 136 JS::HandleObject exclusiveWrapperGlobal, 137 nsIPrincipal* origin); 138 139 XrayTraits(XrayTraits&) = delete; 140 const XrayTraits& operator=(XrayTraits&) = delete; 141 }; 142 143 class DOMXrayTraits : public XrayTraits { 144 public: 145 constexpr DOMXrayTraits() = default; 146 147 static const XrayType Type = XrayForDOMObject; 148 149 virtual bool resolveOwnProperty( 150 JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, 151 JS::HandleObject holder, JS::HandleId id, 152 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override; 153 154 bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 155 JS::ObjectOpResult& result); 156 157 bool defineProperty( 158 JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 159 JS::Handle<JS::PropertyDescriptor> desc, 160 JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc, 161 JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result, 162 bool* done); 163 virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, 164 unsigned flags, JS::MutableHandleIdVector props); 165 static bool call(JSContext* cx, JS::HandleObject wrapper, 166 const JS::CallArgs& args, const js::Wrapper& baseInstance); 167 static bool construct(JSContext* cx, JS::HandleObject wrapper, 168 const JS::CallArgs& args, 169 const js::Wrapper& baseInstance); 170 171 static bool getPrototype(JSContext* cx, JS::HandleObject wrapper, 172 JS::HandleObject target, 173 JS::MutableHandleObject protop); 174 175 virtual void preserveWrapper(JSObject* target) override; 176 177 virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override; 178 179 static DOMXrayTraits singleton; 180 181 protected: 182 virtual const JSClass* getExpandoClass( 183 JSContext* cx, JS::HandleObject target) const override; 184 }; 185 186 class JSXrayTraits : public XrayTraits { 187 public: 188 static const XrayType Type = XrayForJSObject; 189 190 virtual bool resolveOwnProperty( 191 JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, 192 JS::HandleObject holder, JS::HandleId id, 193 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override; 194 195 bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 196 JS::ObjectOpResult& result); 197 198 bool defineProperty( 199 JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 200 JS::Handle<JS::PropertyDescriptor> desc, 201 JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc, 202 JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result, 203 bool* defined); 204 205 virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, 206 unsigned flags, JS::MutableHandleIdVector props); 207 call(JSContext * cx,JS::HandleObject wrapper,const JS::CallArgs & args,const js::Wrapper & baseInstance)208 static bool call(JSContext* cx, JS::HandleObject wrapper, 209 const JS::CallArgs& args, const js::Wrapper& baseInstance) { 210 JSXrayTraits& self = JSXrayTraits::singleton; 211 JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper)); 212 if (!holder) { 213 return false; 214 } 215 if (xpc::JSXrayTraits::getProtoKey(holder) == JSProto_Function) { 216 return baseInstance.call(cx, wrapper, args); 217 } 218 219 JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); 220 js::ReportIsNotFunction(cx, v); 221 return false; 222 } 223 224 static bool construct(JSContext* cx, JS::HandleObject wrapper, 225 const JS::CallArgs& args, 226 const js::Wrapper& baseInstance); 227 getPrototype(JSContext * cx,JS::HandleObject wrapper,JS::HandleObject target,JS::MutableHandleObject protop)228 bool getPrototype(JSContext* cx, JS::HandleObject wrapper, 229 JS::HandleObject target, JS::MutableHandleObject protop) { 230 JS::RootedObject holder(cx, ensureHolder(cx, wrapper)); 231 if (!holder) { 232 return false; 233 } 234 JSProtoKey key = getProtoKey(holder); 235 if (isPrototype(holder)) { 236 JSProtoKey protoKey = js::InheritanceProtoKeyForStandardClass(key); 237 if (protoKey == JSProto_Null) { 238 protop.set(nullptr); 239 return true; 240 } 241 key = protoKey; 242 } 243 244 { 245 JSAutoRealm ar(cx, target); 246 if (!JS_GetClassPrototype(cx, key, protop)) { 247 return false; 248 } 249 } 250 return JS_WrapObject(cx, protop); 251 } 252 preserveWrapper(JSObject * target)253 virtual void preserveWrapper(JSObject* target) override { 254 // In the case of pure JS objects, there is no underlying object, and 255 // the target is the canonical representation of state. If it gets 256 // collected, then expandos and such should be collected too. So there's 257 // nothing to do here. 258 } 259 260 enum { 261 SLOT_PROTOKEY = HOLDER_SHARED_SLOT_COUNT, 262 SLOT_ISPROTOTYPE, 263 SLOT_CONSTRUCTOR_FOR, 264 SLOT_COUNT 265 }; 266 virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override; 267 getProtoKey(JSObject * holder)268 static JSProtoKey getProtoKey(JSObject* holder) { 269 int32_t key = JS::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32(); 270 return static_cast<JSProtoKey>(key); 271 } 272 isPrototype(JSObject * holder)273 static bool isPrototype(JSObject* holder) { 274 return JS::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean(); 275 } 276 constructorFor(JSObject * holder)277 static JSProtoKey constructorFor(JSObject* holder) { 278 int32_t key = JS::GetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR).toInt32(); 279 return static_cast<JSProtoKey>(key); 280 } 281 282 // Operates in the wrapper compartment. 283 static bool getOwnPropertyFromWrapperIfSafe( 284 JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 285 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); 286 287 // Like the above, but operates in the target compartment. wrapperGlobal is 288 // the caller's global (must be in the wrapper compartment). 289 static bool getOwnPropertyFromTargetIfSafe( 290 JSContext* cx, JS::HandleObject target, JS::HandleObject wrapper, 291 JS::HandleObject wrapperGlobal, JS::HandleId id, 292 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); 293 294 static const JSClass HolderClass; 295 static JSXrayTraits singleton; 296 }; 297 298 // These traits are used when the target is not Xrayable and we therefore want 299 // to make it opaque modulo the usual Xray machinery (like expandos and 300 // .wrappedJSObject). 301 class OpaqueXrayTraits : public XrayTraits { 302 public: 303 static const XrayType Type = XrayForOpaqueObject; 304 305 virtual bool resolveOwnProperty( 306 JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, 307 JS::HandleObject holder, JS::HandleId id, 308 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override; 309 defineProperty(JSContext * cx,JS::HandleObject wrapper,JS::HandleId id,JS::Handle<JS::PropertyDescriptor> desc,JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc,JS::Handle<JSObject * > existingHolder,JS::ObjectOpResult & result,bool * defined)310 bool defineProperty( 311 JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 312 JS::Handle<JS::PropertyDescriptor> desc, 313 JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc, 314 JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result, 315 bool* defined) { 316 *defined = false; 317 return true; 318 } 319 enumerateNames(JSContext * cx,JS::HandleObject wrapper,unsigned flags,JS::MutableHandleIdVector props)320 virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, 321 unsigned flags, JS::MutableHandleIdVector props) { 322 return true; 323 } 324 call(JSContext * cx,JS::HandleObject wrapper,const JS::CallArgs & args,const js::Wrapper & baseInstance)325 static bool call(JSContext* cx, JS::HandleObject wrapper, 326 const JS::CallArgs& args, const js::Wrapper& baseInstance) { 327 JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); 328 js::ReportIsNotFunction(cx, v); 329 return false; 330 } 331 construct(JSContext * cx,JS::HandleObject wrapper,const JS::CallArgs & args,const js::Wrapper & baseInstance)332 static bool construct(JSContext* cx, JS::HandleObject wrapper, 333 const JS::CallArgs& args, 334 const js::Wrapper& baseInstance) { 335 JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); 336 js::ReportIsNotFunction(cx, v); 337 return false; 338 } 339 getPrototype(JSContext * cx,JS::HandleObject wrapper,JS::HandleObject target,JS::MutableHandleObject protop)340 bool getPrototype(JSContext* cx, JS::HandleObject wrapper, 341 JS::HandleObject target, JS::MutableHandleObject protop) { 342 // Opaque wrappers just get targetGlobal.Object.prototype as their 343 // prototype. This is preferable to using a null prototype because it 344 // lets things like |toString| and |__proto__| work. 345 { 346 JSAutoRealm ar(cx, target); 347 if (!JS_GetClassPrototype(cx, JSProto_Object, protop)) { 348 return false; 349 } 350 } 351 return JS_WrapObject(cx, protop); 352 } 353 getBuiltinClass(JSContext * cx,JS::HandleObject wrapper,const js::Wrapper & baseInstance,js::ESClass * cls)354 static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, 355 const js::Wrapper& baseInstance, 356 js::ESClass* cls) { 357 *cls = js::ESClass::Other; 358 return true; 359 } 360 className(JSContext * cx,JS::HandleObject wrapper,const js::Wrapper & baseInstance)361 static const char* className(JSContext* cx, JS::HandleObject wrapper, 362 const js::Wrapper& baseInstance) { 363 return "Opaque"; 364 } 365 preserveWrapper(JSObject * target)366 virtual void preserveWrapper(JSObject* target) override {} 367 createHolder(JSContext * cx,JSObject * wrapper)368 virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override { 369 return JS_NewObjectWithGivenProto(cx, &HolderClass, nullptr); 370 } 371 372 static OpaqueXrayTraits singleton; 373 }; 374 375 XrayType GetXrayType(JSObject* obj); 376 XrayTraits* GetXrayTraits(JSObject* obj); 377 378 template <typename Base, typename Traits> 379 class XrayWrapper : public Base { 380 static_assert(std::is_base_of_v<js::BaseProxyHandler, Base>, 381 "Base *must* derive from js::BaseProxyHandler"); 382 383 public: XrayWrapper(unsigned flags)384 constexpr explicit XrayWrapper(unsigned flags) 385 : Base(flags | WrapperFactory::IS_XRAY_WRAPPER_FLAG, 386 /* aHasPrototype = */ true){}; 387 388 /* Standard internal methods. */ 389 virtual bool getOwnPropertyDescriptor( 390 JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id, 391 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) 392 const override; 393 virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper, 394 JS::Handle<jsid> id, 395 JS::Handle<JS::PropertyDescriptor> desc, 396 JS::ObjectOpResult& result) const override; 397 virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper, 398 JS::MutableHandleIdVector props) const override; 399 virtual bool delete_(JSContext* cx, JS::Handle<JSObject*> wrapper, 400 JS::Handle<jsid> id, 401 JS::ObjectOpResult& result) const override; 402 virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> wrapper, 403 JS::MutableHandleIdVector props) const override; 404 virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper, 405 JS::MutableHandleObject protop) const override; 406 virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper, 407 JS::HandleObject proto, 408 JS::ObjectOpResult& result) const override; 409 virtual bool getPrototypeIfOrdinary( 410 JSContext* cx, JS::HandleObject wrapper, bool* isOrdinary, 411 JS::MutableHandleObject protop) const override; 412 virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper, 413 bool* succeeded) const override; 414 virtual bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> wrapper, 415 JS::ObjectOpResult& result) const override; 416 virtual bool isExtensible(JSContext* cx, JS::Handle<JSObject*> wrapper, 417 bool* extensible) const override; 418 virtual bool has(JSContext* cx, JS::Handle<JSObject*> wrapper, 419 JS::Handle<jsid> id, bool* bp) const override; 420 virtual bool get(JSContext* cx, JS::Handle<JSObject*> wrapper, 421 JS::HandleValue receiver, JS::Handle<jsid> id, 422 JS::MutableHandle<JS::Value> vp) const override; 423 virtual bool set(JSContext* cx, JS::Handle<JSObject*> wrapper, 424 JS::Handle<jsid> id, JS::Handle<JS::Value> v, 425 JS::Handle<JS::Value> receiver, 426 JS::ObjectOpResult& result) const override; 427 virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper, 428 const JS::CallArgs& args) const override; 429 virtual bool construct(JSContext* cx, JS::Handle<JSObject*> wrapper, 430 const JS::CallArgs& args) const override; 431 432 /* SpiderMonkey extensions. */ 433 virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> wrapper, 434 JS::Handle<jsid> id, bool* bp) const override; 435 virtual bool getOwnEnumerablePropertyKeys( 436 JSContext* cx, JS::Handle<JSObject*> wrapper, 437 JS::MutableHandleIdVector props) const override; 438 439 virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject wapper, 440 js::ESClass* cls) const override; 441 virtual bool hasInstance(JSContext* cx, JS::HandleObject wrapper, 442 JS::MutableHandleValue v, bool* bp) const override; 443 virtual const char* className(JSContext* cx, 444 JS::HandleObject proxy) const override; 445 446 static const XrayWrapper singleton; 447 448 protected: 449 bool getPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper, 450 unsigned flags, JS::MutableHandleIdVector props) const; 451 }; 452 453 #define PermissiveXrayDOM \ 454 xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::DOMXrayTraits> 455 #define PermissiveXrayJS \ 456 xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::JSXrayTraits> 457 #define PermissiveXrayOpaque \ 458 xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::OpaqueXrayTraits> 459 460 extern template class PermissiveXrayDOM; 461 extern template class PermissiveXrayJS; 462 extern template class PermissiveXrayOpaque; 463 464 /* 465 * Slots for Xray expando objects. See comments in XrayWrapper.cpp for details 466 * of how these get used; we mostly want the value of JSSLOT_EXPANDO_COUNT here. 467 */ 468 enum ExpandoSlots { 469 JSSLOT_EXPANDO_NEXT = 0, 470 JSSLOT_EXPANDO_ORIGIN, 471 JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER, 472 JSSLOT_EXPANDO_PROTOTYPE, 473 JSSLOT_EXPANDO_COUNT 474 }; 475 476 extern const JSClassOps XrayExpandoObjectClassOps; 477 478 /* 479 * Clear the given slot on all Xray expandos for the given object. 480 * 481 * No-op when called on non-main threads (where Xrays don't exist). 482 */ 483 void ClearXrayExpandoSlots(JSObject* target, size_t slotIndex); 484 485 /* 486 * Ensure the given wrapper has an expando object and return it. This can 487 * return null on failure. Will only be called when "wrapper" is an Xray for a 488 * DOM object. 489 */ 490 JSObject* EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper); 491 492 // Information about xrays for use by the JITs. 493 extern JS::XrayJitInfo gXrayJitInfo; 494 495 } // namespace xpc 496 497 #endif 498