tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

XrayWrapper.h (19879B)


      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/Maybe.h"
     11 
     12 #include "WrapperFactory.h"
     13 
     14 #include "jsapi.h"
     15 #include "jsfriendapi.h"
     16 #include "js/friend/XrayJitInfo.h"  // JS::XrayJitInfo
     17 #include "js/Object.h"              // JS::GetReservedSlot
     18 #include "js/Proxy.h"
     19 #include "js/Wrapper.h"
     20 #include "mozilla/dom/ScriptSettings.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 
     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 
     69  bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
     70               JS::ObjectOpResult& result) {
     71    return result.succeed();
     72  }
     73 
     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 
     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 void ExpandoObjectFinalize(JS::GCContext* gcx, JSObject* obj);
    144 
    145 class DOMXrayTraits : public XrayTraits {
    146 public:
    147  constexpr DOMXrayTraits() = default;
    148 
    149  static const XrayType Type = XrayForDOMObject;
    150 
    151  virtual bool resolveOwnProperty(
    152      JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
    153      JS::HandleObject holder, JS::HandleId id,
    154      JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override;
    155 
    156  bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
    157               JS::ObjectOpResult& result);
    158 
    159  bool defineProperty(
    160      JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
    161      JS::Handle<JS::PropertyDescriptor> desc,
    162      JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc,
    163      JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result,
    164      bool* done);
    165  virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper,
    166                              unsigned flags, JS::MutableHandleIdVector props);
    167  static bool call(JSContext* cx, JS::HandleObject wrapper,
    168                   const JS::CallArgs& args, const js::Wrapper& baseInstance);
    169  static bool construct(JSContext* cx, JS::HandleObject wrapper,
    170                        const JS::CallArgs& args,
    171                        const js::Wrapper& baseInstance);
    172 
    173  static bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
    174                           JS::HandleObject target,
    175                           JS::MutableHandleObject protop);
    176 
    177  virtual void preserveWrapper(JSObject* target) override;
    178 
    179  virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override;
    180 
    181  static DOMXrayTraits singleton;
    182 
    183 protected:
    184  virtual const JSClass* getExpandoClass(
    185      JSContext* cx, JS::HandleObject target) const override;
    186 };
    187 
    188 class JSXrayTraits : public XrayTraits {
    189 public:
    190  static const XrayType Type = XrayForJSObject;
    191 
    192  virtual bool resolveOwnProperty(
    193      JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
    194      JS::HandleObject holder, JS::HandleId id,
    195      JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override;
    196 
    197  bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
    198               JS::ObjectOpResult& result);
    199 
    200  bool defineProperty(
    201      JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
    202      JS::Handle<JS::PropertyDescriptor> desc,
    203      JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc,
    204      JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result,
    205      bool* defined);
    206 
    207  virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper,
    208                              unsigned flags, JS::MutableHandleIdVector props);
    209 
    210  static bool call(JSContext* cx, JS::HandleObject wrapper,
    211                   const JS::CallArgs& args, const js::Wrapper& baseInstance) {
    212    JSXrayTraits& self = JSXrayTraits::singleton;
    213    JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper));
    214    if (!holder) {
    215      return false;
    216    }
    217    JSProtoKey key = xpc::JSXrayTraits::getProtoKey(holder);
    218    if (key == JSProto_Function || key == JSProto_BoundFunction) {
    219      return baseInstance.call(cx, wrapper, args);
    220    }
    221 
    222    JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
    223    js::ReportIsNotFunction(cx, v);
    224    return false;
    225  }
    226 
    227  static bool construct(JSContext* cx, JS::HandleObject wrapper,
    228                        const JS::CallArgs& args,
    229                        const js::Wrapper& baseInstance);
    230 
    231  bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
    232                    JS::HandleObject target, JS::MutableHandleObject protop) {
    233    JS::RootedObject holder(cx, ensureHolder(cx, wrapper));
    234    if (!holder) {
    235      return false;
    236    }
    237    JSProtoKey key = getProtoKey(holder);
    238    if (isPrototype(holder)) {
    239      JSProtoKey protoKey = js::InheritanceProtoKeyForStandardClass(key);
    240      if (protoKey == JSProto_Null) {
    241        protop.set(nullptr);
    242        return true;
    243      }
    244      key = protoKey;
    245    }
    246 
    247    {
    248      JSAutoRealm ar(cx, target);
    249      if (!JS_GetClassPrototype(cx, key, protop)) {
    250        return false;
    251      }
    252    }
    253    return JS_WrapObject(cx, protop);
    254  }
    255 
    256  virtual void preserveWrapper(JSObject* target) override {
    257    // In the case of pure JS objects, there is no underlying object, and
    258    // the target is the canonical representation of state. If it gets
    259    // collected, then expandos and such should be collected too. So there's
    260    // nothing to do here.
    261  }
    262 
    263  enum {
    264    SLOT_PROTOKEY = HOLDER_SHARED_SLOT_COUNT,
    265    SLOT_ISPROTOTYPE,
    266    SLOT_CONSTRUCTOR_FOR,
    267    SLOT_COUNT
    268  };
    269  virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override;
    270 
    271  static JSProtoKey getProtoKey(JSObject* holder) {
    272    int32_t key = JS::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32();
    273    return static_cast<JSProtoKey>(key);
    274  }
    275 
    276  static bool isPrototype(JSObject* holder) {
    277    return JS::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean();
    278  }
    279 
    280  static JSProtoKey constructorFor(JSObject* holder) {
    281    int32_t key = JS::GetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR).toInt32();
    282    return static_cast<JSProtoKey>(key);
    283  }
    284 
    285  // Operates in the wrapper compartment.
    286  static bool getOwnPropertyFromWrapperIfSafe(
    287      JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
    288      JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
    289 
    290  // Like the above, but operates in the target compartment. wrapperGlobal is
    291  // the caller's global (must be in the wrapper compartment).
    292  static bool getOwnPropertyFromTargetIfSafe(
    293      JSContext* cx, JS::HandleObject target, JS::HandleObject wrapper,
    294      JS::HandleObject wrapperGlobal, JS::HandleId id,
    295      JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
    296 
    297  static const JSClass HolderClass;
    298  static JSXrayTraits singleton;
    299 };
    300 
    301 // These traits are used when the target is not Xrayable and we therefore want
    302 // to make it opaque modulo the usual Xray machinery (like expandos and
    303 // .wrappedJSObject).
    304 class OpaqueXrayTraits : public XrayTraits {
    305 public:
    306  static const XrayType Type = XrayForOpaqueObject;
    307 
    308  virtual bool resolveOwnProperty(
    309      JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target,
    310      JS::HandleObject holder, JS::HandleId id,
    311      JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override;
    312 
    313  bool defineProperty(
    314      JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
    315      JS::Handle<JS::PropertyDescriptor> desc,
    316      JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc,
    317      JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result,
    318      bool* defined) {
    319    *defined = false;
    320    return true;
    321  }
    322 
    323  virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper,
    324                              unsigned flags, JS::MutableHandleIdVector props) {
    325    return true;
    326  }
    327 
    328  static bool call(JSContext* cx, JS::HandleObject wrapper,
    329                   const JS::CallArgs& args, const js::Wrapper& baseInstance) {
    330    JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
    331    js::ReportIsNotFunction(cx, v);
    332    return false;
    333  }
    334 
    335  static bool construct(JSContext* cx, JS::HandleObject wrapper,
    336                        const JS::CallArgs& args,
    337                        const js::Wrapper& baseInstance) {
    338    JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
    339    js::ReportIsNotFunction(cx, v);
    340    return false;
    341  }
    342 
    343  bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
    344                    JS::HandleObject target, JS::MutableHandleObject protop) {
    345    // Opaque wrappers just get targetGlobal.Object.prototype as their
    346    // prototype. This is preferable to using a null prototype because it
    347    // lets things like |toString| and |__proto__| work.
    348    {
    349      JSAutoRealm ar(cx, target);
    350      if (!JS_GetClassPrototype(cx, JSProto_Object, protop)) {
    351        return false;
    352      }
    353    }
    354    return JS_WrapObject(cx, protop);
    355  }
    356 
    357  static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper,
    358                              const js::Wrapper& baseInstance,
    359                              js::ESClass* cls) {
    360    *cls = js::ESClass::Other;
    361    return true;
    362  }
    363 
    364  static const char* className(JSContext* cx, JS::HandleObject wrapper,
    365                               const js::Wrapper& baseInstance) {
    366    return "Opaque";
    367  }
    368 
    369  virtual void preserveWrapper(JSObject* target) override {}
    370 
    371  virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override {
    372    return JS_NewObjectWithGivenProto(cx, &HolderClass, nullptr);
    373  }
    374 
    375  static OpaqueXrayTraits singleton;
    376 };
    377 
    378 XrayType GetXrayType(JSObject* obj);
    379 XrayTraits* GetXrayTraits(JSObject* obj);
    380 
    381 template <typename Base, typename Traits>
    382 class XrayWrapper : public Base {
    383  static_assert(std::is_base_of_v<js::BaseProxyHandler, Base>,
    384                "Base *must* derive from js::BaseProxyHandler");
    385 
    386 public:
    387  constexpr explicit XrayWrapper(unsigned flags)
    388      : Base(flags | WrapperFactory::IS_XRAY_WRAPPER_FLAG,
    389             /* aHasPrototype = */ true) {};
    390 
    391  /* Standard internal methods. */
    392  virtual bool getOwnPropertyDescriptor(
    393      JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
    394      JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc)
    395      const override;
    396  virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
    397                              JS::Handle<jsid> id,
    398                              JS::Handle<JS::PropertyDescriptor> desc,
    399                              JS::ObjectOpResult& result) const override;
    400  virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
    401                               JS::MutableHandleIdVector props) const override;
    402  virtual bool delete_(JSContext* cx, JS::Handle<JSObject*> wrapper,
    403                       JS::Handle<jsid> id,
    404                       JS::ObjectOpResult& result) const override;
    405  virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> wrapper,
    406                         JS::MutableHandleIdVector props) const override;
    407  virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
    408                            JS::MutableHandleObject protop) const override;
    409  virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper,
    410                            JS::HandleObject proto,
    411                            JS::ObjectOpResult& result) const override;
    412  virtual bool getPrototypeIfOrdinary(
    413      JSContext* cx, JS::HandleObject wrapper, bool* isOrdinary,
    414      JS::MutableHandleObject protop) const override;
    415  virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper,
    416                                     bool* succeeded) const override;
    417  virtual bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> wrapper,
    418                                 JS::ObjectOpResult& result) const override;
    419  virtual bool isExtensible(JSContext* cx, JS::Handle<JSObject*> wrapper,
    420                            bool* extensible) const override;
    421  virtual bool has(JSContext* cx, JS::Handle<JSObject*> wrapper,
    422                   JS::Handle<jsid> id, bool* bp) const override;
    423  virtual bool get(JSContext* cx, JS::Handle<JSObject*> wrapper,
    424                   JS::HandleValue receiver, JS::Handle<jsid> id,
    425                   JS::MutableHandle<JS::Value> vp) const override;
    426  virtual bool set(JSContext* cx, JS::Handle<JSObject*> wrapper,
    427                   JS::Handle<jsid> id, JS::Handle<JS::Value> v,
    428                   JS::Handle<JS::Value> receiver,
    429                   JS::ObjectOpResult& result) const override;
    430  virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper,
    431                    const JS::CallArgs& args) const override;
    432  virtual bool construct(JSContext* cx, JS::Handle<JSObject*> wrapper,
    433                         const JS::CallArgs& args) const override;
    434 
    435  /* SpiderMonkey extensions. */
    436  virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> wrapper,
    437                      JS::Handle<jsid> id, bool* bp) const override;
    438  virtual bool getOwnEnumerablePropertyKeys(
    439      JSContext* cx, JS::Handle<JSObject*> wrapper,
    440      JS::MutableHandleIdVector props) const override;
    441 
    442  virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject wapper,
    443                               js::ESClass* cls) const override;
    444  virtual const char* className(JSContext* cx,
    445                                JS::HandleObject proxy) const override;
    446 
    447  static const XrayWrapper singleton;
    448 
    449 protected:
    450  bool getPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
    451                       unsigned flags, JS::MutableHandleIdVector props) const;
    452 };
    453 
    454 #define PermissiveXrayDOM \
    455  xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::DOMXrayTraits>
    456 #define PermissiveXrayJS \
    457  xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::JSXrayTraits>
    458 #define PermissiveXrayOpaque \
    459  xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::OpaqueXrayTraits>
    460 
    461 extern template class PermissiveXrayDOM;
    462 extern template class PermissiveXrayJS;
    463 extern template class PermissiveXrayOpaque;
    464 
    465 /*
    466 * Slots for Xray expando objects.  See comments in XrayWrapper.cpp for details
    467 * of how these get used; we mostly want the value of JSSLOT_EXPANDO_COUNT here.
    468 */
    469 enum ExpandoSlots {
    470  JSSLOT_EXPANDO_NEXT = 0,
    471  JSSLOT_EXPANDO_ORIGIN,
    472  JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER,
    473  JSSLOT_EXPANDO_PROTOTYPE,
    474  JSSLOT_EXPANDO_COUNT
    475 };
    476 
    477 extern const JSClassOps XrayExpandoObjectClassOps;
    478 
    479 /*
    480 * Call aFunc on all Xray expandos for the given object. aFunc will be passed a
    481 * JSObject pointer to an Xray expando object.
    482 *
    483 * No-op when called on non-main threads (where Xrays don't exist).
    484 */
    485 template <typename F>
    486 void ForEachXrayExpandoObject(JS::RootingContext* aCx, JSObject* aTarget,
    487                              F&& aFunc) {
    488  if (!NS_IsMainThread()) {
    489    // No Xrays
    490    return;
    491  }
    492 
    493  MOZ_ASSERT(GetXrayTraits(aTarget) == &DOMXrayTraits::singleton);
    494  JS::RootedObject rootedTarget(aCx, aTarget);
    495  JS::RootedObject head(aCx,
    496                        DOMXrayTraits::singleton.getExpandoChain(rootedTarget));
    497  while (head) {
    498    aFunc(head);
    499    head = JS::GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
    500  }
    501 }
    502 
    503 /*
    504 * Ensure the given wrapper has an expando object and return it.  This can
    505 * return null on failure.  Will only be called when "wrapper" is an Xray for a
    506 * DOM object.
    507 */
    508 JSObject* EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper);
    509 
    510 // Information about xrays for use by the JITs.
    511 extern JS::XrayJitInfo gXrayJitInfo;
    512 
    513 }  // namespace xpc
    514 
    515 #endif