Bug 1356334: Part 3 - Enforce a stricter slow script timeout for extension content scripts. r=billm draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 11 Aug 2017 21:30:54 -0700
changeset 645266 0b5c2bdab8a394eb8565959f8b88fa19fdefc8dc
parent 645265 dc2781f7b4ee5c3e6844f0a12c70dc4adc5b738b
child 645267 f66e837f86b9781c683b3813f75d72b40366cbb8
push id73722
push usermaglione.k@gmail.com
push dateSat, 12 Aug 2017 05:24:05 +0000
reviewersbillm
bugs1356334
milestone57.0a1
Bug 1356334: Part 3 - Enforce a stricter slow script timeout for extension content scripts. r=billm MozReview-Commit-ID: LLvPQn1x1Xj
js/xpconnect/src/XPCJSContext.cpp
js/xpconnect/src/XPCJSRuntime.cpp
modules/libpref/init/all.js
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -56,16 +56,18 @@
 #include "AccessCheck.h"
 #include "nsGlobalWindow.h"
 #include "nsAboutProtocolUtils.h"
 
 #include "GeckoProfiler.h"
 #include "nsIInputStream.h"
 #include "nsIXULRuntime.h"
 #include "nsJSPrincipals.h"
+#include "ExpandedPrincipal.h"
+#include "SystemPrincipal.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 #ifdef XP_WIN
 #include <windows.h>
 #endif
@@ -222,16 +224,17 @@ class Watchdog
     bool mHibernating;
     bool mInitialized;
     bool mShuttingDown;
     mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds;
 };
 
 #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
 #define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time"
+#define PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT "dom.max_ext_content_script_run_time"
 
 class WatchdogManager : public nsIObserver
 {
   public:
 
     NS_DECL_ISUPPORTS
     explicit WatchdogManager(XPCJSContext* aContext) : mContext(aContext)
                                                      , mContextState(CONTEXT_INACTIVE)
@@ -242,16 +245,17 @@ class WatchdogManager : public nsIObserv
 
         // Enable the watchdog, if appropriate.
         RefreshWatchdog();
 
         // Register ourselves as an observer to get updates on the pref.
         mozilla::Preferences::AddStrongObserver(this, "dom.use_watchdog");
         mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
         mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
+        mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT);
     }
 
   protected:
 
     virtual ~WatchdogManager()
     {
         // Shutting down the watchdog requires context-switching to the watchdog
         // thread, which isn't great to do in a destructor. So we require
@@ -261,16 +265,17 @@ class WatchdogManager : public nsIObserv
 
   public:
 
     void Shutdown()
     {
         mozilla::Preferences::RemoveObserver(this, "dom.use_watchdog");
         mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
         mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
+        mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT);
     }
 
     NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
                        const char16_t* aData) override
     {
         RefreshWatchdog();
         return NS_OK;
     }
@@ -336,17 +341,20 @@ class WatchdogManager : public nsIObserv
 
         if (mWatchdog) {
             int32_t contentTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 10);
             if (contentTime <= 0)
                 contentTime = INT32_MAX;
             int32_t chromeTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CHROME, 20);
             if (chromeTime <= 0)
                 chromeTime = INT32_MAX;
