Bug 951948 - Add a way for automation to force the creation of a privileged Components object for an unprivileged scope. r=ted,r=mrbkap
authorBobby Holley <bobbyholley@gmail.com>
Tue, 14 Jan 2014 18:49:29 -0800
changeset 163797 b61deb1bee05550378eb468c0bf4b2d9b7163e80
parent 163796 4c687bba563b762fdf999dea21b79c3f1a8469ce
child 163798 707abbb92a8ba920caae74fdaf9dc62e6364bc66
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersted, mrbkap
bugs951948
milestone29.0a1
Bug 951948 - Add a way for automation to force the creation of a privileged Components object for an unprivileged scope. r=ted,r=mrbkap
js/xpconnect/idl/xpccomponents.idl
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/XPCWrappedNativeScope.cpp
js/xpconnect/src/xpcprivate.h
testing/specialpowers/content/specialpowersAPI.js
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -115,17 +115,17 @@ interface nsIXPCComponents_utils_Sandbox
 interface ScheduledGCCallback : nsISupports
 {
     void callback();
 };
 
 /**
 * interface of Components.utils
 */
-[scriptable, uuid(ae292d08-cfb0-4862-a2b6-4779e5ef7e72)]
+[scriptable, uuid(1b981569-2b21-4702-a01a-81ffdb5d3d4c)]
 interface nsIXPCComponents_Utils : nsISupports
 {
 
     /* reportError is designed to be called from JavaScript only.
      *
      * It will report a JS Error object to the JS console, and return. It
      * is meant for use in exception handler blocks which want to "eat"
      * an exception, but still want to report it to the console.
@@ -408,16 +408,23 @@ interface nsIXPCComponents_Utils : nsISu
      *
      * Enables Xray vision for same-compartment access for the compartment
      * indicated by |vscope|. All outgoing wrappers are recomputed.
      */
     [implicit_jscontext]
     void setWantXrays(in jsval vscope);
 
     /*
+     * Forces the usage of a privileged |Components| object for a potentially-
+     * unprivileged scope. This will crash if used outside of automation.
+     */
+    [implicit_jscontext]
+    void forcePrivilegedComponentsForScope(in jsval vscope);
+
+    /*
      * This seemingly-paradoxical API allows privileged code to explicitly give
      * unprivileged code a reference to its own Components object (whereas it's
      * normally hidden away on a scope chain visible only to XBL methods). See
      * also SpecialPowers.getComponents.
      */
     [implicit_jscontext]
     jsval getComponentsForScope(in jsval vscope);
 
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3193,16 +3193,29 @@ nsXPCComponents_Utils::SetWantXrays(cons
     JSCompartment *compartment = js::GetObjectCompartment(scopeObj);
     EnsureCompartmentPrivate(scopeObj)->wantXrays = true;
     bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment),
                                     js::AllCompartments());
     NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
     return NS_OK;
 }
 
+/* jsval forcePrivilegedComponentsForScope(jsval vscope); */
+NS_IMETHODIMP
+nsXPCComponents_Utils::ForcePrivilegedComponentsForScope(const jsval &vscope,
+                                                         JSContext *cx)
+{
+    if (!vscope.isObject())
+        return NS_ERROR_INVALID_ARG;
+    JSObject *scopeObj = js::UncheckedUnwrap(&vscope.toObject());
+    XPCWrappedNativeScope *scope = GetObjectScope(scopeObj);
+    scope->ForcePrivilegedComponents();
+    return NS_OK;
+}
+
 /* jsval getComponentsForScope(jsval vscope); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::GetComponentsForScope(const jsval &vscope, JSContext *cx,
                                              jsval *rval)
 {
     if (!vscope.isObject())
         return NS_ERROR_INVALID_ARG;
     JSObject *scopeObj = js::UncheckedUnwrap(&vscope.toObject());
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -556,16 +556,17 @@ EnableUniversalXPConnect(JSContext *cx)
     NS_ENSURE_TRUE(ok, false);
 
     // The Components object normally isn't defined for unprivileged web content,
     // but we define it when UniversalXPConnect is enabled to support legacy
     // tests.
     XPCWrappedNativeScope *scope = priv->scope;
     if (!scope)
         return true;
+    scope->ForcePrivilegedComponents();
     return scope->AttachComponentsObject(cx);
 }
 
 JSObject *
 GetJunkScope()
 {
     XPCJSRuntime *self = nsXPConnect::GetRuntimeInstance();
     NS_ENSURE_TRUE(self, nullptr);
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -1,10 +1,11 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=78:
+ * 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/. */
 
 /* Class used to manage the wrapped native objects within a JS scope. */
 
 #include "xpcprivate.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionNoteRootCallback.h"
@@ -148,16 +149,29 @@ XPCWrappedNativeScope::GetComponentsJSOb
     // The call to wrap() here is necessary even though the object is same-
     // compartment, because it applies our security wrapper.
     JS::RootedObject obj(cx, &val.toObject());
     if (NS_WARN_IF(!JS_WrapObject(cx, &obj)))
         return nullptr;
     return obj;
 }
 
