Bug 840488 - Introduce a mechanism to temporarily or permanently block script for a given scope, and use it for unsafe channels. r=bz
authorBobby Holley <bobbyholley@gmail.com>
Tue, 12 Nov 2013 16:43:33 -0800
changeset 169373 52a8082a281f16affbbdf79f081df098efe580cc
parent 169372 9f0e23094b733ca953ffff0fa887ad443305da90
child 169374 a0a49b75b8aaffd096673f2bee153b4f6c473f0b
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs840488
milestone28.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 840488 - Introduce a mechanism to temporarily or permanently block script for a given scope, and use it for unsafe channels. r=bz
caps/src/nsScriptSecurityManager.cpp
docshell/base/nsDocShell.cpp
dom/base/nsGlobalWindow.cpp
js/xpconnect/idl/xpccomponents.idl
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
modules/libjar/nsIJARChannel.idl
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -1709,16 +1709,21 @@ nsScriptSecurityManager::CanExecuteScrip
 NS_IMETHODIMP_(bool)
 nsScriptSecurityManager::ScriptAllowed(JSObject *aGlobal)
 {
     MOZ_ASSERT(aGlobal);
     MOZ_ASSERT(JS_IsGlobalObject(aGlobal) || js::IsOuterObject(aGlobal));
     AutoJSContext cx_;
     JS::RootedObject global(cx_, js::UncheckedUnwrap(aGlobal, /* stopAtOuter = */ false));
 
+    // Check the bits on the compartment private.
+    if (!xpc::Scriptability::Get(global).Allowed()) {
+        return false;
+    }
+
     nsCOMPtr<nsIScriptContext> scx = nsJSUtils::GetStaticScriptContext(global);
     AutoPushJSContext cx(scx ? scx->GetNativeContext() : GetSafeJSContext());
     bool result = false;
     nsresult rv = CanExecuteScripts(cx, doGetObjectPrincipal(global), &result);
     return NS_SUCCEEDED(rv) && result;
 }
 
 ///////////////// Principals ///////////////////////
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2072,22 +2072,16 @@ nsDocShell::SetAllowPlugins(bool aAllowP
 }
 
 NS_IMETHODIMP
 nsDocShell::GetAllowJavascript(bool * aAllowJavascript)
 {
     NS_ENSURE_ARG_POINTER(aAllowJavascript);
 
     *aAllowJavascript = mAllowJavascript;
-    if (!mAllowJavascript) {
-        return NS_OK;
-    }
-
-    bool unsafe;
-    *aAllowJavascript = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetAllowJavascript(bool aAllowJavascript)
 {
     mAllowJavascript = aAllowJavascript;
     return NS_OK;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -134,16 +134,17 @@
 #include "nsITimedChannel.h"
 #include "nsServiceManagerUtils.h"
 #ifdef MOZ_XUL
 #include "nsIDOMXULControlElement.h"
 #include "nsMenuPopupFrame.h"
 #endif
 #include "nsIDOMCustomEvent.h"
 #include "nsIFrameRequestCallback.h"
+#include "nsIJARChannel.h"
 
 #include "xpcprivate.h"
 
 #ifdef NS_PRINTING
 #include "nsIPrintSettings.h"
 #include "nsIPrintSettingsService.h"
 #include "nsIWebBrowserPrint.h"
 #endif
@@ -2536,16 +2537,24 @@ nsGlobalWindow::SetNewDocument(nsIDocume
       newInnerWindow->InnerSetNewDocument(aDocument);
 
       // Initialize DOM classes etc on the inner window.
       JS::Rooted<JSObject*> obj(cx, newInnerWindow->mJSObject);
       rv = mContext->InitClasses(obj);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
+    // If the document comes from a JAR, check if the channel was determined
+    // to be unsafe. If so, permanently disable script on the compartment by
+    // calling Block() and throwing away the key.
+    nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aDocument->GetChannel());
+    if (jarChannel && jarChannel->GetIsUnsafe()) {
+      xpc::Scriptability::Get(newInnerWindow->mJSObject).Block();
+    }
+
     if (mArguments) {
       newInnerWindow->DefineArgumentsProperty(mArguments);
       mArguments = nullptr;
     }
 
     // Give the new inner window our chrome event handler (since it
     // doesn't have one).
     newInnerWindow->mChromeEventHandler = mChromeEventHandler;
--- 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(90019271-a0d1-439f-911c-967f8e85cd5b)]
+[scriptable, uuid(12368474-b448-4ac6-ba2c-db1966a7a697)]
 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.
@@ -443,16 +443,33 @@ interface nsIXPCComponents_Utils : nsISu
     attribute boolean ion;
 
     [implicit_jscontext]
     void setGCZeal(in long zeal);
 
     [implicit_jscontext]
     void nukeSandbox(in jsval obj);
 
+    /*
+     * API to dynamically block script for a given global. This takes effect
+     * immediately, unlike other APIs that only affect newly-created globals.
+     *
+     * The machinery here maintains a counter, and allows script only if each
+     * call to blockScriptForGlobal() has been matched with a call to
+     * unblockScriptForGlobal(). The caller _must_ make sure never to call
+     * unblock() more times than it calls block(), since that could potentially
+     * interfere with another consumer's script blocking.
+     */
+
+    [implicit_jscontext]
+    void blockScriptForGlobal(in jsval global);
+
+    [implicit_jscontext]
+    void unblockScriptForGlobal(in jsval global);
+
     /**
      * Check whether the given object is an XrayWrapper.
      */
     bool isXrayWrapper(in jsval obj);
 
     /**
      * Waive Xray on a given value. Identity op for primitives.
      */
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3370,16 +3370,48 @@ nsXPCComponents_Utils::NukeSandbox(const
     NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG);
     NukeCrossCompartmentWrappers(cx, AllCompartments(),
                                  SingleCompartment(GetObjectCompartment(sb)),
                                  NukeWindowReferences);
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsXPCComponents_Utils::BlockScriptForGlobal(const JS::Value &globalArg,
+                                            JSContext *cx)
+{
+    NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG);
+    JSObject *global = UncheckedUnwrap(&globalArg.toObject(),
+                                       /* stopAtOuter = */ false);
+    NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG);
+    if (nsContentUtils::IsSystemPrincipal(GetObjectPrincipal(global))) {
+        JS_ReportError(cx, "Script may not be disabled for system globals");
+        return NS_ERROR_FAILURE;
+    }
+    Scriptability::Get(global).Block();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXPCComponents_Utils::UnblockScriptForGlobal(const JS::Value &globalArg,
+                                              JSContext *cx)
+{
+    NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG);
+    JSObject *global = UncheckedUnwrap(&globalArg.toObject(),
+                                       /* stopAtOuter = */ false);
+    NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG);
+    if (nsContentUtils::IsSystemPrincipal(GetObjectPrincipal(global))) {
+        JS_ReportError(cx, "Script may not be disabled for system globals");
+        return NS_ERROR_FAILURE;
+    }
+    Scriptability::Get(global).Unblock();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsXPCComponents_Utils::IsXrayWrapper(const Value &obj, bool* aRetval)
 {
     *aRetval =
         obj.isObject() && xpc::WrapperFactory::IsXrayWrapper(&obj.toObject());
     return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -405,16 +405,44 @@ EnsureCompartmentPrivate(JSCompartment *
     CompartmentPrivate *priv = GetCompartmentPrivate(c);
     if (priv)
         return priv;
     priv = new CompartmentPrivate();
     JS_SetCompartmentPrivate(c, priv);
     return priv;
 }
 
+Scriptability::Scriptability() : mScriptBlocks(0) {}
+
+bool
+Scriptability::Allowed()
+{
+    return mScriptBlocks == 0;
+}
+
+void
+Scriptability::Block()
+{
+    ++mScriptBlocks;
+}
+
+void
+Scriptability::Unblock()
+{
+    MOZ_ASSERT(mScriptBlocks > 0);
+    --mScriptBlocks;
+}
+
+/* static */
+Scriptability&
+Scriptability::Get(JSObject *aScope)
+{
+    return EnsureCompartmentPrivate(aScope)->scriptability;
+}
+
 bool
 IsXBLScope(JSCompartment *compartment)
 {
     // We always eagerly create compartment privates for XBL scopes.
     CompartmentPrivate *priv = GetCompartmentPrivate(compartment);
     if (!priv || !priv->scope)
         return false;
     return priv->scope->IsXBLScope();
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -3777,16 +3777,19 @@ public:
     // 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;
 
     // for telemetry. See bug 928476.
     bool adoptedNode;
     bool donatedNode;
 
+    // The scriptability of this compartment.
+    Scriptability scriptability;
+
     // Our XPCWrappedNativeScope. This is non-null if and only if this is an
     // XPConnect compartment.
     XPCWrappedNativeScope *scope;
 
     const nsACString& GetLocation() {
         if (location.IsEmpty() && locationURI) {
             if (NS_FAILED(locationURI->GetSpec(location)))
                 location = NS_LITERAL_CSTRING("<unknown location>");
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -29,16 +29,35 @@ class nsScriptNameSpaceManager;
 class nsIGlobalObject;
 class nsIMemoryReporterCallback;
 
 #ifndef BAD_TLS_INDEX
 #define BAD_TLS_INDEX ((uint32_t) -1)
 #endif
 
 namespace xpc {
+
+class Scriptability {
+public:
+    Scriptability();
+    bool Allowed();
+
+    void Block();
+    void Unblock();
+
+    static Scriptability& Get(JSObject *aScope);
+
+private:
+    // Whenever a consumer wishes to prevent script from running on a global,
+    // it increments this value with a call to Block(). When it wishes to
+    // re-enable it (if ever), it decrements this value with a call to Unblock().
+    // Script may not run if this value is non-zero.
+    uint32_t mScriptBlocks;
+};
+
 JSObject *
 TransplantObject(JSContext *cx, JS::HandleObject origobj, JS::HandleObject target);
 
 // Return a raw XBL scope object corresponding to contentScope, which must
 // be an object whose global is a DOM window.
 //
 // The return value is not wrapped into cx->compartment, so be sure to enter
 // its compartment before doing anything meaningful.
--- a/modules/libjar/nsIJARChannel.idl
+++ b/modules/libjar/nsIJARChannel.idl
@@ -1,23 +1,23 @@
 /* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "nsIChannel.idl"
 
-[scriptable, uuid(6e6cc56d-51eb-4299-a795-dcfd1229ab3d)]
+[scriptable, builtinclass, uuid(6e6cc56d-51eb-4299-a795-dcfd1229ab3d)]
 interface nsIJARChannel : nsIChannel 
 {
     /**
      * Returns TRUE if the JAR file is not safe (if the content type reported
      * by the server for a remote JAR is not of an expected type).  Scripting,
      * redirects, and plugins should be disabled when loading from this
      * channel.
      */
-    readonly attribute boolean isUnsafe;
+    [infallible] readonly attribute boolean isUnsafe;
 
     /**
      * Forces the uri to be a app:// uri.
      */
     void setAppURI(in nsIURI uri);
 };