Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/base/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 18 kB image not shown  

Quelle  MaybeCrossOriginObject.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "mozilla/dom/MaybeCrossOriginObject.h"

#include "mozilla/BasePrincipal.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMJSProxyHandler.h"
#include "mozilla/dom/RemoteObjectProxy.h"
#include "js/CallAndConstruct.h"    // JS::Call
#include "js/friend/WindowProxy.h"  // js::IsWindowProxy
#include "js/Object.h"              // JS::GetClass
#include "js/PropertyAndElement.h"  // JS_DefineFunctions, JS_DefineProperties
#include "js/PropertyDescriptor.h"  // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById
#include "js/Proxy.h"
#include "js/RootingAPI.h"
#include "js/WeakMap.h"
#include "js/Wrapper.h"
#include "jsfriendapi.h"
#include "AccessCheck.h"
#include "nsContentUtils.h"

#ifdef DEBUG
static bool IsLocation(JSObject* obj) {
  return strcmp(JS::GetClass(obj)->name, "Location") == 0;
}
#endif  // DEBUG

namespace mozilla::dom {

/* static */
bool MaybeCrossOriginObjectMixins::IsPlatformObjectSameOrigin(JSContext* cx,
                                                              JSObject* obj) {
  MOZ_ASSERT(!js::IsCrossCompartmentWrapper(obj));
  // WindowProxy and Window must always be same-Realm, so we can do
  // our IsPlatformObjectSameOrigin check against either one.  But verify that
  // in case we have a WindowProxy the right things happen.
  MOZ_ASSERT(js::GetNonCCWObjectRealm(obj) ==
                 // "true" for second arg means to unwrap WindowProxy to
                 // get at the Window.
                 js::GetNonCCWObjectRealm(js::UncheckedUnwrap(obj, true)),
             "WindowProxy not same-Realm as Window?");

  BasePrincipal* subjectPrincipal =
      BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx));
  BasePrincipal* objectPrincipal =
      BasePrincipal::Cast(nsContentUtils::ObjectPrincipal(obj));

  // The spec effectively has an EqualsConsideringDomain check here,
  // because the spec has no concept of asymmetric security
  // relationships.  But we shouldn't ever end up here in the
  // asymmetric case anyway: That case should end up with Xrays, which
  // don't call into this code.
  //
  // Let's assert that EqualsConsideringDomain and
  // SubsumesConsideringDomain give the same results and use
  // EqualsConsideringDomain for the check we actually do, since it's
  // stricter and more closely matches the spec.
  //
  // That said, if the (not very well named)
  // OriginAttributes::IsRestrictOpenerAccessForFPI() method returns
  // false, we want to use FastSubsumesConsideringDomainIgnoringFPD
  // instead of FastEqualsConsideringDomain, because in that case we
  // still want to treat things which are in different first-party
  // contexts as same-origin.
  MOZ_ASSERT(
      subjectPrincipal->FastEqualsConsideringDomain(objectPrincipal) ==
          subjectPrincipal->FastSubsumesConsideringDomain(objectPrincipal),
      "Why are we in an asymmetric case here?");
  if (OriginAttributes::IsRestrictOpenerAccessForFPI()) {
    return subjectPrincipal->FastEqualsConsideringDomain(objectPrincipal);
  }

  return subjectPrincipal->FastSubsumesConsideringDomainIgnoringFPD(
             objectPrincipal) &&
         objectPrincipal->FastSubsumesConsideringDomainIgnoringFPD(
             subjectPrincipal);
}

bool MaybeCrossOriginObjectMixins::CrossOriginGetOwnPropertyHelper(
    JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
    JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const {
  MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
             "Why did we get called?");
  // First check for an IDL-defined cross-origin property with the given name.
  // This corresponds to
  // https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)
  // step 2.
  JS::Rooted<JSObject*> holder(cx);
  if (!EnsureHolder(cx, obj, &holder)) {
    return false;
  }

  return JS_GetOwnPropertyDescriptorById(cx, holder, id, desc);
}

/* static */
bool MaybeCrossOriginObjectMixins::CrossOriginPropertyFallback(
    JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
    JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) {
  MOZ_ASSERT(desc.isNothing(), "Why are we being called?");

  // Step 1.
  if (xpc::IsCrossOriginWhitelistedProp(cx, id)) {
    // Spec says to return PropertyDescriptor {
    //   [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
    //   [[Configurable]]: true
    // }.
    desc.set(Some(JS::PropertyDescriptor::Data(
        JS::UndefinedValue(), {JS::PropertyAttribute::Configurable})));
    return true;
  }

  // Step 2.
  return ReportCrossOriginDenial(cx, id, "access"_ns);
}