+void
+XPCWrappedNativeScope::ForcePrivilegedComponents()
+{
+    // This may only be called on unprivileged scopes during automation where
+    // we allow insecure things.
+    MOZ_RELEASE_ASSERT(Preferences::GetBool("security.turn_off_all_security_so_"
+                                            "that_viruses_can_take_over_this_"
+                                            "computer"));
+    nsCOMPtr<nsIXPCComponents> c = do_QueryInterface(mComponents);
+    if (!c)
+        mComponents = new nsXPCComponents(this);
+}
+
 bool
 XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx)
 {
     RootedObject components(aCx, GetComponentsJSObject());
     if (!components)
         return false;
 
     RootedObject global(aCx, GetGlobalJSObject());
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1070,16 +1070,21 @@ public:
     GetWrappedNativeMap() const {return mWrappedNativeMap;}
 
     ClassInfo2WrappedNativeProtoMap*
     GetWrappedNativeProtoMap() const {return mWrappedNativeProtoMap;}
 
     nsXPCComponentsBase*
     GetComponents() const {return mComponents;}
 
+    // Forces the creation of a privileged |Components| object, even in
+    // content scopes. This will crash if used outside of automation.
+    void
+    ForcePrivilegedComponents();
+
     bool AttachComponentsObject(JSContext *aCx);
 
     // Returns the JS object reflection of the Components object.
     JSObject*
     GetComponentsJSObject();
 
     JSObject*
     GetGlobalJSObject() const {
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -37,16 +37,23 @@ function bindDOMWindowUtils(aWindow) {
     return
 
    var util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIDOMWindowUtils);
    return wrapPrivileged(util);
 }
 
 function getRawComponents(aWindow) {
+  // If we're running in automation that supports enablePrivilege, then we also
+  // provided access to the privileged Components.
+  try {
+    let win = Cu.waiveXrays(aWindow);
+    if (typeof win.netscape.security.PrivilegeManager == 'object')
+      Cu.forcePrivilegedComponentsForScope(aWindow);
+  } catch (e) {}
   return Cu.getComponentsForScope(aWindow);
 }
 
 function isWrappable(x) {
   if (typeof x === "object")
     return x !== null;
   return typeof x === "function";
 };
@@ -544,30 +551,52 @@ SpecialPowersAPI.prototype = {
     return this.wrap(chromeScript);
   },
 
   get Services() {
     return wrapPrivileged(Services);
   },
 
   /*
+   * In general, any Components object created for unprivileged scopes is
+   * neutered (it implements nsIXPCComponentsBase, but not nsIXPCComponents).
+   * We override this in certain legacy automation configurations (see the
+   * implementation of getRawComponents() above), but don't want to support
+   * it in cases where it isn't already required.
+   *
+   * In scopes with neutered Components, we don't have a natural referent for
+   * things like SpecialPowers.Cc. So in those cases, we fall back to the
+   * Components object from the SpecialPowers scope. This doesn't quite behave
+   * the same way (in particular, SpecialPowers.Cc[foo].createInstance() will
+   * create an instance in the SpecialPowers scope), but SpecialPowers wrapping
+   * is already a YMMV / Whatever-It-Takes-To-Get-TBPL-Green sort of thing.
+   *
+   * It probably wouldn't be too much work to just make SpecialPowers.Components
+   * unconditionally point to the Components object in the SpecialPowers scope.
+   * Try will tell what needs to be fixed up.
+   */
+  getFullComponents: function() {
+    return typeof this.Components.classes == 'object' ? this.Components
+                                                      : Components;
+  },
+
+  /*
    * Convenient shortcuts to the standard Components abbreviations. Note that
    * we don't SpecialPowers-wrap Components.interfaces, because it's available
    * to untrusted content, and wrapping it confuses QI and identity checks.
    */
-  get Cc() { return wrapPrivileged(this.Components).classes; },
+  get Cc() { return wrapPrivileged(this.getFullComponents()).classes; },
   get Ci() { return this.Components.interfaces; },
-  get Cu() { return wrapPrivileged(this.Components).utils; },
+  get Cu() { return wrapPrivileged(this.getFullComponents()).utils; },
   get Cr() { return wrapPrivileged(this.Components).results; },
 
   /*
-   * SpecialPowers.getRawComponents() allows content to get a reference to the
-   * naked (non-SpecialPowers-wrapped) Components object for its scope. This
-   * object is normally hidden away on a scope chain available only to XBL
-   * functions.
+   * SpecialPowers.getRawComponents() allows content to get a reference to a
+   * naked (and, in certain automation configurations, privileged) Components
+   * object for its scope.
    *
    * SpecialPowers.getRawComponents(window) is defined as the global property
    * window.SpecialPowers.Components for convenience.
    */
   getRawComponents: getRawComponents,
 
   getDOMWindowUtils: function(aWindow) {
     if (aWindow == this.window.get() && this.DOMWindowUtils != null)