Bug 1273251: Part 1 - Mark nuked sandboxes as nuked and non-scriptable. r?bholley
MozReview-Commit-ID: tq9nExa1P7
--- 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;