Bug 1273251: Part 1 - Mark nuked sandboxes as nuked and non-scriptable. r?bholley draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 14 Nov 2016 18:20:21 -0800
changeset 440647 2de38a7d10d48e8afb28bb929f4557197db782a5
parent 438165 71825cbd0e2549d813d8279dcdb19fb357f8ab3f
child 440648 ccf57291e2b56650eff94d44f6bd09ff267232b5
push id36285
push usermaglione.k@gmail.com
push dateThu, 17 Nov 2016 23:17:43 +0000
reviewersbholley
bugs1273251
milestone52.0a1
Bug 1273251: Part 1 - Mark nuked sandboxes as nuked and non-scriptable. r?bholley MozReview-Commit-ID: tq9nExa1P7
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSContext.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/wrappers/WrapperFactory.cpp
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3015,21 +3015,27 @@ nsXPCComponents_Utils::SetGCZeal(int32_t
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx)
 {
     PROFILER_LABEL_FUNC(js::ProfileEntry::Category::JS);
     NS_ENSURE_TRUE(obj.isObject(), NS_ERROR_INVALID_ARG);
     JSObject* wrapper = &obj.toObject();
     NS_ENSURE_TRUE(IsWrapper(wrapper), NS_ERROR_INVALID_ARG);
-    JSObject* sb = UncheckedUnwrap(wrapper);
+    RootedObject sb(cx, UncheckedUnwrap(wrapper));
     NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG);
     NukeCrossCompartmentWrappers(cx, AllCompartments(),
                                  SingleCompartment(GetObjectCompartment(sb)),
                                  NukeWindowReferences);
+
+    // Now mark the compartment as nuked and non-scriptable.
+    auto compartmentPrivate = xpc::CompartmentPrivate::Get(sb);
+    compartmentPrivate->wasNuked = true;
+    compartmentPrivate->scriptability.Block();
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::BlockScriptForGlobal(HandleValue globalArg,
                                             JSContext* cx)
 {
     NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG);
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -194,16 +194,17 @@ CompartmentPrivate::CompartmentPrivate(J
     , allowWaivers(true)
     , writeToGlobalPrototype(false)
     , skipWriteToGlobalPrototype(false)
     , isWebExtensionContentScript(false)
     , waiveInterposition(false)
     , allowCPOWs(false)
     , universalXPConnectEnabled(false)
     , forcePermissiveCOWs(false)
+    , wasNuked(false)
     , scriptability(c)
     , scope(nullptr)
     , mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH))
 {
     MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
     mozilla::PodArrayZero(wrapperDenialWarnings);
 }
 
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -3356,16 +3356,20 @@ public:
     // This is only ever set during mochitest runs when enablePrivilege is called.
     // It allows the SpecialPowers scope to waive the normal chrome security
     // wrappers and expose properties directly to content. This lets us avoid a
     // bunch of overhead and complexity in our SpecialPowers automation glue.
     //
     // Using it in production is inherently unsafe.
     bool forcePermissiveCOWs;
 
+    // True if this compartment has been nuked. If true, any wrappers into or
+    // out of it should be considered invalid.
+    bool wasNuked;
+
     // Whether we've emitted a warning about a property that was filtered out
     // by a security wrapper. See XrayWrapper.cpp.
     bool wrapperDenialWarnings[WrapperDenialTypeCount];
 
     // The scriptability of this compartment.
     Scriptability scriptability;
 
     // Our XPCWrappedNativeScope. This is non-null if and only if this is an
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -319,17 +319,20 @@ WrapperFactory::PrepareForWrapping(JSCon
     retObj.set(waive ? WaiveXray(cx, obj) : obj);
 }
 
 #ifdef DEBUG
 static void
 DEBUG_CheckUnwrapSafety(HandleObject obj, const js::Wrapper* handler,
                         JSCompartment* origin, JSCompartment* target)
 {
-    if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) {
+    if (CompartmentPrivate::Get(origin)->wasNuked || CompartmentPrivate::Get(target)->wasNuked) {
+        // If either compartment has already been nuked, we should have an opaque wrapper.
+        MOZ_ASSERT(handler->hasSecurityPolicy());
+    } else if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) {
         // If the caller is chrome (or effectively so), unwrap should always be allowed.
         MOZ_ASSERT(!handler->hasSecurityPolicy());
     } else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) {
         // Similarly, if this is a privileged scope that has opted to make itself
         // accessible to the world (allowed only during automation), unwrap should
         // be allowed.
         MOZ_ASSERT(!handler->hasSecurityPolicy());
     } else {
@@ -438,19 +441,28 @@ WrapperFactory::Rewrap(JSContext* cx, Ha
     XrayType xrayType = GetXrayType(obj);
 
     const Wrapper* wrapper;
 
     //
     // First, handle the special cases.
     //
 
+    // If we've somehow gotten to this point after either the source or target
+    // compartment has been nuked, then nuke the newly-created wrapper.
+    if (CompartmentPrivate::Get(origin)->wasNuked ||
+        CompartmentPrivate::Get(target)->wasNuked) {
+        NS_WARNING("Trying to create a wrapper into or out of a nuked compartment");
+
+        wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
+    }
+
     // If UniversalXPConnect is enabled, this is just some dumb mochitest. Use
     // a vanilla CCW.
-    if (xpc::IsUniversalXPConnectEnabled(target)) {
+    else if (xpc::IsUniversalXPConnectEnabled(target)) {
         CrashIfNotInAutomation();
         wrapper = &CrossCompartmentWrapper::singleton;
     }
 
     // Let the SpecialPowers scope make its stuff easily accessible to content.
     else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) {
         CrashIfNotInAutomation();
         wrapper = &CrossCompartmentWrapper::singleton;