Bug 742444 - Only propagate waivers between same-origin compartments. r=gabor
authorBobby Holley <bobbyholley@gmail.com>
Tue, 22 Jul 2014 16:14:27 -0700
changeset 195562 d1fa85777c40
parent 195561 cd56605c08f6
child 195563 4f6d9db92389
push id46631
push userbobbyholley@gmail.com
push date2014-07-22 23:14 +0000
treeherdermozilla-inbound@d1fa85777c40 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgabor
bugs742444
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 742444 - Only propagate waivers between same-origin compartments. r=gabor
js/xpconnect/tests/chrome/test_bug812415.xul
js/xpconnect/tests/unit/test_bug742444.js
js/xpconnect/tests/unit/xpcshell.ini
js/xpconnect/wrappers/WrapperFactory.cpp
--- a/js/xpconnect/tests/chrome/test_bug812415.xul
+++ b/js/xpconnect/tests/chrome/test_bug812415.xul
@@ -20,19 +20,19 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 812415 and Bug 823348 **/
 
   const Cu = Components.utils;
   SimpleTest.waitForExplicitFinish();
 
   function testWaiving(iwin, sb) {
     sb.win = iwin;
     is(Cu.evalInSandbox('win', sb), iwin, "Basic identity works");
-    is(Cu.evalInSandbox('win.wrappedJSObject', sb), iwin.wrappedJSObject, "Waivers work via .wrappedJSObject");
-    is(Cu.evalInSandbox('XPCNativeWrapper.unwrap(win)', sb), iwin.wrappedJSObject, "Waivers work via XPCNativeWrapper.unwrap");
-    is(Cu.evalInSandbox('win.wrappedJSObject.document', sb), iwin.document.wrappedJSObject, "Waivers are deep");
+    is(Cu.evalInSandbox('win.wrappedJSObject.expando', sb), 42, "Waivers work via .wrappedJSObject");
+    is(Cu.evalInSandbox('XPCNativeWrapper.unwrap(win).expando', sb), 42, "Waivers work via XPCNativeWrapper.unwrap");
+    is(Cu.evalInSandbox('win.wrappedJSObject.document.defaultView.expando', sb), 42, "Waivers are deep");
   }
 
   function checkThrows(expression, sb, msg) {
     var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb);
     ok(!!/denied/.exec(result), msg);
   }
 
   function testAsymmetric(regular, expanded) {
@@ -62,16 +62,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     // Check __proto__ stuff.
     is(Cu.evalInSandbox('regFun.__proto__', expanded), regular.Function.prototype, "expanded can get __proto__");
     checkThrows('expFun.__proto__', regular, "regular can't use __proto__");
     checkThrows('expFun.__proto__ = {}', regular, "regular can't mutate __proto__");
   }
 
   function go() {
     var iwin = document.getElementById('ifr').contentWindow;
+    iwin.wrappedJSObject.expando = 42;
 
     // Make our sandboxes. We pass wantXrays=false for the nsEP to ensure that
     // the Xrays we get are the result of being an nsEP, not from the wantXrays
     // flag.
     var regular = new Components.utils.Sandbox(iwin);
     var expanded = new Components.utils.Sandbox([iwin], {wantXrays: false});
 
     // Because of the crazy secret life of wantXrays, passing wantXrays=false
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_bug742444.js
@@ -0,0 +1,17 @@
+const Cu = Components.utils;
+function run_test() {
+  let sb1A = Cu.Sandbox('http://www.example.com');
+  let sb1B = Cu.Sandbox('http://www.example.com');
+  let sb2 = Cu.Sandbox('http://www.example.org');
+  let sbChrome = Cu.Sandbox(this);
+  let obj = new sb1A.Object();
+  sb1B.obj = obj;
+  sb1B.waived = Cu.waiveXrays(obj);
+  sb2.obj = obj;
+  sb2.waived = Cu.waiveXrays(obj);
+  sbChrome.obj = obj;
+  sbChrome.waived = Cu.waiveXrays(obj);
+  do_check_true(Cu.evalInSandbox('obj === waived', sb1B));
+  do_check_true(Cu.evalInSandbox('obj === waived', sb2));
+  do_check_true(Cu.evalInSandbox('obj !== waived', sbChrome));
+}
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -19,16 +19,17 @@ support-files =
 
 [test_bogus_files.js]
 [test_bug408412.js]
 [test_bug451678.js]
 [test_bug604362.js]
 [test_bug641378.js]
 [test_bug677864.js]
 [test_bug711404.js]
+[test_bug742444.js]
 [test_bug778409.js]
 [test_bug780370.js]
 [test_bug805807.js]
 [test_bug809652.js]
 [test_bug809674.js]
 [test_bug813901.js]
 [test_bug845201.js]
 [test_bug845862.js]
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -123,21 +123,48 @@ ForceCOWBehavior(JSObject *obj)
     // Proxies get OpaqueXrayTraits, but we still need COWs to them for now to
     // let the SpecialPowers wrapper work.
     if (key == JSProto_Proxy)
         return true;
 
     return false;
 }
 
+inline bool
+ShouldWaiveXray(JSContext *cx, JSObject *originalObj)
+{
+    unsigned flags;
+    (void) js::UncheckedUnwrap(originalObj, /* stopAtOuter = */ true, &flags);
+
+    // If the original object did not point through an Xray waiver, we're done.
+    if (!(flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG))
+        return false;
+
+    // If the original object was not a cross-compartment wrapper, that means
+    // that the caller explicitly created a waiver. Preserve it so that things
+    // like WaiveXrayAndWrap work.
+    if (!(flags & Wrapper::CROSS_COMPARTMENT))
+        return true;
+
+    // Otherwise, this is a case of explicitly passing a wrapper across a
+    // compartment boundary. In that case, we only want to preserve waivers
+    // in transactions between same-origin compartments.
+    JSCompartment *oldCompartment = js::GetObjectCompartment(originalObj);
+    JSCompartment *newCompartment = js::GetContextCompartment(cx);
+    bool sameOrigin =
+        AccessCheck::subsumesConsideringDomain(oldCompartment, newCompartment) &&
+        AccessCheck::subsumesConsideringDomain(newCompartment, oldCompartment);
+    return sameOrigin;
+}
+
 JSObject *
 WrapperFactory::PrepareForWrapping(JSContext *cx, HandleObject scope,
                                    HandleObject objArg, HandleObject objectPassedToWrap)
 {
-    bool waive = WrapperFactory::HasWaiveXrayFlag(objectPassedToWrap);
+    bool waive = ShouldWaiveXray(cx, objectPassedToWrap);
     RootedObject obj(cx, objArg);
     // Outerize any raw inner objects at the entry point here, so that we don't
     // have to worry about them for the rest of the wrapping code.
     if (js::IsInnerObject(obj)) {
         JSAutoCompartment ac(cx, obj);
         obj = JS_ObjectToOuterObject(cx, obj);
         NS_ENSURE_TRUE(obj, nullptr);
         // The outerization hook wraps, which means that we can end up with a