-            mWatchdog->SetMinScriptRunTimeSeconds(std::min(contentTime, chromeTime));
+            int32_t extTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT, 5);
+            if (extTime <= 0)
+                extTime = INT32_MAX;
+            mWatchdog->SetMinScriptRunTimeSeconds(std::min({contentTime, chromeTime, extTime}));
         }
     }
 
     void StartWatchdog()
     {
         MOZ_ASSERT(!mWatchdog);
         mWatchdog = new Watchdog(this);
         mWatchdog->Init();
@@ -458,16 +466,43 @@ XPCJSContext::ActivityCallback(void* arg
     if (!active) {
         ProcessHangMonitor::ClearHang();
     }
 
     XPCJSContext* self = static_cast<XPCJSContext*>(arg);
     self->mWatchdogManager->RecordContextActivity(active);
 }
 
+static inline bool
+IsWebExtensionPrincipal(nsIPrincipal* principal, nsAString& addonId)
+{
+    return (NS_SUCCEEDED(principal->GetAddonId(addonId)) &&
+            !addonId.IsEmpty());
+}
+
+static bool
+IsWebExtensionContentScript(BasePrincipal* principal, nsAString& addonId)
+{
+    if (!principal->Is<ExpandedPrincipal>()) {
+        return false;
+    }
+
+    auto expanded = principal->As<ExpandedPrincipal>();
+
+    nsTArray<nsCOMPtr<nsIPrincipal>>* principals;
+    expanded->GetWhiteList(&principals);
+    for (auto prin : *principals) {
+        if (IsWebExtensionPrincipal(prin, addonId)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 // static
 bool
 XPCJSContext::InterruptCallback(JSContext* cx)
 {
     XPCJSContext* self = XPCJSContext::Get();
 
     // Now is a good time to turn on profiling if it's pending.
     profiler_js_interrupt_callback();
@@ -487,20 +522,33 @@ XPCJSContext::InterruptCallback(JSContex
     // has finished bootstrapping. Avoid crashing in nsContentUtils below.
     if (!nsContentUtils::IsInitialized())
         return true;
 
     // This is at least the second interrupt callback we've received since
     // returning to the event loop. See how long it's been, and what the limit
     // is.
     TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
-    bool chrome = nsContentUtils::IsSystemCaller(cx);
-    const char* prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
-                                  : PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
-    int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10);
+    int32_t limit;
+
+    nsString addonId;
+    const char* prefName;
+
+    auto principal = BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx));
+    bool chrome = principal->Is<SystemPrincipal>();
+    if (chrome) {
+        prefName = PREF_MAX_SCRIPT_RUN_TIME_CHROME;
+        limit = Preferences::GetInt(prefName, 20);
+    } else if (IsWebExtensionContentScript(principal, addonId)) {
+        prefName = PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT;
+        limit = Preferences::GetInt(prefName, 5);
+    } else {
+        prefName = PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
+        limit = Preferences::GetInt(prefName, 10);
+    }
 
     // If there's no limit, or we're within the limit, let it go.
     if (limit == 0 || duration.ToSeconds() < limit / 2.0)
         return true;
 
     self->mSlowScriptActualWait += duration;
 
     // In order to guard against time changes or laptops going to sleep, we
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -312,35 +312,35 @@ bool CompartmentPrivate::TryParseLocatio
 
 static bool
 PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal)
 {
     // System principal gets a free pass.
     if (nsXPConnect::SecurityManager()->IsSystemPrincipal(aPrincipal))
         return true;
 
+    auto principal = BasePrincipal::Cast(aPrincipal);
+
     // ExpandedPrincipal gets a free pass.
-    nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
-    if (ep)
+    if (principal->Is<ExpandedPrincipal>()) {
         return true;
+    }
+
+    // WebExtension principals get a free pass.
+    nsString addonId;
+    if (IsExtensionPrincipal(principal, addonId)) {
+        return true;
+    }
 
     // Check whether our URI is an "about:" URI that allows scripts.  If it is,
     // we need to allow JS to run.
     nsCOMPtr<nsIURI> principalURI;
     aPrincipal->GetURI(getter_AddRefs(principalURI));
     MOZ_ASSERT(principalURI);
 
-    // WebExtension principals gets a free pass.
-    nsString addonId;
-    aPrincipal->GetAddonId(addonId);
-    bool isWebExtension = !addonId.IsEmpty();
-    if (isWebExtension) {
-        return true;
-    }
-
     bool isAbout;
     nsresult rv = principalURI->SchemeIs("about", &isAbout);
     if (NS_SUCCEEDED(rv) && isAbout) {
         nsCOMPtr<nsIAboutModule> module;
         rv = NS_GetAboutModule(principalURI, getter_AddRefs(module));
         if (NS_SUCCEEDED(rv)) {
             uint32_t flags;
             rv = module->GetURIFlags(principalURI, &flags);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3105,16 +3105,17 @@ pref("viewmanager.do_doublebuffering", t
 pref("gestures.enable_single_finger_input", true);
 
 pref("editor.resizing.preserve_ratio",       true);
 pref("editor.positioning.offset",            0);
 
 pref("dom.use_watchdog", true);
 pref("dom.max_chrome_script_run_time", 20);
 pref("dom.max_script_run_time", 10);
+pref("dom.max_ext_content_script_run_time", 5);
 
 // Stop all scripts in a compartment when the "stop script" dialog is used.
 pref("dom.global_stop_script", true);
 
 // Time (milliseconds) between throttled idle callbacks.
 pref("dom.idle_period.throttled_length", 10000);
 
 // The amount of idle time (milliseconds) reserved for a long idle period