tor-browser

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

XPCWrappedNativeScope.cpp (17071B)


      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 /* Class used to manage the wrapped native objects within a JS scope. */
      8 
      9 #include "AccessCheck.h"
     10 #include "xpcprivate.h"
     11 #include "XPCWrapper.h"
     12 #include "nsContentUtils.h"
     13 #include "nsCycleCollectionNoteRootCallback.h"
     14 #include "ExpandedPrincipal.h"
     15 #include "mozilla/BasePrincipal.h"
     16 #include "mozilla/Preferences.h"
     17 #include "XPCMaps.h"
     18 #include "js/Object.h"              // JS::GetCompartment
     19 #include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_DefinePropertyById
     20 #include "js/RealmIterators.h"
     21 #include "mozJSModuleLoader.h"
     22 
     23 #include "mozilla/dom/BindingUtils.h"
     24 
     25 using namespace mozilla;
     26 using namespace xpc;
     27 using namespace JS;
     28 
     29 /***************************************************************************/
     30 
     31 static XPCWrappedNativeScopeList& AllScopes() {
     32  return XPCJSRuntime::Get()->GetWrappedNativeScopes();
     33 }
     34 
     35 static bool RemoteXULForbidsXBLScopeForPrincipal(nsIPrincipal* aPrincipal) {
     36  // AllowXULXBLForPrincipal will return true for system principal, but we
     37  // don't want that here.
     38  MOZ_ASSERT(nsContentUtils::IsInitialized());
     39  if (aPrincipal->IsSystemPrincipal()) {
     40    return false;
     41  }
     42 
     43  // If this domain isn't whitelisted, we're done.
     44  if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal)) {
     45    return false;
     46  }
     47 
     48  // Check the pref to determine how we should behave.
     49  return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
     50 }
     51 
     52 static bool RemoteXULForbidsXBLScope(HandleObject aFirstGlobal) {
     53  MOZ_ASSERT(aFirstGlobal);
     54 
     55  // Certain singleton sandoxes are created very early in startup - too early
     56  // to call into AllowXULXBLForPrincipal. We never create XBL scopes for
     57  // sandboxes anway, and certainly not for these singleton scopes. So we just
     58  // short-circuit here.
     59  if (IsSandbox(aFirstGlobal)) {
     60    return false;
     61  }
     62 
     63  nsIPrincipal* principal = xpc::GetObjectPrincipal(aFirstGlobal);
     64  return RemoteXULForbidsXBLScopeForPrincipal(principal);
     65 }
     66 
     67 XPCWrappedNativeScope::XPCWrappedNativeScope(JS::Compartment* aCompartment,
     68                                             JS::HandleObject aFirstGlobal)
     69    : mWrappedNativeMap(mozilla::MakeUnique<Native2WrappedNativeMap>()),
     70      mWrappedNativeProtoMap(
     71          mozilla::MakeUnique<ClassInfo2WrappedNativeProtoMap>()),
     72      mComponents(nullptr),
     73      mCompartment(aCompartment) {
     74 #ifdef DEBUG
     75  for (XPCWrappedNativeScope* cur : AllScopes()) {
     76    MOZ_ASSERT(aCompartment != cur->Compartment(), "dup object");
     77  }
     78 #endif
     79 
     80  AllScopes().insertBack(this);
     81 
     82  MOZ_COUNT_CTOR(XPCWrappedNativeScope);
     83 
     84  // Determine whether we would allow an XBL scope in this situation.
     85  // In addition to being pref-controlled, we also disable XBL scopes for
     86  // remote XUL domains, _except_ if we have an additional pref override set.
     87  //
     88  // Note that we can't quite remove this yet, even though we never actually
     89  // use XBL scopes, because the security manager uses this boolean to make
     90  // decisions that we rely on in our test infrastructure.
     91  //
     92  // FIXME(emilio): Now that the security manager is the only caller probably
     93  // should be renamed, but what's a good name for this?
     94  mAllowContentXBLScope = !RemoteXULForbidsXBLScope(aFirstGlobal);
     95 }
     96 
     97 bool XPCWrappedNativeScope::GetComponentsJSObject(JSContext* cx,
     98                                                  JS::MutableHandleObject obj) {
     99  if (!mComponents) {
    100    bool system = AccessCheck::isChrome(mCompartment);
    101    MOZ_RELEASE_ASSERT(system, "How did we get a non-system Components?");
    102    mComponents = new nsXPCComponents(this);
    103  }
    104 
    105  RootedValue val(cx);
    106  xpcObjectHelper helper(mComponents);
    107  bool ok = XPCConvert::NativeInterface2JSObject(cx, &val, helper, nullptr,
    108                                                 false, nullptr);
    109  if (NS_WARN_IF(!ok)) {
    110    return false;
    111  }
    112 
    113  if (NS_WARN_IF(!val.isObject())) {
    114    return false;
    115  }
    116 
    117  obj.set(&val.toObject());
    118  return true;
    119 }
    120 
    121 static bool DefineSubcomponentProperty(JSContext* aCx, HandleObject aGlobal,
    122                                       nsISupports* aSubcomponent,
    123                                       const nsID* aIID,
    124                                       unsigned int aStringIndex) {
    125  RootedValue subcompVal(aCx);
    126  xpcObjectHelper helper(aSubcomponent);
    127  if (!XPCConvert::NativeInterface2JSObject(aCx, &subcompVal, helper, aIID,
    128                                            false, nullptr))
    129    return false;
    130  if (NS_WARN_IF(!subcompVal.isObject())) {
    131    return false;
    132  }
    133  RootedId id(aCx, XPCJSContext::Get()->GetStringID(aStringIndex));
    134  return JS_DefinePropertyById(aCx, aGlobal, id, subcompVal, 0);
    135 }
    136 
    137 bool XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx) {
    138  RootedObject components(aCx);
    139  if (!GetComponentsJSObject(aCx, &components)) {
    140    return false;
    141  }
    142 
    143  RootedObject global(aCx, CurrentGlobalOrNull(aCx));
    144 
    145  const unsigned attrs = JSPROP_READONLY | JSPROP_RESOLVING | JSPROP_PERMANENT;
    146 
    147  RootedId id(aCx,
    148              XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS));
    149  if (!JS_DefinePropertyById(aCx, global, id, components, attrs)) {
    150    return false;
    151  }
    152 
    153 // _iid can be nullptr if the object implements classinfo.
    154 #define DEFINE_SUBCOMPONENT_PROPERTY(_comp, _type, _iid, _id)                 \
    155  nsCOMPtr<nsIXPCComponents_##_type> obj##_type;                              \
    156  if (NS_FAILED(_comp->Get##_type(getter_AddRefs(obj##_type)))) return false; \
    157  if (!DefineSubcomponentProperty(aCx, global, obj##_type, _iid,              \
    158                                  XPCJSContext::IDX_##_id))                   \
    159    return false;
    160 
    161  DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Interfaces, nullptr, CI)
    162  DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Results, nullptr, CR)
    163 
    164  DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Classes, nullptr, CC)
    165  DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Utils,
    166                               &NS_GET_IID(nsIXPCComponents_Utils), CU)
    167 
    168 #undef DEFINE_SUBCOMPONENT_PROPERTY
    169 
    170  return true;
    171 }
    172 
    173 bool XPCWrappedNativeScope::AttachJSServices(JSContext* aCx) {
    174  RootedObject global(aCx, CurrentGlobalOrNull(aCx));
    175  return mozJSModuleLoader::Get()->DefineJSServices(aCx, global);
    176 }
    177 
    178 bool XPCWrappedNativeScope::XBLScopeStateMatches(nsIPrincipal* aPrincipal) {
    179  return mAllowContentXBLScope ==
    180         !RemoteXULForbidsXBLScopeForPrincipal(aPrincipal);
    181 }
    182 
    183 bool XPCWrappedNativeScope::AllowContentXBLScope(Realm* aRealm) {
    184  // We only disallow XBL scopes in remote XUL situations.
    185  MOZ_ASSERT_IF(!mAllowContentXBLScope, nsContentUtils::AllowXULXBLForPrincipal(
    186                                            xpc::GetRealmPrincipal(aRealm)));
    187  return mAllowContentXBLScope;
    188 }
    189 
    190 namespace xpc {
    191 JSObject* GetUAWidgetScope(JSContext* cx, JSObject* contentScopeArg) {
    192  JS::RootedObject contentScope(cx, contentScopeArg);
    193  JSAutoRealm ar(cx, contentScope);
    194  nsIPrincipal* principal = GetObjectPrincipal(contentScope);
    195 
    196  if (principal->IsSystemPrincipal()) {
    197    return JS::GetNonCCWObjectGlobal(contentScope);
    198  }
    199 
    200  return GetUAWidgetScope(cx, principal);
    201 }
    202 
    203 JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal) {
    204  RootedObject scope(cx, XPCJSRuntime::Get()->GetUAWidgetScope(cx, principal));
    205  NS_ENSURE_TRUE(scope, nullptr);  // See bug 858642.
    206 
    207  scope = js::UncheckedUnwrap(scope);
    208  JS::ExposeObjectToActiveJS(scope);
    209  return scope;
    210 }
    211 
    212 bool AllowContentXBLScope(JS::Realm* realm) {
    213  JS::Compartment* comp = GetCompartmentForRealm(realm);
    214  XPCWrappedNativeScope* scope = CompartmentPrivate::Get(comp)->GetScope();
    215  MOZ_ASSERT(scope);
    216  return scope->AllowContentXBLScope(realm);
    217 }
    218 
    219 } /* namespace xpc */
    220 
    221 XPCWrappedNativeScope::~XPCWrappedNativeScope() {
    222  MOZ_COUNT_DTOR(XPCWrappedNativeScope);
    223 
    224  // We can do additional cleanup assertions here...
    225 
    226  MOZ_ASSERT(0 == mWrappedNativeMap->Count(), "scope has non-empty map");
    227 
    228  MOZ_ASSERT(0 == mWrappedNativeProtoMap->Count(), "scope has non-empty map");
    229 
    230  // This should not be necessary, since the Components object should die
    231  // with the scope but just in case.
    232  if (mComponents) {
    233    mComponents->mScope = nullptr;
    234  }
    235 
    236  // XXX we should assert that we are dead or that xpconnect has shutdown
    237  // XXX might not want to do this at xpconnect shutdown time???
    238  mComponents = nullptr;
    239 
    240  MOZ_RELEASE_ASSERT(!mXrayExpandos.initialized());
    241 
    242  mCompartment = nullptr;
    243 }
    244 
    245 // static
    246 void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt,
    247                                                           JSTracer* trc) {
    248  // Do JS::TraceEdge for all wrapped natives with external references, as
    249  // well as any DOM expando objects.
    250  //
    251  // Note: the GC can call this from a JS helper thread. We don't use
    252  // AllScopes() because that asserts we're on the main thread.
    253 
    254  for (XPCWrappedNativeScope* cur : xpcrt->GetWrappedNativeScopes()) {
    255    for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) {
    256      XPCWrappedNative* wrapper = i.get().value();
    257      if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired()) {
    258        wrapper->TraceSelf(trc);
    259      }
    260    }
    261  }
    262 }
    263 
    264 // static
    265 void XPCWrappedNativeScope::SuspectAllWrappers(
    266    nsCycleCollectionNoteRootCallback& cb) {
    267  for (XPCWrappedNativeScope* cur : AllScopes()) {
    268    for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) {
    269      i.get().value()->Suspect(cb);
    270    }
    271  }
    272 }
    273 
    274 void XPCWrappedNativeScope::UpdateWeakPointersAfterGC(JSTracer* trc) {
    275  // Sweep waivers.
    276  if (mWaiverWrapperMap) {
    277    mWaiverWrapperMap->UpdateWeakPointers(trc);
    278  }
    279 
    280  if (!js::IsCompartmentZoneSweepingOrCompacting(mCompartment)) {
    281    return;
    282  }
    283 
    284  if (!js::CompartmentHasLiveGlobal(mCompartment)) {
    285    GetWrappedNativeMap()->Clear();
    286    mWrappedNativeProtoMap->Clear();
    287 
    288    // The fields below are traced only if there's a live global in the
    289    // compartment, see TraceXPCGlobal. The compartment has no live globals so
    290    // clear these pointers here.
    291    if (mXrayExpandos.initialized()) {
    292      mXrayExpandos.destroy();
    293    }
    294    mIDProto = nullptr;
    295    mIIDProto = nullptr;
    296    mCIDProto = nullptr;
    297    return;
    298  }
    299 
    300  // Sweep mWrappedNativeMap for dying flat JS objects. Moving has already
    301  // been handled by XPCWrappedNative::FlatJSObjectMoved.
    302  for (auto iter = GetWrappedNativeMap()->ModIter(); !iter.done();
    303       iter.next()) {
    304    XPCWrappedNative* wrapper = iter.get().value();
    305    JSObject* obj = wrapper->GetFlatJSObjectPreserveColor();
    306    if (JS_UpdateWeakPointerAfterGCUnbarriered(trc, &obj)) {
    307      MOZ_ASSERT(obj == wrapper->GetFlatJSObjectPreserveColor());
    308      MOZ_ASSERT(JS::GetCompartment(obj) == mCompartment);
    309    } else {
    310      iter.remove();
    311    }
    312  }
    313 
    314  // Sweep mWrappedNativeProtoMap for dying prototype JSObjects. Moving has
    315  // already been handled by XPCWrappedNativeProto::JSProtoObjectMoved.
    316  for (auto i = mWrappedNativeProtoMap->ModIter(); !i.done(); i.next()) {
    317    XPCWrappedNativeProto* proto = i.get().value();
    318    JSObject* obj = proto->GetJSProtoObjectPreserveColor();
    319    if (JS_UpdateWeakPointerAfterGCUnbarriered(trc, &obj)) {
    320      MOZ_ASSERT(JS::GetCompartment(obj) == mCompartment);
    321      MOZ_ASSERT(obj == proto->GetJSProtoObjectPreserveColor());
    322    } else {
    323      i.remove();
    324    }
    325  }
    326 }
    327 
    328 // static
    329 void XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() {
    330  for (XPCWrappedNativeScope* cur : AllScopes()) {
    331    for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) {
    332      i.get().value()->SweepTearOffs();
    333    }
    334  }
    335 }
    336 
    337 // static
    338 void XPCWrappedNativeScope::SystemIsBeingShutDown() {
    339  // We're forcibly killing scopes, rather than allowing them to go away
    340  // when they're ready. As such, we need to do some cleanup before they
    341  // can safely be destroyed.
    342 
    343  for (XPCWrappedNativeScope* cur : AllScopes()) {
    344    // Give the Components object a chance to try to clean up.
    345    if (cur->mComponents) {
    346      cur->mComponents->SystemIsBeingShutDown();
    347    }
    348 
    349    // Null out these pointers to prevent ~ObjectPtr assertion failures if we
    350    // leaked things at shutdown.
    351    cur->mIDProto = nullptr;
    352    cur->mIIDProto = nullptr;
    353    cur->mCIDProto = nullptr;
    354 
    355    // Similarly, destroy mXrayExpandos to prevent assertion failures.
    356    if (cur->mXrayExpandos.initialized()) {
    357      cur->mXrayExpandos.destroy();
    358    }
    359 
    360    // Walk the protos first. Wrapper shutdown can leave dangling
    361    // proto pointers in the proto map.
    362    for (auto i = cur->mWrappedNativeProtoMap->ModIter(); !i.done(); i.next()) {
    363      i.get().value()->SystemIsBeingShutDown();
    364      i.remove();
    365    }
    366    for (auto i = cur->mWrappedNativeMap->ModIter(); !i.done(); i.next()) {
    367      i.get().value()->SystemIsBeingShutDown();
    368      i.remove();
    369    }
    370 
    371    CompartmentPrivate* priv = CompartmentPrivate::Get(cur->Compartment());
    372    priv->SystemIsBeingShutDown();
    373  }
    374 }
    375 
    376 /***************************************************************************/
    377 
    378 JSObject* XPCWrappedNativeScope::GetExpandoChain(HandleObject target) {
    379  MOZ_ASSERT(ObjectScope(target) == this);
    380  if (!mXrayExpandos.initialized()) {
    381    return nullptr;
    382  }
    383  return mXrayExpandos.lookup(target);
    384 }
    385 
    386 JSObject* XPCWrappedNativeScope::DetachExpandoChain(HandleObject target) {
    387  MOZ_ASSERT(ObjectScope(target) == this);
    388  if (!mXrayExpandos.initialized()) {
    389    return nullptr;
    390  }
    391  return mXrayExpandos.removeValue(target);
    392 }
    393 
    394 bool XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target,
    395                                            HandleObject chain) {
    396  MOZ_ASSERT(ObjectScope(target) == this);
    397  MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
    398  MOZ_ASSERT_IF(chain, ObjectScope(chain) == this);
    399  if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx)) {
    400    return false;
    401  }
    402  return mXrayExpandos.put(cx, target, chain);
    403 }
    404 
    405 /***************************************************************************/
    406 
    407 // static
    408 void XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) {
    409 #ifdef DEBUG
    410  depth--;
    411 
    412  // get scope count.
    413  int count = 0;
    414  for (XPCWrappedNativeScope* cur : AllScopes()) {
    415    (void)cur;
    416    count++;
    417  }
    418 
    419  XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count));
    420  XPC_LOG_INDENT();
    421  if (depth) {
    422    for (XPCWrappedNativeScope* cur : AllScopes()) {
    423      cur->DebugDump(depth);
    424    }
    425  }
    426  XPC_LOG_OUTDENT();
    427 #endif
    428 }
    429 
    430 void XPCWrappedNativeScope::DebugDump(int16_t depth) {
    431 #ifdef DEBUG
    432  depth--;
    433  XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %p", this));
    434  XPC_LOG_INDENT();
    435  XPC_LOG_ALWAYS(("next @ %p", getNext()));
    436  XPC_LOG_ALWAYS(("mComponents @ %p", mComponents.get()));
    437  XPC_LOG_ALWAYS(("mCompartment @ %p", mCompartment));
    438 
    439  XPC_LOG_ALWAYS(("mWrappedNativeMap @ %p with %d wrappers(s)",
    440                  mWrappedNativeMap.get(), mWrappedNativeMap->Count()));
    441  // iterate contexts...
    442  if (depth && mWrappedNativeMap->Count()) {
    443    XPC_LOG_INDENT();
    444    for (auto i = mWrappedNativeMap->Iter(); !i.done(); i.next()) {
    445      i.get().value()->DebugDump(depth);
    446    }
    447    XPC_LOG_OUTDENT();
    448  }
    449 
    450  XPC_LOG_ALWAYS(("mWrappedNativeProtoMap @ %p with %d protos(s)",
    451                  mWrappedNativeProtoMap.get(),
    452                  mWrappedNativeProtoMap->Count()));
    453  // iterate contexts...
    454  if (depth && mWrappedNativeProtoMap->Count()) {
    455    XPC_LOG_INDENT();
    456    for (auto i = mWrappedNativeProtoMap->Iter(); !i.done(); i.next()) {
    457      i.get().value()->DebugDump(depth);
    458    }
    459    XPC_LOG_OUTDENT();
    460  }
    461  XPC_LOG_OUTDENT();
    462 #endif
    463 }
    464 
    465 void XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(
    466    JSContext* cx, ScopeSizeInfo* scopeSizeInfo) {
    467  for (XPCWrappedNativeScope* cur : AllScopes()) {
    468    cur->AddSizeOfIncludingThis(cx, scopeSizeInfo);
    469  }
    470 }
    471 
    472 void XPCWrappedNativeScope::AddSizeOfIncludingThis(
    473    JSContext* cx, ScopeSizeInfo* scopeSizeInfo) {
    474  scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this);
    475  scopeSizeInfo->mScopeAndMapSize +=
    476      mWrappedNativeMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
    477  scopeSizeInfo->mScopeAndMapSize +=
    478      mWrappedNativeProtoMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
    479 
    480  auto realmCb = [](JSContext*, void* aData, JS::Realm* aRealm,
    481                    const JS::AutoRequireNoGC& nogc) {
    482    auto* scopeSizeInfo = static_cast<ScopeSizeInfo*>(aData);
    483    JSObject* global = GetRealmGlobalOrNull(aRealm);
    484    if (global && dom::HasProtoAndIfaceCache(global)) {
    485      dom::ProtoAndIfaceCache* cache = dom::GetProtoAndIfaceCache(global);
    486      scopeSizeInfo->mProtoAndIfaceCacheSize +=
    487          cache->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf);
    488    }
    489  };
    490  IterateRealmsInCompartment(cx, Compartment(), scopeSizeInfo, realmCb);
    491 
    492  // There are other XPCWrappedNativeScope members that could be measured;
    493  // the above ones have been seen by DMD to be worth measuring.  More stuff
    494  // may be added later.
    495 }