diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 3830e7efff0c..9b162a631d2f 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -3017,11 +3017,17 @@ nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx) 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; } diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp index b543ba551e8e..2ea69ce93e00 100644 --- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -199,6 +199,7 @@ CompartmentPrivate::CompartmentPrivate(JSCompartment* c) , allowCPOWs(false) , universalXPConnectEnabled(false) , forcePermissiveCOWs(false) + , wasNuked(false) , scriptability(c) , scope(nullptr) , mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)) diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index ddc075ff184d..4daf3598ef11 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -3183,6 +3183,10 @@ public: // 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]; diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index 6e10dcb1dbe2..7380faaa0462 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -324,7 +324,10 @@ 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) { @@ -443,9 +446,21 @@ WrapperFactory::Rewrap(JSContext* cx, HandleObject existing, HandleObject obj) // First, handle the special cases. // + // If we've somehow gotten to this point after either the source or target + // compartment has been nuked, return an opaque wrapper to prevent further + // access. + // Ideally, we should return a DeadProxyObject instead of a wrapper in this + // case (bug 1322273). + 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::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; }