Bug 1322273: Cut wrappers that point out of a browser compartment when nuking it. r=bholley

MozReview-Commit-ID: FLS3xRgih2u

--HG--
extra : rebase_source : ffae5590afe3efcb70cdda6386f5b1d71123943c
extra : absorb_source : 8b3db1348e793dabca338841dc18b6d9021c6ef8
This commit is contained in:
Kris Maglione 2017-02-23 13:56:10 -08:00
Родитель fca1830f46
Коммит 42bebe8564
8 изменённых файлов: 121 добавлений и 25 удалений

Просмотреть файл

@ -9406,21 +9406,16 @@ public:
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);
// Now mark the compartment as nuked and non-scriptable.
auto compartmentPrivate = xpc::CompartmentPrivate::Get(cpt);
compartmentPrivate->wasNuked = true;
compartmentPrivate->scriptability.Block();
xpc::NukeAllWrappersForCompartment(cx, cpt,
win->IsInnerWindow() ? js::DontNukeWindowReferences
: js::NukeWindowReferences);
} 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);
}
}
}

Просмотреть файл

@ -1130,12 +1130,17 @@ 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.
@ -1178,7 +1183,8 @@ 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. */

Просмотреть файл

@ -513,20 +513,23 @@ 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
@ -538,13 +541,15 @@ js::NukeCrossCompartmentWrappers(JSContext* cx,
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);

Просмотреть файл

@ -3020,14 +3020,8 @@ nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx)
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);
// Now mark the compartment as nuked and non-scriptable.
auto compartmentPrivate = xpc::CompartmentPrivate::Get(sb);
compartmentPrivate->wasNuked = true;
compartmentPrivate->scriptability.Block();
xpc::NukeAllWrappersForCompartment(cx, GetObjectCompartment(sb));
return NS_OK;
}

Просмотреть файл

@ -580,6 +580,37 @@ CurrentWindowOrNull(JSContext* cx)
return glob ? WindowOrNull(glob) : nullptr;
}
// Nukes all wrappers into or out of the given compartment, and prevents new
// wrappers from being created. Additionally marks the compartment as
// unscriptable after wrappers have been nuked.
//
// Note: This should *only* be called for browser or extension compartments.
// Wrappers between web compartments must never be cut in web-observable
// ways.
void
NukeAllWrappersForCompartment(JSContext* cx, JSCompartment* compartment,
js::NukeReferencesToWindow nukeReferencesToWindow)
{
// First, nuke all wrappers into or out of the target compartment. Once
// the compartment is marked as nuked, WrapperFactory will refuse to
// create new live wrappers for it, in either direction. This means that
// we need to be sure that we don't have any existing cross-compartment
// wrappers which may be replaced with dead wrappers during unrelated
// wrapper recomputation *before* we set that bit.
js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(),
js::SingleCompartment(compartment),
nukeReferencesToWindow,
js::NukeAllReferences);
// At this point, we should cross-compartment wrappers for the nuked
// compartment. Set the wasNuked bit so WrapperFactory will return a
// DeadObjectProxy when asked to create a new wrapper for it, and mark as
// unscriptable.
auto compartmentPrivate = xpc::CompartmentPrivate::Get(compartment);
compartmentPrivate->wasNuked = true;
compartmentPrivate->scriptability.Block();
}
} // namespace xpc
static void

Просмотреть файл

@ -400,6 +400,9 @@ bool StringToJsval(JSContext* cx, mozilla::dom::DOMString& str,
nsIPrincipal* GetCompartmentPrincipal(JSCompartment* compartment);
void NukeAllWrappersForCompartment(JSContext* cx, JSCompartment* compartment,
js::NukeReferencesToWindow nukeReferencesToWindow = js::NukeWindowReferences);
void SetLocationForGlobal(JSObject* global, const nsACString& location);
void SetLocationForGlobal(JSObject* global, nsIURI* locationURI);

Просмотреть файл

@ -101,6 +101,7 @@ support-files =
[test_getWebIDLCaller.html]
skip-if = (debug == false || os == "android")
[test_nac.xhtml]
[test_nukeContentWindow.html]
[test_sameOriginPolicy.html]
[test_sandbox_fetch.html]
support-files =

Просмотреть файл

@ -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>