Disallow content access to chrome functions without __exposedProps__ (bug 628410, r=gal).
authorJonas Sicking <jonas@sicking.cc>
Sat, 29 Jan 2011 18:47:17 -0800
changeset 61692 6d5c859c452db930a3e1d620c8b2592a124ff79c
parent 61691 8835fffb27afe4de1580c98596abe3f10ea2d83c
child 61693 2fb3475b30365f1fbceee6298206828a3c6cc0ea
child 61869 d7bd84fc85da815e67a6b1fdaef80fe7c5a9c12d
push id18450
push usercleary@mozilla.com
push dateTue, 01 Feb 2011 03:47:12 +0000
treeherdermozilla-central@fd63bdaa9cfc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgal
bugs628410
milestone2.0b11pre
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
Disallow content access to chrome functions without __exposedProps__ (bug 628410, r=gal).
dom/tests/mochitest/whatwg/postMessage_chrome_helper.html
dom/tests/mochitest/whatwg/test_bug500328.html
editor/libeditor/text/tests/test_bug527935.html
js/src/xpconnect/wrappers/AccessCheck.cpp
layout/base/tests/test_bug458898.html
layout/tools/reftest/reftest.js
testing/mochitest/specialpowers/content/specialpowers.js
toolkit/components/microformats/tests/test_Microformats_add.html
toolkit/components/prompts/test/test_modal_prompts.html
--- a/dom/tests/mochitest/whatwg/postMessage_chrome_helper.html
+++ b/dom/tests/mochitest/whatwg/postMessage_chrome_helper.html
@@ -7,17 +7,21 @@
 
     function receiveMessage(evt)
     {
       if (evt.data.substring(0,9) == "chrome://") {
         gPrePath = evt.data;
         respond("path-is-set");
       } else {
         // Content cannot post to chrome without privileges
-        window.parent.postMessage("SHOULD NOT GET THIS!", "*");
+        try {
+          window.parent.postMessage("SHOULD NOT GET THIS!", "*");
+        }
+        catch (ex) {
+        }
 
         var msg = "post-to-content-response";
 
         if (evt.source !== null)
           msg += " wrong-source(" + evt.source + ")";
         if (!evt.isTrusted)
           msg += " unexpected-untrusted-event";
         if (evt.type !== "message")
--- a/dom/tests/mochitest/whatwg/test_bug500328.html
+++ b/dom/tests/mochitest/whatwg/test_bug500328.html
@@ -123,30 +123,16 @@ function getSHistory(theWindow)
                     .getInterface(Ci.nsIWebNavigation)
                     .sessionHistory;
   if (!sh || sh == null)
     throw("Couldn't get shistory for window!");
 
   return sh;
 }
 
-function getChromeWin(theWindow)
-{
- netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
- const Ci = Components.interfaces;
- return theWindow
-           .QueryInterface(Ci.nsIInterfaceRequestor)
-           .getInterface(Ci.nsIWebNavigation)
-           .QueryInterface(Ci.nsIDocShellTreeItem)
-           .rootTreeItem
-           .QueryInterface(Ci.nsIInterfaceRequestor)
-           .getInterface(Ci.nsIDOMWindow)
-           .QueryInterface(Ci.nsIDOMChromeWindow);
-}
-
 function getSHTitle(sh, offset)
 {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   if (!offset)
     offset = 0;
 
   // False instructs the SHistory not to modify its current index.
@@ -400,18 +386,17 @@ function runTest() {
 
   var shistory = getSHistory(popup);
 
   enableChildPopStateCallback();
   yield;
   popstateExpected("Didn't get popstate after opening window.");
 
   popup.history.pushState(null, "title 0");
-  ok(!getChromeWin(popup).document
-        .getElementById("Browser:Back").hasAttribute("disabled"),
+  ok(SpecialPowers.isBackButtonEnabled(popup),
      "Back button was not enabled after initial pushstate.");
 
   popup.document.title = "title 1";
 
   // Yield to the event loop so listeners will be notified of the title change
   // and so that the hash change we trigger below generates a new session
   // history entry.
   shortWait();
--- a/editor/libeditor/text/tests/test_bug527935.html
+++ b/editor/libeditor/text/tests/test_bug527935.html
@@ -17,62 +17,45 @@ https://bugzilla.mozilla.org/show_bug.cg
   <iframe id="formTarget" name="formTarget"></iframe>
   <form action="data:text/html," target="formTarget">
     <input name="test" id="initValue"><input type="submit">
   </form>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
-function getAutocompletePopup() {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var Ci = Components.interfaces;
-    chromeWin = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIWebNavigation)
-                      .QueryInterface(Ci.nsIDocShellTreeItem)
-                      .rootTreeItem
-                      .QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIDOMWindow)
-                      .QueryInterface(Ci.nsIDOMChromeWindow);
-    autocompleteMenu = chromeWin.document.getElementById("PopupAutoComplete");
-    ok(autocompleteMenu, "Got autocomplete popup");
-
-    return autocompleteMenu;
-}
-
 /** Test for Bug 527935 **/
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function() {
   var formTarget = document.getElementById("formTarget");
   var initValue = document.getElementById("initValue");
 
   formTarget.addEventListener("load", function() {
     var newInput = document.createElement("input");
     newInput.setAttribute("name", "test");
     document.body.appendChild(newInput);
 
     setTimeout(function() {
       var popupShown = false;
-      var popup = getAutocompletePopup();
       function listener() {
         popupShown = true;
       }
-      popup.addEventListener("popupshowing", listener, false);
+      SpecialPowers.addAutoCompletePopupEventListener(window, listener);
 
       var event = document.createEvent("KeyboardEvent");
 
       event.initKeyEvent("keypress", true, true, null, false, false,
                          false, false, 0, "f".charCodeAt(0));
       newInput.value = "";
       newInput.focus();
       newInput.dispatchEvent(event);
 
       setTimeout(function() {
         ok(!popupShown, "Popup must not be opened");
-        popup.removeEventListener("popupshowing", listener, false);
+        SpecialPowers.removeAutoCompletePopupEventListener(window, listener);
         SimpleTest.finish();
       }, 1000);
     }, 0);
   }, false);
 
   initValue.focus();
   initValue.value = "foo";
   synthesizeKey("VK_ENTER", {});
--- a/js/src/xpconnect/wrappers/AccessCheck.cpp
+++ b/js/src/xpconnect/wrappers/AccessCheck.cpp
@@ -418,66 +418,106 @@ AccessCheck::deny(JSContext *cx, jsid id
         if (!str)
             return;
         const jschar *chars = JS_GetStringCharsZ(cx, str);
         if (chars)
             JS_ReportError(cx, "Permission denied to access property '%hs'", chars);
     }
 }
 
-typedef enum { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 } Access;
+enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 };
+
+bool
+PermitIfUniversalXPConnect(ExposedPropertiesOnly::Permission &perm)
+{
+    // If UniversalXPConnect is enabled, allow access even if __exposedProps__ doesn't
+    // exists.
+    nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
+    if (!ssm) {
+        return false;
+    }
+    PRBool privileged;
+    if (NS_SUCCEEDED(ssm->IsCapabilityEnabled("UniversalXPConnect", &privileged)) &&
+        privileged) {
+        perm = ExposedPropertiesOnly::PermitPropertyAccess;
+        return true; // Allow
+    }
+
+    // Use default
+    return true;
+}
 
 bool
 ExposedPropertiesOnly::check(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act,
                              Permission &perm)
 {
-    JSObject *holder = JSWrapper::wrappedObject(wrapper);
+    JSObject *wrappedObject = JSWrapper::wrappedObject(wrapper);
+
+    if (act == JSWrapper::CALL) {
+        perm = PermitObjectAccess;
+        return true;
+    }
 
     perm = DenyAccess;
 
     jsid exposedPropsId = GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS);
 
     JSBool found = JS_FALSE;
     JSAutoEnterCompartment ac;
-    if (!ac.enter(cx, holder) || !JS_HasPropertyById(cx, holder, exposedPropsId, &found))
+    if (!ac.enter(cx, wrappedObject) ||
+        !JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
         return false;
+
+    // Always permit access to "length" and indexed properties of arrays.
+    if (JS_IsArrayObject(cx, wrappedObject) &&
+        ((JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) ||
+         (JSID_IS_ATOM(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length")))) {
+        perm = PermitPropertyAccess;
+        return true; // Allow
+    }
+
+    // If no __exposedProps__ existed, deny access.
     if (!found) {
-        perm = PermitObjectAccess;
-        return true; // Allow
+        // For now, only do this on functions.
+        if (!JS_ObjectIsFunction(cx, wrappedObject)) {
+            perm = PermitPropertyAccess;
+            return true;
+        }
+        return PermitIfUniversalXPConnect(perm); // Deny
     }
 
     if (id == JSID_VOID) {
         // This will force the caller to call us back for individual property accesses.
         perm = PermitPropertyAccess;
         return true;
     }
 
     jsval exposedProps;
-    if (!JS_LookupPropertyById(cx, holder, exposedPropsId, &exposedProps))
+    if (!JS_LookupPropertyById(cx, wrappedObject, exposedPropsId, &exposedProps))
         return false;
 
     if (JSVAL_IS_VOID(exposedProps) || JSVAL_IS_NULL(exposedProps)) {
-        return true; // Deny
+        return PermitIfUniversalXPConnect(perm); // Deny
     }
 
     if (!JSVAL_IS_OBJECT(exposedProps)) {
         JS_ReportError(cx, "__exposedProps__ must be undefined, null, or an Object");
         return false;
     }
 
     JSObject *hallpass = JSVAL_TO_OBJECT(exposedProps);
 
     Access access = NO_ACCESS;
 
     JSPropertyDescriptor desc;
     if (!JS_GetPropertyDescriptorById(cx, hallpass, id, JSRESOLVE_QUALIFIED, &desc)) {
         return false; // Error
     }
     if (desc.obj == NULL || !(desc.attrs & JSPROP_ENUMERATE)) {
-        return true; // Deny
+        return PermitIfUniversalXPConnect(perm); // Deny
     }
 
     if (!JSVAL_IS_STRING(desc.value)) {
         JS_ReportError(cx, "property must be a string");
         return false;
     }
 
     JSString *str = JSVAL_TO_STRING(desc.value);
@@ -512,16 +552,16 @@ ExposedPropertiesOnly::check(JSContext *
 
     if (access == NO_ACCESS) {
         JS_ReportError(cx, "specified properties must have a permission bit set");
         return false;
     }
 
     if ((act == JSWrapper::SET && !(access & WRITE)) ||
         (act != JSWrapper::SET && !(access & READ))) {
-        return true; // Deny
+        return PermitIfUniversalXPConnect(perm); // Deny
     }
 
     perm = PermitPropertyAccess;
     return true; // Allow
 }
 
 }
--- a/layout/base/tests/test_bug458898.html
+++ b/layout/base/tests/test_bug458898.html
@@ -14,25 +14,27 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=458898">Mozilla Bug 458898</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
+// This should be rewritten as a chrome test
 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 var win = window.openDialog("data:text/html,<div style='height:200px; width:100px;'>");
 
 // doesn't succeed on SeaMonkey currently, see bug 469331
 // mark it todo there instead
 var testfunc_h = (navigator.userAgent.match(/ SeaMonkey\//)) ? todo : ok;
 var testfunc_w = (navigator.userAgent.match(/Windows/)) ? ok : testfunc_h;
 
 function loaded() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   win.sizeToContent();
   testfunc_w(win.innerWidth >= 100, "innerWidth: " + win.innerWidth + " >= 100 ?");
   testfunc_h(win.innerHeight >= 200, "innerHeight: " + win.innerHeight + " >= 200 ?");
   win.close();
   SimpleTest.finish();
 }
 
 win.addEventListener("load", loaded, false);
--- a/layout/tools/reftest/reftest.js
+++ b/layout/tools/reftest/reftest.js
@@ -380,17 +380,17 @@ function getStreamContent(inputStream)
   return streamBuf;
 }
 
 // Build the sandbox for fails-if(), etc., condition evaluation.
 function BuildConditionSandbox(aURL) {
     var sandbox = new Components.utils.Sandbox(aURL.spec);
     var xr = CC[NS_XREAPPINFO_CONTRACTID].getService(CI.nsIXULRuntime);
     sandbox.isDebugBuild = gDebug.isDebugBuild;
-    sandbox.xulRuntime = {widgetToolkit: xr.widgetToolkit, OS: xr.OS};
+    sandbox.xulRuntime = {widgetToolkit: xr.widgetToolkit, OS: xr.OS, __exposedProps__: { widgetToolkit: "r", OS: "r", XPCOMABI: "r", shell: "r" } };
 
     // xr.XPCOMABI throws exception for configurations without full ABI
     // support (mobile builds on ARM)
     try {
       sandbox.xulRuntime.XPCOMABI = xr.XPCOMABI;
     } catch(e) {
       sandbox.xulRuntime.XPCOMABI = "";
     }
@@ -410,22 +410,24 @@ function BuildConditionSandbox(aURL) {
     // Shortcuts for widget toolkits.
     sandbox.cocoaWidget = xr.widgetToolkit == "cocoa";
     sandbox.gtk2Widget = xr.widgetToolkit == "gtk2";
     sandbox.qtWidget = xr.widgetToolkit == "qt";
     sandbox.winWidget = xr.widgetToolkit == "windows";
 
     var hh = CC[NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX + "http"].
                  getService(CI.nsIHttpProtocolHandler);
-    sandbox.http = {};
+    sandbox.http = { __exposedProps__: {} };
     for each (var prop in [ "userAgent", "appName", "appVersion",
                             "vendor", "vendorSub",
                             "product", "productSub",
-                            "platform", "oscpu", "language", "misc" ])
+                            "platform", "oscpu", "language", "misc" ]) {
         sandbox.http[prop] = hh[prop];
+        sandbox.http.__exposedProps__[prop] = "r";
+    }
     // see if we have the test plugin available,
     // and set a sandox prop accordingly
     sandbox.haveTestPlugin = false;
     for (var i = 0; i < navigator.mimeTypes.length; i++) {
         if (navigator.mimeTypes[i].type == "application/x-test" &&
             navigator.mimeTypes[i].enabledPlugin != null &&
             navigator.mimeTypes[i].enabledPlugin.name == "Test Plug-in") {
             sandbox.haveTestPlugin = true;
--- a/testing/mochitest/specialpowers/content/specialpowers.js
+++ b/testing/mochitest/specialpowers/content/specialpowers.js
@@ -84,19 +84,55 @@ var SpecialPowers = {
   _setPref: function(aPrefName, aPrefType, aValue, aIid) {
     var msg = {};
     if (aIid) {
       msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]};
     } else {
       msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue};
     }
     return(sendSyncMessage('SPPrefService', msg)[0]);
-  }
+  },
+
+  _getTopChromeWindow: function(window) {
+    var Ci = Components.interfaces;
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIDocShellTreeItem)
+                 .rootTreeItem
+                 .QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIDOMWindow)
+                 .QueryInterface(Ci.nsIDOMChromeWindow);
+  },
+  _getAutoCompletePopup: function(window) {
+    return this._getTopChromeWindow(window).document
+                                           .getElementById("PopupAutoComplete");
+  },
+  addAutoCompletePopupEventListener: function(window, listener) {
+    this._getAutoCompletePopup(window).addEventListener("popupshowing",
+                                                        listener,
+                                                        false);
+  },
+  removeAutoCompletePopupEventListener: function(window, listener) {
+    this._getAutoCompletePopup(window).removeEventListener("popupshowing",
+                                                           listener,
+                                                           false);
+  },
+  isBackButtonEnabled: function(window) {
+    return !this._getTopChromeWindow(window).document
+                                      .getElementById("Browser:Back")
+                                      .hasAttribute("disabled")
+  },
 }
 
