Bug 1645510: Part 1 - Make unprivileged junk scope creation lazy, weak, and fallible. r=mccr8,bholley
authorKris Maglione <maglione.k@gmail.com>
Sat, 27 Jun 2020 03:06:26 +0000
changeset 537674 821960c5fa97cbe6fabaa26182c35d1e76a3ae4e
parent 537673 56434e256a52955813614e439b493d551a98a9ee
child 537675 93bca0225002f5dae30b60b823cb73684031a348
push id37545
push usernerli@mozilla.com
push dateSat, 27 Jun 2020 09:38:32 +0000
treeherdermozilla-central@0a4b3f99d2d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8, bholley
bugs1645510
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1645510: Part 1 - Make unprivileged junk scope creation lazy, weak, and fallible. r=mccr8,bholley Differential Revision: https://phabricator.services.mozilla.com/D79719
dom/base/nsINode.cpp
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
js/xpconnect/src/Sandbox.cpp
js/xpconnect/src/SandboxPrivate.h
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -2968,17 +2968,17 @@ JSObject* nsINode::WrapObject(JSContext*
       !hasHadScriptHandlingObject && !nsContentUtils::IsSystemCaller(aCx)) {
     Throw(aCx, NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   JS::Rooted<JSObject*> obj(aCx, WrapNode(aCx, aGivenProto));
   if (obj && ChromeOnlyAccess()) {
     MOZ_RELEASE_ASSERT(
-        JS::GetNonCCWObjectGlobal(obj) == xpc::UnprivilegedJunkScope() ||
+        xpc::IsUnprivilegedJunkScope(JS::GetNonCCWObjectGlobal(obj)) ||
         xpc::IsInUAWidgetScope(obj) || xpc::AccessCheck::isChrome(obj));
   }
   return obj;
 }
 
 already_AddRefed<nsINode> nsINode::CloneNode(bool aDeep, ErrorResult& aError) {
   return Clone(aDeep, nullptr, aError);
 }
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -4179,23 +4179,29 @@ void DeprecationWarning(JSContext* aCx, 
 
 void DeprecationWarning(const GlobalObject& aGlobal,
                         Document::DeprecatedOperations aOperation) {
   MaybeShowDeprecationWarning(aGlobal, aOperation);
   MaybeReportDeprecation(aGlobal, aOperation);
 }
 
 namespace binding_detail {
-JSObject* UnprivilegedJunkScopeOrWorkerGlobal() {
+JSObject* UnprivilegedJunkScopeOrWorkerGlobal(const fallible_t&) {
   if (NS_IsMainThread()) {
-    return xpc::UnprivilegedJunkScope();
+    return xpc::UnprivilegedJunkScope(fallible);
   }
 
   return GetCurrentThreadWorkerGlobal();
 }
+
+JSObject* UnprivilegedJunkScopeOrWorkerGlobal() {
+  JSObject* scope = UnprivilegedJunkScopeOrWorkerGlobal(fallible);
+  MOZ_RELEASE_ASSERT(scope);
+  return scope;
+}
 }  // namespace binding_detail
 
 JS::Handle<JSObject*> GetPerInterfaceObjectHandle(
     JSContext* aCx, size_t aSlotId, CreateInterfaceObjectsMethod aCreator,
     bool aDefineOnGlobal) {
   /* Make sure our global is sane.  Hopefully we can remove this sometime */
   JSObject* global = JS::CurrentGlobalOrNull(aCx);
   if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -3160,16 +3160,17 @@ namespace binding_detail {
 // environment controlled by a hostile adversary.  This is because in the worker
 // case the global is in fact the worker global, so it and its standard objects
 // are controlled by the worker script.  This is why this function is in the
 // binding_detail namespace.  Any use of this function MUST be very carefully
 // reviewed by someone who is sufficiently devious and has a very good
 // understanding of all the code that will run while we're using the return
 // value, including the SpiderMonkey parts.
 JSObject* UnprivilegedJunkScopeOrWorkerGlobal();
+JSObject* UnprivilegedJunkScopeOrWorkerGlobal(const fallible_t&);
 
 // Implementation of the [HTMLConstructor] extended attribute.
 bool HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
                      constructors::id::ID aConstructorId,
                      prototypes::id::ID aProtoId,
                      CreateInterfaceObjectsMethod aCreator);
 
 // A method to test whether an attribute with the given JSJitGetterOp getter is
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -88,16 +88,17 @@ using namespace xpc;
 using mozilla::dom::DestroyProtoAndIfaceCache;
 using mozilla::dom::IndexedDatabaseManager;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(SandboxPrivate)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SandboxPrivate)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
   tmp->UnlinkObjectsInGlobal();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SandboxPrivate)
   tmp->TraverseObjectsInGlobal(cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(SandboxPrivate)
@@ -1346,17 +1347,17 @@ nsresult xpc::CreateSandboxObject(JSCont
       return NS_ERROR_XPC_UNEXPECTED;
 
     if (!options.globalProperties.DefineInSandbox(cx, sandbox)) {
       return NS_ERROR_XPC_UNEXPECTED;
     }
   }
 
   // We handle the case where the context isn't in a compartment for the
-  // benefit of InitSingletonScopes.
+  // benefit of UnprivilegedJunkScope().
   vp.setObject(*sandbox);
   if (js::GetContextCompartment(cx) && !JS_WrapValue(cx, vp)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   // Set the location information for the new global, so that tools like
   // about:memory may use that information
   xpc::SetLocationForGlobal(sandbox, options.sandboxName);
--- a/js/xpconnect/src/SandboxPrivate.h
+++ b/js/xpconnect/src/SandboxPrivate.h
@@ -2,29 +2,32 @@
 /* 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/. */
 
 #ifndef __SANDBOXPRIVATE_H__
 #define __SANDBOXPRIVATE_H__
 
+#include "mozilla/WeakPtr.h"
 #include "nsIGlobalObject.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIPrincipal.h"
 #include "nsWeakReference.h"
 #include "nsWrapperCache.h"
 
 #include "js/RootingAPI.h"
 
 class SandboxPrivate : public nsIGlobalObject,
                        public nsIScriptObjectPrincipal,
                        public nsSupportsWeakReference,
+                       public mozilla::SupportsWeakPtr<SandboxPrivate>,
                        public nsWrapperCache {
  public:
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(SandboxPrivate);
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(SandboxPrivate,
                                                          nsIGlobalObject)
 
   static void Create(nsIPrincipal* principal, JS::Handle<JSObject*> global) {
     RefPtr<SandboxPrivate> sbp = new SandboxPrivate(principal);
     sbp->SetWrapper(global);
     sbp->PreserveWrapper(ToSupports(sbp.get()));
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -561,16 +561,24 @@ void SetCompartmentChangedDocumentDomain
     priv->originInfo.SetChangedDocumentDomain();
   }
 }
 
 JSObject* UnprivilegedJunkScope() {
   return XPCJSRuntime::Get()->UnprivilegedJunkScope();
 }
 
+JSObject* UnprivilegedJunkScope(const fallible_t&) {
+  return XPCJSRuntime::Get()->UnprivilegedJunkScope(fallible);
+}
+
+bool IsUnprivilegedJunkScope(JSObject* obj) {
+  return XPCJSRuntime::Get()->IsUnprivilegedJunkScope(obj);
+}
+
 JSObject* NACScope(JSObject* global) {
   // If we're a chrome global, just use ourselves.
   if (AccessCheck::isChrome(global)) {
     return global;
   }
 
   JSObject* scope = UnprivilegedJunkScope();
   JS::ExposeObjectToActiveJS(scope);
@@ -3009,17 +3017,16 @@ HelperThreadPoolShutdownObserver::Observ
   // Shut down the helper threads
   gHelperThreads->Shutdown();
   gHelperThreads = nullptr;
 
   return NS_OK;
 }
 
 void XPCJSRuntime::Initialize(JSContext* cx) {
-  mUnprivilegedJunkScope.init(cx, nullptr);
   mLoaderGlobal.init(cx, nullptr);
 
   // these jsids filled in later when we have a JSContext to work with.
   mStrIDs[0] = JSID_VOID;
 
   nsScriptSecurityManager::GetScriptSecurityManager()->InitJSCallbacks(cx);
 
   // Unconstrain the runtime's threshold on nominal heap size, to avoid
@@ -3286,40 +3293,56 @@ JSObject* XPCJSRuntime::GetUAWidgetScope
     MOZ_ASSERT(xpc::IsInUAWidgetScope(unwrapped));
 
     MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, unwrapped));
   } while (false);
 
   return scope;
 }
 
-void XPCJSRuntime::InitSingletonScopes() {
-  // This all happens very early, so we don't bother with cx pushing.
-  JSContext* cx = XPCJSContext::Get()->Context();
-  RootedValue v(cx);
-  nsresult rv;
-
-  // Create the Unprivileged Junk Scope.
-  SandboxOptions unprivilegedJunkScopeOptions;
-  unprivilegedJunkScopeOptions.sandboxName.AssignLiteral(
-      "XPConnect Junk Compartment");
-  unprivilegedJunkScopeOptions.invisibleToDebugger = true;
-  rv = CreateSandboxObject(cx, &v, nullptr, unprivilegedJunkScopeOptions);
-  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
-  mUnprivilegedJunkScope = js::UncheckedUnwrap(&v.toObject());
+JSObject* XPCJSRuntime::UnprivilegedJunkScope(const mozilla::fallible_t&) {
+  if (!mUnprivilegedJunkScope) {
+    dom::AutoJSAPI jsapi;
+    jsapi.Init();
+    JSContext* cx = jsapi.cx();
+
+    SandboxOptions options;
+    options.sandboxName.AssignLiteral("XPConnect Junk Compartment");
+    options.invisibleToDebugger = true;
+
+    RootedValue sandbox(cx);
+    nsresult rv = CreateSandboxObject(cx, &sandbox, nullptr, options);
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    mUnprivilegedJunkScope =
+        SandboxPrivate::GetPrivate(sandbox.toObjectOrNull());
+  }
+  MOZ_ASSERT(mUnprivilegedJunkScope->GetWrapper(),
+             "Wrapper should have same lifetime as weak reference");
+  return mUnprivilegedJunkScope->GetWrapper();
+}
+
+JSObject* XPCJSRuntime::UnprivilegedJunkScope() {
+  JSObject* scope = UnprivilegedJunkScope(fallible);
+  MOZ_RELEASE_ASSERT(scope);
+  return scope;
+}
+
+bool XPCJSRuntime::IsUnprivilegedJunkScope(JSObject* obj) {
+  return mUnprivilegedJunkScope && obj == mUnprivilegedJunkScope->GetWrapper();
 }
 
 void XPCJSRuntime::DeleteSingletonScopes() {
   // We're pretty late in shutdown, so we call ReleaseWrapper on the scopes.
   // This way the GC can collect them immediately, and we don't rely on the CC
   // to clean up.
-  RefPtr<SandboxPrivate> sandbox =
-      SandboxPrivate::GetPrivate(mUnprivilegedJunkScope);
-  sandbox->ReleaseWrapper(sandbox);
-  mUnprivilegedJunkScope = nullptr;
+  if (RefPtr<SandboxPrivate> sandbox = mUnprivilegedJunkScope.get()) {
+    sandbox->ReleaseWrapper(sandbox);
+    mUnprivilegedJunkScope = nullptr;
+  }
   mLoaderGlobal = nullptr;
 }
 
 JSObject* XPCJSRuntime::LoaderGlobal() {
   if (!mLoaderGlobal) {
     RefPtr<mozJSComponentLoader> loader = mozJSComponentLoader::Get();
 
     dom::AutoJSAPI jsapi;
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -79,19 +79,16 @@ void nsXPConnect::InitJSContext() {
 
   XPCJSContext* xpccx = XPCJSContext::NewXPCJSContext();
   if (!xpccx) {
     MOZ_CRASH("Couldn't create XPCJSContext.");
   }
   gSelf->mContext = xpccx;
   gSelf->mRuntime = xpccx->Runtime();
 
-  // Initialize our singleton scopes.
-  gSelf->mRuntime->InitSingletonScopes();
-
   mozJSComponentLoader::InitStatics();
 
   // Initialize the script preloader cache.
   Unused << mozilla::ScriptPreloader::GetSingleton();
 
   nsJSContext::EnsureStatics();
 }
 
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -562,20 +562,37 @@ class XPCJSRuntime final : public mozill
 
   void AddGCCallback(xpcGCCallback cb);
   void RemoveGCCallback(xpcGCCallback cb);
 
   JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal);
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
-  JSObject* UnprivilegedJunkScope() { return mUnprivilegedJunkScope; }
+  /**
+   * The unprivileged junk scope is an unprivileged sandbox global used for
+   * convenience by certain operations which need an unprivileged global but
+   * don't have one immediately handy. It should generally be avoided when
+   * possible.
+   *
+   * The scope is created lazily when it is needed, and held weakly so that it
+   * is destroyed when there are no longer any remaining external references to
+   * it. This means that under low memory conditions, when the scope does not
+   * already exist, we may not be able to create one. In these circumstances,
+   * the infallible version of this API will abort, and the fallible version
+   * will return null. Callers should therefore prefer the fallible version when
+   * on a codepath which can already return failure, but may use the infallible
+   * one otherwise.
+   */
+  JSObject* UnprivilegedJunkScope();
+  JSObject* UnprivilegedJunkScope(const mozilla::fallible_t&);
+
+  bool IsUnprivilegedJunkScope(JSObject*);
   JSObject* LoaderGlobal();
 
-  void InitSingletonScopes();
   void DeleteSingletonScopes();
 
   void SystemIsBeingShutDown();
 
  private:
   explicit XPCJSRuntime(JSContext* aCx);
 
   MOZ_IS_CLASS_INIT
@@ -616,17 +633,17 @@ class XPCJSRuntime final : public mozill
   bool mGCIsRunning;
   nsTArray<nsISupports*> mNativesToReleaseArray;
   bool mDoingFinalization;
   XPCRootSetElem* mVariantRoots;
   XPCRootSetElem* mWrappedJSRoots;
   nsTArray<xpcGCCallback> extraGCCallbacks;
   JS::GCSliceCallback mPrevGCSliceCallback;
   JS::DoCycleCollectionCallback mPrevDoCycleCollectionCallback;
-  JS::PersistentRootedObject mUnprivilegedJunkScope;
+  mozilla::WeakPtr<SandboxPrivate> mUnprivilegedJunkScope;
   JS::PersistentRootedObject mLoaderGlobal;
   RefPtr<AsyncFreeSnowWhite> mAsyncSnowWhiteFreer;
 
   friend class XPCJSContext;
   friend class XPCIncrementalReleaseRunnable;
 };
 
 inline JS::HandleId XPCJSContext::GetStringID(unsigned index) const {
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -479,16 +479,20 @@ already_AddRefed<nsISupports> ReflectorT
  * Singleton scopes for stuff that really doesn't fit anywhere else.
  *
  * If you find yourself wanting to use these compartments, you're probably doing
  * something wrong. Callers MUST consult with the XPConnect module owner before
  * using this compartment. If you don't, bholley will hunt you down.
  */
 JSObject* UnprivilegedJunkScope();
 
+JSObject* UnprivilegedJunkScope(const mozilla::fallible_t&);
+
+bool IsUnprivilegedJunkScope(JSObject*);
+
 /**
  * This will generally be the shared JSM global, but callers should not depend
  * on that fact.
  */
 JSObject* PrivilegedJunkScope();
 
 /**
  * Shared compilation scope for XUL prototype documents and XBL