From faa8815613c28d5aa4f266b3f995a8bbe73d5246 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 18 Aug 2017 11:04:55 -0700 Subject: [PATCH] Bug 1356334: Part 3 - Enforce a stricter slow script timeout for extension content scripts. r=billm MozReview-Commit-ID: LLvPQn1x1Xj --HG-- extra : source : 805c568069301ae91ead5780cdc118af73907229 extra : histedit_source : b188836d1dc3ad8021bf2d0b1c89aebedf2db185%2C8c7b51c9f4af4eb5ad67811c29b56c72b43fa31d --- dom/ipc/ContentPrefs.cpp | 1 + js/xpconnect/src/XPCJSContext.cpp | 58 +++++++++++++++++-- js/xpconnect/src/XPCJSRuntime.cpp | 20 +++---- modules/libpref/init/all.js | 1 + .../test/mochitest/mochitest-common.ini | 1 + 5 files changed, 66 insertions(+), 15 deletions(-) diff --git a/dom/ipc/ContentPrefs.cpp b/dom/ipc/ContentPrefs.cpp index 355e737474f6..37d9be22478e 100644 --- a/dom/ipc/ContentPrefs.cpp +++ b/dom/ipc/ContentPrefs.cpp @@ -54,6 +54,7 @@ const char* mozilla::dom::ContentPrefs::gInitPrefs[] = { "dom.ipc.processPriorityManager.backgroundPerceivableGracePeriodMS", "dom.ipc.useNativeEventProcessing.content", "dom.max_chrome_script_run_time", + "dom.max_ext_content_script_run_time", "dom.max_script_run_time", "dom.mozBrowserFramesEnabled", "dom.performance.enable_notify_performance_timing", diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp index a4adbeb83bb7..5895bfc0850f 100644 --- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -61,6 +61,8 @@ #include "nsIInputStream.h" #include "nsIXULRuntime.h" #include "nsJSPrincipals.h" +#include "ExpandedPrincipal.h" +#include "SystemPrincipal.h" #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" @@ -227,6 +229,7 @@ class Watchdog #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 { @@ -247,6 +250,7 @@ class WatchdogManager : public nsIObserver 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: @@ -266,6 +270,7 @@ class WatchdogManager : public nsIObserver 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, @@ -341,7 +346,10 @@ class WatchdogManager : public nsIObserver 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})); } } @@ -463,6 +471,33 @@ XPCJSContext::ActivityCallback(void* arg, bool active) 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()) { + return false; + } + + auto expanded = principal->As(); + + nsTArray>* principals; + expanded->GetWhiteList(&principals); + for (auto prin : *principals) { + if (IsWebExtensionPrincipal(prin, addonId)) { + return true; + } + } + + return false; +} + // static bool XPCJSContext::InterruptCallback(JSContext* cx) @@ -492,10 +527,23 @@ XPCJSContext::InterruptCallback(JSContext* cx) // 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(); + 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) diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 69161ec12986..e0fe3052274a 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -324,10 +324,18 @@ PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) if (nsXPConnect::SecurityManager()->IsSystemPrincipal(aPrincipal)) return true; + auto principal = BasePrincipal::Cast(aPrincipal); + // ExpandedPrincipal gets a free pass. - nsCOMPtr ep = do_QueryInterface(aPrincipal); - if (ep) + if (principal->Is()) { return true; + } + + // WebExtension principals get a free pass. + nsString addonId; + if (IsWebExtensionPrincipal(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. @@ -335,14 +343,6 @@ PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) 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) { diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 13670b04d4ab..4c643dc500cb 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3167,6 +3167,7 @@ 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); diff --git a/toolkit/components/extensions/test/mochitest/mochitest-common.ini b/toolkit/components/extensions/test/mochitest/mochitest-common.ini index ef53beafea14..673ad47a3a75 100644 --- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini +++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini @@ -61,6 +61,7 @@ skip-if = os == 'android' # Android does not support multiple windows. [test_ext_content_security_policy.html] [test_ext_contentscript_api_injection.html] [test_ext_contentscript_async_loading.html] +skip-if = os == 'android' && debug # The generated script takes too long to load on Android debug [test_ext_contentscript_cache.html] skip-if = (os == 'linux' && debug) || (toolkit == 'android' && debug) # bug 1348241 [test_ext_contentscript_canvas.html]