+SpecialPowers.__exposedProps__ = {};
+for each (i in Object.keys(SpecialPowers).filter(function(v) {return v.charAt(0) != "_"})) {
+  SpecialPowers.__exposedProps__[i] = "r";
+}
+
+
 // Attach our API to the window
 function attachSpecialPwrToWindow(aSubject) {
   try {
     if ((aSubject !== null) && 
         (aSubject !== undefined) &&
         (aSubject.wrappedJSObject) &&
         !(aSubject.wrappedJSObject.SpecialPowers)) {
       aSubject.wrappedJSObject.SpecialPowers = SpecialPowers;
--- a/toolkit/components/microformats/tests/test_Microformats_add.html
+++ b/toolkit/components/microformats/tests/test_Microformats_add.html
@@ -76,16 +76,17 @@
     </span>
   </div>
 
   <!-- Ok, the test, here we go -->
   <pre id="test">
   <script class="testbody" type="text/javascript">
 
   function hTest(node, validate) {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
     if (node)
       Microformats.parser.newMicroformat(this, node, "hTest", validate);
   }
 
   hTest.prototype.toString = function () {
     return("This is a test");
   }
 
--- a/toolkit/components/prompts/test/test_modal_prompts.html
+++ b/toolkit/components/prompts/test/test_modal_prompts.html
@@ -754,17 +754,20 @@ function handleDialog(ui, testNum) {
 
     if (testNum == 24) {
         ui.button2.click();
     } else if (testNum == 26) {
         // Buttons are disabled at the moment, poll until they're reenabled.
         // Can't use setInterval here, because the window's in a modal state
         // and thus DOM events are suppressed.
         pollTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-        pollTimer.initWithCallback(function() { pollDialog(ui.button0); },
+        pollTimer.initWithCallback(function() {
+          netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+          pollDialog(ui.button0);
+        },
                                    100, Ci.nsITimer.TYPE_REPEATING_SLACK);
         return;
     } else {
         if (clickOK)
             ui.button0.click();
         else
             ui.button1.click();
     }