Bug 1702863 - Use a dedicate object for cross-realm weak map key. r=jandem,peterv

Differential Revision: https://phabricator.services.mozilla.com/D110954
This commit is contained in:
Tooru Fujisawa 2021-04-08 15:54:21 +00:00
Родитель ffa4291708
Коммит 55af083348
12 изменённых файлов: 128 добавлений и 5 удалений

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

@ -269,13 +269,26 @@ bool MaybeCrossOriginObjectMixins::EnsureHolder(
// our objects are per-Realm singletons, we are basically using "obj" itself
// as part of the key.
//
// To represent the current settings, we use the current-Realm
// Object.prototype. We can't use the current global, because we can't get a
// useful cross-compartment wrapper for it; such wrappers would always go
// To represent the current settings, we use a dedicated key object of the
// current-Realm.
//
// We can't use the current global, because we can't get a useful
// cross-compartment wrapper for it; such wrappers would always go
// through a WindowProxy and would not be guarantee to keep pointing to a
// single Realm when unwrapped. We want to grab this key before we start
// changing Realms.
JS::Rooted<JSObject*> key(cx, JS::GetRealmObjectPrototype(cx));
//
// Also we can't use arbitrary object (e.g.: Object.prototype), because at
// this point those compartments are not same-origin, and don't have access to
// each other, and the object retrieved here will be wrapped by a security
// wrapper below, and the wrapper will be stored into the cache
// (see Compartment::wrap). Those compartments can get access later by
// modifying `document.domain`, and wrapping objects after that point
// shouldn't result in a security wrapper. Wrap operation looks up the
// existing wrapper in the cache, that contains the security wrapper created
// here. We should use unique/private object here, so that this doesn't
// affect later wrap operation.
JS::Rooted<JSObject*> key(cx, JS::GetRealmWeakMapKey(cx));
if (!key) {
return false;
}

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

@ -702,7 +702,7 @@ static const uint32_t JSCLASS_FOREGROUND_FINALIZE =
// application.
static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 28;
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 29;
static constexpr uint32_t JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(uint32_t n) {
return JSCLASS_IS_GLOBAL |

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

@ -102,6 +102,10 @@ extern JS_PUBLIC_API JSObject* GetRealmErrorPrototype(JSContext* cx);
extern JS_PUBLIC_API JSObject* GetRealmIteratorPrototype(JSContext* cx);
// Returns a key for cross-origin realm weak map.
// See the consumer in `MaybeCrossOriginObjectMixins::EnsureHolder` for details.
extern JS_PUBLIC_API JSObject* GetRealmWeakMapKey(JSContext* cx);
// Implements https://tc39.github.io/ecma262/#sec-getfunctionrealm
// 7.3.22 GetFunctionRealm ( obj )
//

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

@ -963,6 +963,24 @@ NativeObject* GlobalObject::getOrCreateForOfPICObject(
return forOfPIC;
}
/* static */
JSObject* GlobalObject::getOrCreateRealmWeakMapKey(
JSContext* cx, Handle<GlobalObject*> global) {
cx->check(global);
Value v = global->getReservedSlot(REALM_WEAK_MAP_KEY);
if (v.isObject()) {
return &v.toObject();
}
PlainObject* key = NewBuiltinClassInstance<PlainObject>(cx);
if (!key) {
return nullptr;
}
global->setReservedSlot(REALM_WEAK_MAP_KEY, ObjectValue(*key));
return key;
}
/* static */
RegExpStatics* GlobalObject::getRegExpStatics(JSContext* cx,
Handle<GlobalObject*> global) {

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

@ -123,6 +123,7 @@ class GlobalObject : public NativeObject {
GLOBAL_THIS_RESOLVED,
INSTRUMENTATION,
SOURCE_URLS,
REALM_WEAK_MAP_KEY,
/* Total reserved-slot count for global objects. */
RESERVED_SLOTS
@ -894,6 +895,10 @@ class GlobalObject : public NativeObject {
getSlotRef(SOURCE_URLS).unbarrieredSet(UndefinedValue());
}
// Returns a key for a weak map, used by embedder.
static JSObject* getOrCreateRealmWeakMapKey(JSContext* cx,
Handle<GlobalObject*> global);
// A class used in place of a prototype during off-thread parsing.
struct OffThreadPlaceholderObject : public NativeObject {
static const int32_t SlotIndexSlot = 0;

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

@ -786,6 +786,10 @@ JS_PUBLIC_API JSObject* JS::GetRealmIteratorPrototype(JSContext* cx) {
return GlobalObject::getOrCreateIteratorPrototype(cx, cx->global());
}
JS_PUBLIC_API JSObject* JS::GetRealmWeakMapKey(JSContext* cx) {
return GlobalObject::getOrCreateRealmWeakMapKey(cx, cx->global());
}
JS_PUBLIC_API Realm* JS::GetFunctionRealm(JSContext* cx, HandleObject objArg) {
// https://tc39.github.io/ecma262/#sec-getfunctionrealm
// 7.3.22 GetFunctionRealm ( obj )

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

@ -2,6 +2,11 @@
support-files =
browser_consoleStack.html
browser_deadObjectOnUnload.html
browser_realm_weak_map_object_prototype_top.html
browser_realm_weak_map_object_prototype_frame.html
browser_realm_weak_map_promise_top.html
browser_realm_weak_map_promise_frame.html
[browser_dead_object.js]
[browser_exception_leak.js]
[browser_parent_process_hang_telemetry.js]
[browser_realm_weak_map_and_document_domain.js]

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

@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
"use strict";
async function test_document(url) {
await BrowserTestUtils.withNewTab(url, async function (browser) {
let result = await ContentTask.spawn(
browser, {},
async function() {
let result = content.document.getElementById("result");
return result.innerText;
}
);
is(result, "OK", "test succeeds");
});
}
add_task(async function test_explicit_object_prototype() {
await test_document("http://mochi.test:8888/browser/js/xpconnect/tests/browser/browser_realm_weak_map_object_prototype_top.html");
});
add_task(async function test_implicit_object_prototype() {
await test_document("http://mochi.test:8888/browser/js/xpconnect/tests/browser/browser_realm_weak_map_promise_top.html");
});

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

@ -0,0 +1,11 @@
<script type="text/javascript">
// Access to the top-level window property before getting access.
// This will create an entry in cross-origin realm weak map.
try {
window.top.Object;
} catch (e) {}
document.domain = "mochi.test";
window.top.check();
</script>

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

@ -0,0 +1,12 @@
<script type="text/javascript">
document.domain = "mochi.test";
function check() {
// Ensure frame's Object.prototype is accessible.
if (document.getElementById("frame").contentWindow.Object.prototype.toString.call({}) == "[object Object]") {
document.getElementById("result").textContent = "OK";
}
}
</script>
<iframe id="frame" src="http://test2.mochi.test:8888/browser/js/xpconnect/tests/browser/browser_realm_weak_map_object_prototype_frame.html">
</iframe>
<span id="result"></span>

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

@ -0,0 +1,17 @@
<script type="text/javascript">
// Access to the top-level window property before getting access.
// This will create an entry in cross-origin realm weak map.
try {
window.top.P;
} catch (e) {}
document.domain = "mochi.test";
// Ensure that frame's Object.prototype is accessible from top-level frame
// when getting incumbent global object inside Promise handling.
window.top.P.then(v => {
if (v == 10) {
window.top.document.getElementById("result").textContent = "OK";
}
});
</script>

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

@ -0,0 +1,7 @@
<script type="text/javascript">
document.domain = "mochi.test";
window.P = new Promise(r => r(10));
</script>
<iframe src="http://test2.mochi.test:8888/browser/js/xpconnect/tests/browser/browser_realm_weak_map_promise_frame.html">
</iframe>
<span id="result"></span>