Same-compartment realms are bug 1357862.
Each heading here is a separate bug to be filed, eventually.
https://bugzilla.mozilla.org/show_bug.cgi?id=1363200
The first step in supporting same-compartment realms.
Without any change in behavior, we'll make the following superficial JSAPI changes:
-
Define JS::Realm as a public typedef of JSCompartment
-
Move several APIs from jsapi.h to public/Realm.h and rename them
-
Rename JSAutoCompartment -> JS::AutoRealm. (Look for cases where the object passed to the ctor might be a CCW, since we want to ban that later.)
-
Finally, change JS::Realm so that it is not the same type as JSCompartment, to ensure that API users outside js/src do not conflate the two. (JS::Realm can be an opaque struct type, for now, and inside the JSAPI we can just cast it to JSCompartment immediately on entry.)
https://bugzilla.mozilla.org/show_bug.cgi?id=1363206
With same-compartment realms, all non-ccw objects need a link to their realm/global (for document.domain, but mainly for other reasons unrelated to security, like when a random object is passed the JS::AutoRealm constructor).
https://bugzilla.mozilla.org/show_bug.cgi?id=1363211
It needs to be a Proxy because we need to implement security checks in the object's internal methods (because of the interaction between same-compartment realms and document.domain). See next bug.
https://bugzilla.mozilla.org/show_bug.cgi?id=1363208
With same-compartment realms, we need a new way to enforce security checks during cross-realm access.
This is a matter of following the spec:
<bz> jorendorff: well, we have a spec. <bz> jorendorff: lemme pull it up <bz> jorendorff: https://html.spec.whatwg.org/multipage/browsers.html#location-getprototypeof and following <bz> jorendorff: some of those internal methods just are, but some do sPlatformObjectSameOrigin( <bz> jorendorff: We'd just do that <bz> jorendorff: So in [[GetPrototypeOf]], [[GetOwnProperty]], [[DefineOwnProperty]], [[Get]], [[Set]], [[Delete]], [[OwnPropertyKeys]] <bz> jorendorff: (in terms of our code, in the corresponding proxy handler bits <bz> jorendorff: The windowproxy bits are at https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof and following <bz> jorendorff: but it's the same list
https://bugzilla.mozilla.org/show_bug.cgi?id=1363212
Superficial change, part of same-compartment realms.
Separate from other bugs because it touches everything in the world.
Not totally trivial, because there are several other things to rename at the same time, like JSContext::compartment_; yet CompartmentChecker isn't affected.
https://bugzilla.mozilla.org/show_bug.cgi?id=1363214
To support same-compartment realms, we factor out a Compartment class so that Compartment and Realm are no longer identical inside js/src. However, initially there will be no way to create multiple Realms in the same Compartment, so they'll still be one-to-one.
Each Realm has a pointer to its freshly factored-out Compartment. All Wrapper stuff moves from JS::Realm to the new JS::Compartment.
Initially, JS::Compartment also contains a pointer to its sole Realm. (It will be eliminated later.)
To make way for JS::Compartment, js::wasm::Compartment will have to be renamed to js::wasm::WasmCompartment.
Behavior changes caused by this bug should be limited to fairly obscure
cases, all having to do with "the current global". Before this bug,
that's based on cx->compartment_, and it's updated when crossing
compartment boundaries. In this bug we switch over to cx->realm_,
which is updated in exactly those places required by the ECMAScript
spec.
We need to update cx->realm_ when entering and leaving scripts:
- calling and executing
- "inline" interpreter function calls and returns
- unwinding for an exception
- jit calls: initially broken. So actually using same-compartment realms will have to preffed off until this is fixed (see the next bug).
Otherwise, I don't think we have to change cx->realm_ at JSAPI boundaries.
With this change, AutoRealm updates the current realm, not just the current compartment.
Jan agreed to take this.
- when calling an unknown function jit->jit, update cx->realm_
- also at inlined jit calls
- adjust optimization where we innerize the windowproxy (per bz)
Use cx->realm_->compartment_ instead. Separate bug because this is a
subtle change in behavior, in corner cases only, and I have no real
expectation as to what it might break.
Not sure what this will entail until I get here.
Actually support N:1 relationship between realms and compartments.
We can test the new API this in the shell before using it in the browser. This will likely involve some work on the Debugger and places where we currently iterate over compartments. Not a big deal though.
Intended design: Make this an option in CompartmentOptions: "don't
create a new compartment; instead bundle with X existing compartment".
Alternative design: Keep a HashMap<JSPrincipals*, Compartment*> in
each Zone. When creating a global, check for an existing compartment
with the same principals. Always bundle same-principals globals into the
same compartments whenever possible.
I like the former because it's one less HashMap and it seems like it'll work out about the same in practice.
Behind a pref, off by default. (Content is not included yet because as long as all content remains CPG, we don't have any problems with document.domain.)
This eliminates CCWs between JSMs, for example.
Sandboxes will still get their own compartment. Add-ons, TBD.
Requires JIT work to be done.
Change Gecko to put multiple same-tab-group, same-origin content globals in the same compartment.
This has to be preffed off by default because initially document.domain won't work.
A content-to-content CCW for an object across origins is opaque by default. We need a waiver for these, to reimplement document.domain in the new order.
Since we have existing bugs (XXX cite bug here) that allow exactly the kind of object-injected-across-domain hole bholley was worried about, we are going with bholley's waiver approach.
Depends on all of the above.
Behavior of accesses to WindowProxy and Location objects will depend on the current value of document.domain for the current realm and target.
Here "current realm" means the realm of the code trying to access the object -- "the current Realm Record" in standardese, cx->realm_ in our implementation. "Target" means the realm of the object being accessed; for Location objects that is obvious enough since there's one per global, and for WindowProxy objects it means the immutable Realm field of whatever Window we're currently navigated to. (check with bz)
-
When you're accessing your own WindowProxy/Location, same-realm, of course you just get normal direct access.
-
If you're accessing a WindowProxy/Location of another realm in the same compartment, there's a document.domain check. There's no wrapper; this check is performed by the object itself (it's implemented as a proxy).
-
If you're accessing a WindowProxy/Location of another compartment, you have a cross-compartment wrapper. Then what?
I think what should happen here is: (check with bz, all of this)
-
The CCW should have a no-op security policy. That is, the wrapper doesn't need to do security checks because the referent is a WindowProxy/Location object which does security checks on every access.
-
But the CCW must not enter the referent's compartment before calling the referent's ProxyHandler methods. I gather this is something CrossOriginXrayWrappers already do.
-
WindowProxy/Location's custom ProxyHandler methods need to check the current realm and must return special document.domain waiver objects if granting cross-compartment access.
-
These objects (and other wrappers, maybe) also implement some spec-mandated function-caching magic -- check with bz whether this will need to change.
-
What about other objects?
-
Realms that are same-compartment are same-origin (ignoring document.domain) and no wrappers protect them from one another. Unmediated access.
- If via document.domain two same-origin realms become different-"origin-domain", they retain unmediated access (except for WindowProxy and Location, which do C++ security checks that can now fail).
-
Cross-compartment access is mediated by wrappers.
-
Opaque by default. (WindowProxy and Location are special; they get CrossOriginXrayWrappers.)
-
WindowProxy/Location will return "document.domain waivers" when granting cross-compartment access. This will result in the caller receiving a DocumentDomainWrapper, which is like a transparent CCW, with the exception that they also hand out DocumentDomainWrappers for any returned object properties, transitively.
-