/* static */
bool MaybeCrossOriginObjectMixins::CrossOriginGet(
    JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JS::Value> receiver,
    JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) {
  // This is fairly similar to BaseProxyHandler::get, but there are some
  // differences.  Most importantly, we want to throw if we have a descriptor
  // with no getter, while BaseProxyHandler::get returns undefined.  The other
  // big difference is that we don't have to worry about prototypes (ours is
  // always null).

  // We want to invoke [[GetOwnProperty]] on "obj", but _without_ entering its
  // compartment, because for the proxies we have here [[GetOwnProperty]] will
  // do security checks based on the current Realm.  Unfortunately,
  // JS_GetPropertyDescriptorById asserts that compartments match.  Luckily, we
  // know that "obj" is a proxy here, so we can directly call its
  // getOwnPropertyDescriptor() hook.
  //
  // It looks like Proxy::getOwnPropertyDescriptor is not public, so just grab
  // the handler and call its getOwnPropertyDescriptor hook directly.
  MOZ_ASSERT(js::IsProxy(obj), "How did we get a bogus object here?");
  MOZ_ASSERT(
      js::IsWindowProxy(obj) || IsLocation(obj) || IsRemoteObjectProxy(obj),
      "Unexpected proxy");
  MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
             "Why did we get called?");
  js::AssertSameCompartment(cx, receiver);

  // Step 1.
  JS::Rooted<Maybe<JS::PropertyDescriptor>> desc(cx);
  if (!js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, obj, id, &desc)) {
    return false;
  }

  // Step 2.
  MOZ_ASSERT(desc.isSome(),
             "Callees should throw in all cases when they are not finding a "
             "property decriptor");
  desc->assertComplete();

  // Step 3.
  if (desc->isDataDescriptor()) {
    vp.set(desc->value());
    return true;
  }

  // Step 4.
  MOZ_ASSERT(desc->isAccessorDescriptor());

  // Step 5.
  JS::Rooted<JSObject*> getter(cx);
  if (!desc->hasGetter() || !(getter = desc->getter())) {
    // Step 6.
    return ReportCrossOriginDenial(cx, id, "get"_ns);
  }

  // Step 7.
  return JS::Call(cx, receiver, getter, JS::HandleValueArray::empty(), vp);
}

/* static */
bool MaybeCrossOriginObjectMixins::CrossOriginSet(
    JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
    JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
    JS::ObjectOpResult& result) {
  // We want to invoke [[GetOwnProperty]] on "obj", but _without_ entering its
  // compartment, because for the proxies we have here [[GetOwnProperty]] will
  // do security checks based on the current Realm.  Unfortunately,
  // JS_GetPropertyDescriptorById asserts that compartments match.  Luckily, we
  // know that "obj" is a proxy here, so we can directly call its
  // getOwnPropertyDescriptor() hook.
  //
  // It looks like Proxy::getOwnPropertyDescriptor is not public, so just grab
  // the handler and call its getOwnPropertyDescriptor hook directly.
  MOZ_ASSERT(js::IsProxy(obj), "How did we get a bogus object here?");
  MOZ_ASSERT(
      js::IsWindowProxy(obj) || IsLocation(obj) || IsRemoteObjectProxy(obj),
      "Unexpected proxy");
  MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
             "Why did we get called?");
  js::AssertSameCompartment(cx, receiver);
  js::AssertSameCompartment(cx, v);

  // Step 1.
  JS::Rooted<Maybe<JS::PropertyDescriptor>> desc(cx);
  if (!js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, obj, id, &desc)) {
    return false;
  }

  // Step 2.
  MOZ_ASSERT(desc.isSome(),
             "Callees should throw in all cases when they are not finding a "
             "property decriptor");
  desc->assertComplete();

  // Step 3.
  JS::Rooted<JSObject*> setter(cx);
  if (desc->hasSetter() && (setter = desc->setter())) {
    JS::Rooted<JS::Value> ignored(cx);
    // Step 3.1.
    if (!JS::Call(cx, receiver, setter, JS::HandleValueArray(v), &ignored)) {
      return false;
    }

    // Step 3.2.
    return result.succeed();
  }

  // Step 4.
  return ReportCrossOriginDenial(cx, id, "set"_ns);
}

