Bug 757046 - Convert enablePrivilege into an insecure test-only construct (preffed off everywhere but in automation). r=bz
authorBobby Holley <bobbyholley@gmail.com>
Thu, 23 Aug 2012 11:45:28 -0700
changeset 105236 c1e3da499d876c011d23fa20c272a9101454b5d4
parent 105235 9ecd49f138f0ecb9f5b15f13c76792092d7077cb
child 105237 f4c286fc04f0a4275cdb24546a916f3750f58a8d
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersbz
bugs757046
milestone17.0a1
Bug 757046 - Convert enablePrivilege into an insecure test-only construct (preffed off everywhere but in automation). r=bz
build/automation.py.in
caps/src/nsScriptSecurityManager.cpp
caps/src/nsSecurityManagerFactory.cpp
js/xpconnect/src/xpcprivate.h
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -429,38 +429,23 @@ user_pref("extensions.hotfix.url", "http
 // Make sure opening about:addons won't hit the network
 user_pref("extensions.webservice.discoverURL", "http://%(server)s/extensions-dummy/discoveryURL");
 // Make sure AddonRepository won't hit the network
 user_pref("extensions.getAddons.maxResults", 0);
 user_pref("extensions.getAddons.get.url", "http://%(server)s/extensions-dummy/repositoryGetURL");
 user_pref("extensions.getAddons.getWithPerformance.url", "http://%(server)s/extensions-dummy/repositoryGetWithPerformanceURL");
 user_pref("extensions.getAddons.search.browseURL", "http://%(server)s/extensions-dummy/repositoryBrowseURL");
 user_pref("extensions.getAddons.search.url", "http://%(server)s/extensions-dummy/repositorySearchURL");
+
+// Make enablePrivilege continue to work for test code. :-(
+user_pref("security.enablePrivilege.enable_for_tests", true);
 """ % { "server" : self.webServer + ":" + str(self.httpPort) }
     prefs.append(part)
 
-    if useServerLocations == False:
-      part = """
-user_pref("capability.principal.codebase.p1.granted", "UniversalXPConnect");
-user_pref("capability.principal.codebase.p1.id", "%(origin)s");
-user_pref("capability.principal.codebase.p1.subjectName", "");
-"""  % { "origin": "http://" + self.webServer + ":" + str(self.httpPort) }
-      prefs.append(part)
-    else:
-      # Grant God-power to all the privileged servers on which tests run.
-      privileged = filter(lambda loc: "privileged" in loc.options, locations)
-      for (i, l) in itertools.izip(itertools.count(1), privileged):
-        part = """
-user_pref("capability.principal.codebase.p%(i)d.granted", "UniversalXPConnect");
-user_pref("capability.principal.codebase.p%(i)d.id", "%(origin)s");
-user_pref("capability.principal.codebase.p%(i)d.subjectName", "");
-"""  % { "i": i,
-         "origin": (l.scheme + "://" + l.host + ":" + str(l.port)) }
-        prefs.append(part)
-
+    if useServerLocations:
       # We need to proxy every server but the primary one.
       origins = ["'%s://%s:%s'" % (l.scheme, l.host, l.port)
                 for l in filter(lambda l: "primary" not in l.options, locations)]
       origins = ", ".join(origins)
 
       pacURL = """data:text/plain,
 function FindProxyForURL(url, host)
 {
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -2436,81 +2436,20 @@ nsScriptSecurityManager::old_doGetObject
 }
 #endif /* DEBUG */
 
 ///////////////// Capabilities API /////////////////////
 NS_IMETHODIMP
 nsScriptSecurityManager::IsCapabilityEnabled(const char *capability,
                                              bool *result)
 {
-    nsresult rv;
-    JSStackFrame *fp = nullptr;
     JSContext *cx = GetCurrentJSContext();
-    fp = cx ? JS_FrameIterator(cx, &fp) : nullptr;
-
-    if (!fp)
-    {
-        // No script code on stack. Allow access if and only if the subject
-        // principal is system.
-        nsresult ignored;
-        nsIPrincipal *subjectPrin = doGetSubjectPrincipal(&ignored);
-        *result = (!subjectPrin || subjectPrin == mSystemPrincipal);
+    if (cx && (*result = xpc::IsUniversalXPConnectEnabled(cx)))
         return NS_OK;
-    }
-
-    *result = false;
-    nsIPrincipal* previousPrincipal = nullptr;
-    do
-    {
-        nsIPrincipal* principal = GetFramePrincipal(cx, fp, &rv);
-        if (NS_FAILED(rv))
-            return rv;
-        if (!principal)
-            continue;
-        // If caller has a different principal, stop looking up the stack.
-        if(previousPrincipal)
-        {
-            bool isEqual = false;
-            if(NS_FAILED(previousPrincipal->Equals(principal, &isEqual)) || !isEqual)
-                break;
-        }
-        else
-            previousPrincipal = principal;
-
-        // First check if the principal is even able to enable the
-        // given capability. If not, don't look any further.
-        int16_t canEnable;
-        rv = principal->CanEnableCapability(capability, &canEnable);
-        if (NS_FAILED(rv)) return rv;
-        if (canEnable != nsIPrincipal::ENABLE_GRANTED &&
-            canEnable != nsIPrincipal::ENABLE_WITH_USER_PERMISSION)
-            return NS_OK;
-
-        // Now see if the capability is enabled.
-        void *annotation = JS_GetFrameAnnotation(cx, fp);
-        rv = principal->IsCapabilityEnabled(capability, annotation, result);
-        if (NS_FAILED(rv)) return rv;
-        if (*result)
-            return NS_OK;
-
-        // Capabilities do not extend to calls into C/C++ and then back into
-        // the JS engine via JS_EvaluateScript or similar APIs.
-        if (JS_IsGlobalFrame(cx, fp))
-            break;
-    } while ((fp = JS_FrameIterator(cx, &fp)) != nullptr);
-
-    if (!previousPrincipal)
-    {
-        // No principals on the stack, all native code.  Allow
-        // execution if the subject principal is the system principal.
-
-        return SubjectPrincipalIsSystem(result);
-    }
-
-    return NS_OK;
+    return SubjectPrincipalIsSystem(result);
 }
 
 void
 nsScriptSecurityManager::FormatCapabilityString(nsAString& aCapability)
 {
     nsAutoString newcaps;
     nsAutoString rawcap;
     NS_NAMED_LITERAL_STRING(capdesc, "capdesc.");
--- a/caps/src/nsSecurityManagerFactory.cpp
+++ b/caps/src/nsSecurityManagerFactory.cpp
@@ -20,94 +20,37 @@
 #include "nsString.h"
 #include "nsNetCID.h"
 #include "nsIClassInfoImpl.h"
 #include "nsJSUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIDocument.h"
 #include "jsfriendapi.h"
+#include "xpcprivate.h"
+#include "mozilla/Preferences.h"
 
 ///////////////////////
 // nsSecurityNameSet //
 ///////////////////////
 
 nsSecurityNameSet::nsSecurityNameSet()
 {
 }
 
 nsSecurityNameSet::~nsSecurityNameSet()
 {
 }
 
 NS_IMPL_ISUPPORTS1(nsSecurityNameSet, nsIScriptExternalNameSet)
 
-static JSString *
-getStringArgument(JSContext *cx, JSObject *obj, uint16_t argNum, unsigned argc, jsval *argv)
-{
-    if (argc <= argNum || !JSVAL_IS_STRING(argv[argNum])) {
-        JS_ReportError(cx, "String argument expected");
-        return nullptr;
-    }
-
-    /*
-     * We don't want to use JS_ValueToString because we want to be able
-     * to have an object to represent a target in subsequent versions.
-     */
-    return JSVAL_TO_STRING(argv[argNum]);
-}
-
-static bool
-getBytesArgument(JSContext *cx, JSObject *obj, uint16_t argNum, unsigned argc, jsval *argv,
-                 JSAutoByteString *bytes)
-{
-    JSString *str = getStringArgument(cx, obj, argNum, argc, argv);
-    return str && bytes->encode(cx, str);
-}
-
 static JSBool
 netscape_security_enablePrivilege(JSContext *cx, unsigned argc, jsval *vp)
 {
-    JSObject *obj = JS_THIS_OBJECT(cx, vp);
-    if (!obj)
-        return JS_FALSE;
-
-    JSAutoByteString cap;
-    if (!getBytesArgument(cx, obj, 0, argc, JS_ARGV(cx, vp), &cap))
-        return JS_FALSE;
-
-    // Can't use nsContentUtils::GetDocumentFromCaller because that
-    // depends on various XPConnect stuff that's not set up here.
-    {
-        JSAutoEnterCompartment ac;
-        if (ac.enter(cx, obj)) {
-            nsCOMPtr<nsPIDOMWindow> win =
-                do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(cx, obj));
-            if (win) {
-                nsCOMPtr<nsIDocument> doc =
-                    do_QueryInterface(win->GetExtantDocument());
-                if (doc) {
-                    doc->WarnOnceAbout(nsIDocument::eEnablePrivilege);
-                }
-            }
-        }
-    }
-
-    nsresult rv;
-    nsCOMPtr<nsIScriptSecurityManager> securityManager = 
-             do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
-    if (NS_FAILED(rv)) 
-        return JS_FALSE;
-
-    //    NS_ASSERTION(cx == GetCurrentContext(), "unexpected context");
-
-    rv = securityManager->EnableCapability(cap.ptr());
-    if (NS_FAILED(rv))
-        return JS_FALSE;
-    JS_SET_RVAL(cx, vp, JSVAL_VOID);
+    xpc::EnableUniversalXPConnect(cx);
     return JS_TRUE;
 }
 
 static JSFunctionSpec PrivilegeManager_static_methods[] = {
     JS_FS("enablePrivilege", netscape_security_enablePrivilege, 1, 0),
     JS_FS_END
 };
 
@@ -116,16 +59,25 @@ static JSFunctionSpec PrivilegeManager_s
  * et al. so that code that worked with 4.0 can still work.
  */
 NS_IMETHODIMP 
 nsSecurityNameSet::InitializeNameSet(nsIScriptContext* aScriptContext)
 {
     JSContext* cx = aScriptContext->GetNativeContext();
     JSObject *global = JS_ObjectToInnerObject(cx, JS_GetGlobalObject(cx));
 
+    // We hide enablePrivilege behind a pref because it has been altered in a
+    // way that makes it fundamentally insecure to use in production. Mozilla
+    // uses this pref during automated testing to support legacy test code that
+    // uses enablePrivilege. If you're not doing test automation, you _must_ not
+    // flip this pref, or you will be exposing all your users to security
+    // vulnerabilities.
+    if (!mozilla::Preferences::GetBool("security.enablePrivilege.enable_for_tests"))
+        return NS_OK;
+
     /*
      * Find Object.prototype's class by walking up the global object's
      * prototype chain.
      */
     JSObject *obj = global;
     JSObject *proto;
     JSAutoRequest ar(cx);
     while ((proto = JS_GetPrototype(obj)) != nullptr)
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -4303,23 +4303,31 @@ namespace xpc {
 
 class CompartmentPrivate
 {
 public:
     typedef nsTHashtable<nsPtrHashKey<JSObject> > DOMExpandoMap;
 
     CompartmentPrivate(bool wantXrays)
         : wantXrays(wantXrays)
+        , universalXPConnectEnabled(false)
     {
         MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
     }
 
     ~CompartmentPrivate();
 
     bool wantXrays;
+
+    // This is only ever set during mochitest runs when enablePrivilege is called.
+    // It's intended as a temporary stopgap measure until we can finish ripping out
+    // enablePrivilege. Once set, this value is never unset (i.e., it doesn't follow
+    // the old scoping rules of enablePrivilege). Using it is inherently unsafe.
+    bool universalXPConnectEnabled;
+
     nsAutoPtr<JSObject2JSObjectMap> waiverWrapperMap;
     nsAutoPtr<DOMExpandoMap> domExpandoMap;
 
     bool RegisterDOMExpandoObject(JSObject *expando) {
         if (!domExpandoMap) {
             domExpandoMap = new DOMExpandoMap();
             domExpandoMap->Init(8);
         }
@@ -4371,16 +4379,40 @@ GetCompartmentPrivate(JSObject *object)
 {
     MOZ_ASSERT(object);
     JSCompartment *compartment = js::GetObjectCompartment(object);
 
     MOZ_ASSERT(compartment);
     return GetCompartmentPrivate(compartment);
 }
 
+inline bool IsUniversalXPConnectEnabled(JSContext *cx)
+{
+    JSCompartment *compartment = js::GetContextCompartment(cx);
+    if (!compartment)
+        return false;
+    CompartmentPrivate *priv =
+      static_cast<CompartmentPrivate*>(JS_GetCompartmentPrivate(compartment));
+    if (!priv)
+        return false;
+    return priv->universalXPConnectEnabled;
+}
+
+inline void EnableUniversalXPConnect(JSContext *cx)
+{
+    JSCompartment *compartment = js::GetContextCompartment(cx);
+    if (!compartment)
+        return;
+    CompartmentPrivate *priv =
+      static_cast<CompartmentPrivate*>(JS_GetCompartmentPrivate(compartment));
+    if (!priv)
+        return;
+    priv->universalXPConnectEnabled = true;
+}
+
 }
 
 /***************************************************************************/
 // Inlines use the above - include last.
 
 #include "XPCInlines.h"
 
 /***************************************************************************/