Bug 1322273: Cut wrappers that point out of a browser compartment when nuking it. r?bholley
MozReview-Commit-ID: FLS3xRgih2u
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -9408,28 +9408,30 @@ public:
nsCOMPtr<nsIPrincipal> pc = nsJSPrincipals::get(JS_GetCompartmentPrincipals(cpt));
nsAutoString addonId;
if (NS_SUCCEEDED(pc->GetAddonId(addonId)) && !addonId.IsEmpty()) {
// We want to nuke all references to the add-on compartment.
js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(),
js::SingleCompartment(cpt),
win->IsInnerWindow() ? js::DontNukeWindowReferences
- : js::NukeWindowReferences);
+ : js::NukeWindowReferences,
+ js::NukeAllReferences);
// Now mark the compartment as nuked and non-scriptable.
auto compartmentPrivate = xpc::CompartmentPrivate::Get(cpt);
compartmentPrivate->wasNuked = true;
compartmentPrivate->scriptability.Block();
} else {
// We only want to nuke wrappers for the chrome->content case
js::NukeCrossCompartmentWrappers(cx, BrowserCompartmentMatcher(),
js::SingleCompartment(cpt),
win->IsInnerWindow() ? js::DontNukeWindowReferences
- : js::NukeWindowReferences);
+ : js::NukeWindowReferences,
+ js::NukeIncomingReferences);
}
}
}
return NS_OK;
}
private:
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1125,22 +1125,27 @@ GetErrorTypeName(JSContext* cx, int16_t
extern JS_FRIEND_API(unsigned)
GetEnterCompartmentDepth(JSContext* cx);
#endif
class RegExpGuard;
extern JS_FRIEND_API(bool)
RegExpToSharedNonInline(JSContext* cx, JS::HandleObject regexp, RegExpGuard* shared);
-/* Implemented in jswrapper.cpp. */
+/* Implemented in CrossCompartmentWrapper.cpp. */
typedef enum NukeReferencesToWindow {
NukeWindowReferences,
DontNukeWindowReferences
} NukeReferencesToWindow;
+typedef enum NukeReferencesFromTarget {
+ NukeAllReferences,
+ NukeIncomingReferences,
+} NukeReferencesFromTarget;
+
/*
* These filters are designed to be ephemeral stack classes, and thus don't
* do any rooting or holding of their members.
*/
struct CompartmentFilter {
virtual bool match(JSCompartment* c) const = 0;
};
@@ -1173,17 +1178,18 @@ struct CompartmentsWithPrincipals : publ
return JS_GetCompartmentPrincipals(c) == principals;
}
};
extern JS_FRIEND_API(bool)
NukeCrossCompartmentWrappers(JSContext* cx,
const CompartmentFilter& sourceFilter,
const CompartmentFilter& targetFilter,
- NukeReferencesToWindow nukeReferencesToWindow);
+ NukeReferencesToWindow nukeReferencesToWindow,
+ NukeReferencesFromTarget nukeReferencesFromTarget);
/* Specify information about DOMProxy proxies in the DOM, for use by ICs. */
/*
* The DOMProxyShadowsCheck function will be called to check if the property for
* id should be gotten from the prototype, or if there is an own property that
* shadows it.
* * If ShadowsViaDirectExpando is returned, then the slot at
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -508,48 +508,53 @@ js::NukeCrossCompartmentWrapper(JSContex
* point to the window object on page navigation (inner window destruction)
* and only do that on tab close (outer window destruction). Thus the
* option of how to handle the global object.
*/
JS_FRIEND_API(bool)
js::NukeCrossCompartmentWrappers(JSContext* cx,
const CompartmentFilter& sourceFilter,
const CompartmentFilter& targetFilter,
- js::NukeReferencesToWindow nukeReferencesToWindow)
+ js::NukeReferencesToWindow nukeReferencesToWindow,
+ js::NukeReferencesFromTarget nukeReferencesFromTarget)
{
CHECK_REQUEST(cx);
JSRuntime* rt = cx->runtime();
EvictAllNurseries(rt);
- // Iterate through scopes looking for system cross compartment wrappers
- // that point to an object that shares a global with obj.
-
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
if (!sourceFilter.match(c))
continue;
+ // If the compartment matches both the source and target filter, we may
+ // want to cut both incoming and outgoing wrappers.
+ bool nukeAll = (nukeReferencesFromTarget == NukeAllReferences &&
+ targetFilter.match(c));
+
// Iterate the wrappers looking for anything interesting.
for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
// Some cross-compartment wrappers are for strings. We're not
// interested in those.
const CrossCompartmentKey& k = e.front().key();
if (!k.is<JSObject*>())
continue;
AutoWrapperRooter wobj(cx, WrapperValue(e));
JSObject* wrapped = UncheckedUnwrap(wobj);
+ // We only skip nuking window references that point to a target
+ // compartment, not the ones that belong to it.
if (nukeReferencesToWindow == DontNukeWindowReferences &&
- IsWindowProxy(wrapped))
+ MOZ_LIKELY(!nukeAll) && IsWindowProxy(wrapped))
{
continue;
}
- if (targetFilter.match(wrapped->compartment())) {
+ if (MOZ_UNLIKELY(nukeAll) || targetFilter.match(wrapped->compartment())) {
// We found a wrapper to nuke.
e.removeFront();
NukeCrossCompartmentWrapper(cx, wobj);
}
}
}
return true;
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3017,17 +3017,18 @@ nsXPCComponents_Utils::NukeSandbox(Handl
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);
RootedObject sb(cx, UncheckedUnwrap(wrapper));
NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG);
NukeCrossCompartmentWrappers(cx, AllCompartments(),
SingleCompartment(GetObjectCompartment(sb)),
- NukeWindowReferences);
+ NukeWindowReferences,
+ NukeAllReferences);
// Now mark the compartment as nuked and non-scriptable.
auto compartmentPrivate = xpc::CompartmentPrivate::Get(sb);
compartmentPrivate->wasNuked = true;
compartmentPrivate->scriptability.Block();
return NS_OK;
}
--- a/js/xpconnect/tests/mochitest/mochitest.ini
+++ b/js/xpconnect/tests/mochitest/mochitest.ini
@@ -96,12 +96,13 @@ support-files =
[test_bug1158558.html]
[test_crossOriginObjects.html]
[test_crosscompartment_weakmap.html]
[test_frameWrapping.html]
# The JS test component we use below is only available in debug builds.
[test_getWebIDLCaller.html]
skip-if = (debug == false || os == "android")
[test_nac.xhtml]
+[test_nukeContentWindow.html]
[test_sameOriginPolicy.html]
[test_sandbox_fetch.html]
support-files =
../../../../dom/tests/mochitest/fetch/test_fetch_basic.js
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/mochitest/test_nukeContentWindow.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1322273
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1322273</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322273">Mozilla Bug 1322273</a>
+
+<iframe id="subframe"></iframe>
+
+<script type="application/javascript">
+"use strict";
+
+add_task(function* () {
+ let frame = $('subframe');
+ frame.src = "data:text/html,";
+ yield new Promise(resolve => frame.addEventListener("load", resolve, {once: true}));
+
+ let win = frame.contentWindow;
+
+ win.eval("obj = {}");
+ win.obj.foo = {bar: "baz"};
+
+ let obj = win.obj;
+
+ let system = SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal()
+ let sandbox = SpecialPowers.Cu.Sandbox(system);
+
+ sandbox.obj = obj;
+
+ let isWrapperDead = SpecialPowers.Cu.evalInSandbox(`(${
+ function isWrapperDead() {
+ return Components.utils.isDeadWrapper(obj);
+ }
+ })`,
+ sandbox);
+
+ is(isWrapperDead(), false, "Sandbox wrapper for content window should not be dead");
+ is(obj.foo.bar, "baz", "Content wrappers into and out of content window should be alive");
+
+ // Remove the frame, which should nuke the content window.
+ info("Remove the content frame");
+ frame.remove();
+
+ // Give the nuke wrappers task a chance to run.
+ yield new Promise(SimpleTest.executeSoon);
+
+ is(isWrapperDead(), true, "Sandbox wrapper for content window should be dead");
+ is(obj.foo.bar, "baz", "Content wrappers into and out of content window should be alive");
+});
+</script>
+
+</body>
+</html>