/* static */
bool MaybeCrossOriginObjectMixins::EnsureHolder(
    JSContext* cx, JS::Handle<JSObject*> obj, size_t slot,
    const CrossOriginProperties& properties,
    JS::MutableHandle<JSObject*> holder) {
  MOZ_ASSERT(!IsPlatformObjectSameOrigin(cx, obj) || IsRemoteObjectProxy(obj),
             "Why are we calling this at all in same-origin cases?");
  // We store the holders in a weakmap stored in obj's slot.  Our object is
  // always a proxy, so we can just go ahead and use GetProxyReservedSlot here.
  JS::Rooted<JS::Value> weakMapVal(cx, js::GetProxyReservedSlot(obj, slot));
  if (weakMapVal.isUndefined()) {
    // Enter the Realm of "obj" when we allocate the WeakMap, since we are going
    // to store it in a slot on "obj" and in general we may not be
    // same-compartment with "obj" here.
    JSAutoRealm ar(cx, obj);
    JSObject* newMap = JS::NewWeakMapObject(cx);
    if (!newMap) {
      return false;
    }
    weakMapVal.setObject(*newMap);
    js::SetProxyReservedSlot(obj, slot, weakMapVal);
  }
  MOZ_ASSERT(weakMapVal.isObject(),
             "How did a non-object else end up in this slot?");

  JS::Rooted<JSObject*> map(cx, &weakMapVal.toObject());
  MOZ_ASSERT(JS::IsWeakMapObject(map),
             "How did something else end up in this slot?");

  // We need to be in "map"'s compartment to work with it.  Per spec, the key
  // for this map is supposed to be the pair (current settings, relevant
  // settings).  The current settings corresponds to the current Realm of cx.
  // The relevant settings corresponds to the Realm of "obj", but since all of
  // our objects are per-Realm singletons, we are basically using "obj" itself
  // as part of the key.
  //
  // To represent the current settings, we use a dedicated key object of the
  // current-Realm.
  //
  // We can't use the current global, because we can't get a useful
  // cross-compartment wrapper for it; such wrappers would always go
  // through a WindowProxy and would not be guarantee to keep pointing to a
  // single Realm when unwrapped.  We want to grab this key before we start
  // changing Realms.
  //
  // Also we can't use arbitrary object (e.g.: Object.prototype), because at
  // this point those compartments are not same-origin, and don't have access to
  // each other, and the object retrieved here will be wrapped by a security
  // wrapper below, and the wrapper will be stored into the cache
  // (see Compartment::wrap).  Those compartments can get access later by
  // modifying `document.domain`, and wrapping objects after that point
  // shouldn't result in a security wrapper.  Wrap operation looks up the
  // existing wrapper in the cache, that contains the security wrapper created
  // here.  We should use unique/private object here, so that this doesn't
  // affect later wrap operation.
  JS::Rooted<JSObject*> key(cx, JS::GetRealmKeyObject(cx));
  if (!key) {
    return false;
  }

  JS::Rooted<JS::Value> holderVal(cx);
  {  // Scope for working with the map
    JSAutoRealm ar(cx, map);
    if (!MaybeWrapObject(cx, &key)) {
      return false;
    }

    JS::Rooted<JS::Value> keyVal(cx, JS::ObjectValue(*key));
    if (!JS::GetWeakMapEntry(cx, map, keyVal, &holderVal)) {
      return false;
    }
  }

  if (holderVal.isObject()) {
    // We want to do an unchecked unwrap, because the holder (and the current
    // caller) may actually be more privileged than our map.
    holder.set(js::UncheckedUnwrap(&holderVal.toObject()));

    // holder might be a dead object proxy if things got nuked.
    if (!JS_IsDeadWrapper(holder)) {
      MOZ_ASSERT(js::GetContextRealm(cx) == js::GetNonCCWObjectRealm(holder),
                 "How did we end up with a key/value mismatch?");
      return true;
    }
  }

  // We didn't find a usable holder.  Go ahead and allocate one.  At this point
  // we have two options: we could allocate the holder in the current Realm and
  // store a cross-compartment wrapper for it in the map as needed, or we could
  // allocate the holder in the Realm of the map and have it hold
  // cross-compartment references to all the methods it holds, since those
  // methods need to be in our current Realm.  It seems better to allocate the
  // holder in our current Realm.
  bool isChrome = xpc::AccessCheck::isChrome(js::GetContextRealm(cx));
  holder.set(JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
  if (!holder || !JS_DefineProperties(cx, holder, properties.mAttributes) ||
      !JS_DefineFunctions(cx, holder, properties.mMethods) ||
      (isChrome && properties.mChromeOnlyAttributes &&
       !JS_DefineProperties(cx, holder, properties.mChromeOnlyAttributes)) ||
      (isChrome && properties.mChromeOnlyMethods &&
       !JS_DefineFunctions(cx, holder, properties.mChromeOnlyMethods))) {
    return false;
  }

  holderVal.setObject(*holder);
  {  // Scope for working with the map
    JSAutoRealm ar(cx, map);

    // Key is already in the right Realm, but we need to wrap the value.
    if (!MaybeWrapValue(cx, &holderVal)) {
      return false;
    }

    JS::Rooted<JS::Value> keyVal(cx, JS::ObjectValue(*key));
    if (!JS::SetWeakMapEntry(cx, map, keyVal, holderVal)) {
      return false;
    }
  }

  return true;
}

/* static */
bool MaybeCrossOriginObjectMixins::ReportCrossOriginDenial(
    JSContext* aCx, JS::Handle<jsid> aId, const nsACString& aAccessType) {
  xpc::AccessCheck::reportCrossOriginDenial(aCx, aId, aAccessType);
  return false;
}

template <typename Base>
bool MaybeCrossOriginObject<Base>::getPrototype(
    JSContext* cx, JS::Handle<JSObject*> proxy,
    JS::MutableHandle<JSObject*> protop) const {
  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    protop.set(nullptr);
    return true;
  }

  {  // Scope for JSAutoRealm
    JSAutoRealm ar(cx, proxy);
    protop.set(getSameOriginPrototype(cx));
    if (!protop) {
      return false;
    }
  }

  return MaybeWrapObject(cx, protop);
}

template <typename Base>
bool MaybeCrossOriginObject<Base>::setPrototype(
    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<JSObject*> proto,
    JS::ObjectOpResult& result) const {
  // Inlined version of
  // https://tc39.github.io/ecma262/#sec-set-immutable-prototype
  js::AssertSameCompartment(cx, proto);

  // We have to be careful how we get the prototype.  In particular, we do _NOT_
  // want to enter the Realm of "proxy" to do that, in case we're not
  // same-origin with it here.
  JS::Rooted<JSObject*> wrappedProxy(cx, proxy);
  if (!MaybeWrapObject(cx, &wrappedProxy)) {
    return false;
  }

  JS::Rooted<JSObject*> currentProto(cx);
  if (!js::GetObjectProto(cx, wrappedProxy, ¤tProto)) {
    return false;
  }

  if (currentProto != proto) {
    return result.failCantSetProto();
  }

  return result.succeed();
}

template <typename Base>
bool MaybeCrossOriginObject<Base>::getPrototypeIfOrdinary(
    JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
    JS::MutableHandle<JSObject*> protop) const {
  // We have a custom [[GetPrototypeOf]]
  *isOrdinary = false;
  return true;
}

template <typename Base>
bool MaybeCrossOriginObject<Base>::setImmutablePrototype(
    JSContext* cx, JS::Handle<JSObject*> proxy, bool* succeeded) const {
  // We just want to disallow this.
  *succeeded = false;
  return true;
}

template <typename Base>
bool MaybeCrossOriginObject<Base>::isExtensible(JSContext* cx,
                                                JS::Handle<JSObject*> proxy,
                                                bool* extensible) const {
  // We never allow [[PreventExtensions]] to succeed.
  *extensible = true;
  return true;
}

template <typename Base>
bool MaybeCrossOriginObject<Base>::preventExtensions(
    JSContext* cx, JS::Handle<JSObject*> proxy,
    JS::ObjectOpResult& result) const {
  return result.failCantPreventExtensions();
}

template <typename Base>
bool MaybeCrossOriginObject<Base>::defineProperty(
    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
    JS::Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result) const {
  if (!IsPlatformObjectSameOrigin(cx, proxy)) {
    return ReportCrossOriginDenial(cx, id, "define"_ns);
  }

  // Enter the Realm of proxy and do the remaining work in there.
  JSAutoRealm ar(cx, proxy);
  JS::Rooted<JS::PropertyDescriptor> descCopy(cx, desc);
  if (!JS_WrapPropertyDescriptor(cx, &descCopy)) {
    return false;
  }

  JS_MarkCrossZoneId(cx, id);

  return definePropertySameOrigin(cx, proxy, id, descCopy, result);
}

template <typename Base>
bool MaybeCrossOriginObject<Base>::enumerate(
    JSContext* cx, JS::Handle<JSObject*> proxy,
    JS::MutableHandleVector<jsid> props) const {
  // Just get the property keys from ourselves, in whatever Realm we happen to
  // be in. It's important to not enter the Realm of "proxy" here, because that
  // would affect the list of keys we claim to have. We wrap the proxy in the
  // current compartment just to be safe; it doesn't affect behavior as far as
  // CrossOriginObjectWrapper and MaybeCrossOriginObject are concerned.
  JS::Rooted<JSObject*> self(cx, proxy);
  if (!MaybeWrapObject(cx, &self)) {
    return false;
  }

  return js::GetPropertyKeys(cx, self, 0, props);
}

// Force instantiations of the out-of-line template methods we need.
template class MaybeCrossOriginObject<js::Wrapper>;
template class MaybeCrossOriginObject<DOMProxyHandler>;

}  // namespace mozilla::dom

92%


¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.