Bug 1583949 - Add a check for IsEvalAllowed to the worker callpath for eval() r=ckerschb,baku
authorTom Ritter <tom@mozilla.com>
Tue, 08 Oct 2019 17:31:35 +0000
changeset 496792 3e896e8ca6b7899fa2e256eb3a6b0c0a935bf5a7
parent 496791 119e4808983044dd1b29fa7da03724ae4354d418
child 496793 668ba95593cffcfce716e51b98d24d0410cae4cf
push id36668
push useraiakab@mozilla.com
push dateWed, 09 Oct 2019 04:06:09 +0000
treeherdermozilla-central@26ebfec08834 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, baku
bugs1583949, 1584564, 1584605, 1584602
milestone71.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 1583949 - Add a check for IsEvalAllowed to the worker callpath for eval() r=ckerschb,baku This patch does several things. Because Workers aren't on the main thread, many of the things done are in the name of off main thread access. 1) Changes a parameter in IsEvalAllowed from a nsIPrincipal to a bool. We only used the principal to determined if it was the System Principal. Principals aren't thread safe and can only be accessed on Main Thread, so if we passed a Principal in, we would be in error. Instead only pass in the bool which - for workers - comes from a thread-safe location. 2) Separates out the Telemetry Event Recording and sending a message to the console into a new function nsContentSecurityUtils::NotifyEvalUsage. (And creates a runnable that calls it.) We do this because we will need to only call this method on the main thread. Telemetry Event Recording has only ever been called on the Main Thread. While I possibly-successfully cut it over to happen Off Main Thread (OMT) by porting preferences to StaticPrefs, I don't know if there were other threading assumptions in the Telemetry Code. So it would be much safer to just continue recording Event Telemetry on the main thread. Sending a message to the console requires calling GetStringBundleService() which requires main thread. I didn't investigate if this could be made thread-safe, I just threw it onto the main thread too. If, in IsEvalAllowed, we are on the main thread - we call NotifyEvalUsage directly. If we are not, we create a runnable which will then call NotifyEvalUsage for us on the main thread. 3) Ports allow_eval_with_system_principal and allow_eval_in_parent_process from bools to RelaxedAtomicBool - because we now check these prefs OMT. 4) In RuntimeService.cpp, adds the call to IsEvalAllowed. 5) Add resource://gre/modules/workers/require.js to the allowlist of eval usage. This was the script that identified this gap in the first place. It uses eval (twice) for structural reasons (scope and line number massaging.) The contents of the eval are the result of a request to a uri (which may be internal, like resource://). The whole point of this is to implement a CommonJS require() api. This usage of eval is safe because the only way an attacker can inject into it is by either controlling the response of the uri request or controlling (or appending to) the argument. If they can do that, they are able to inject script into Firefox even if we cut this usage of eval over to some other type of safe(r) script loader. Bug 1584564 tracks making sure calls to require.js are safe. 6) Adds cld-worker.js to the allowlist. Bug 1584605 is for refactoring that eval usage, which is decidedly non-trivial. 7) Does _not_ enforce the eval restrictions for workers. While I've gotten try to be green and not throw up any instances of eval-usage by workers, it is much safer to deploy this is Telemetry-only mode for Workers for a little bit to see if anything pops up from the Nightly population. Bug 1584602 is for enforcing the checks. Differential Revision: https://phabricator.services.mozilla.com/D47480
caps/nsScriptSecurityManager.cpp
dom/security/CSPEvalChecker.cpp
dom/security/nsContentSecurityUtils.cpp
dom/security/nsContentSecurityUtils.h
dom/workers/RuntimeService.cpp
modules/libpref/init/StaticPrefList.yaml
toolkit/components/ctypes/tests/chrome/test_ctypes.xul
toolkit/components/telemetry/Events.yaml
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -447,18 +447,18 @@ bool nsScriptSecurityManager::ContentSec
 
     if (NS_WARN_IF(!scriptSample.init(cx, jsString))) {
       JS_ClearPendingException(cx);
       return false;
     }
   }
 
 #if !defined(ANDROID)
-  if (!nsContentSecurityUtils::IsEvalAllowed(cx, subjectPrincipal,
-                                             scriptSample)) {
+  if (!nsContentSecurityUtils::IsEvalAllowed(
+          cx, subjectPrincipal->IsSystemPrincipal(), scriptSample)) {
     return false;
   }
 #endif
 
   if (NS_FAILED(rv)) {
     NS_WARNING("CSP: failed to get allowsEval");
     return true;  // fail open to not break sites.
   }
--- a/dom/security/CSPEvalChecker.cpp
+++ b/dom/security/CSPEvalChecker.cpp
@@ -31,18 +31,18 @@ nsresult CheckInternal(nsIContentSecurit
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aAllowed);
 
   // The value is set at any "return", but better to have a default value here.
   *aAllowed = false;
 
 #if !defined(ANDROID)
   JSContext* cx = nsContentUtils::GetCurrentJSContext();
-  if (!nsContentSecurityUtils::IsEvalAllowed(cx, aSubjectPrincipal,
-                                             aExpression)) {
+  if (!nsContentSecurityUtils::IsEvalAllowed(
+          cx, aSubjectPrincipal->IsSystemPrincipal(), aExpression)) {
     *aAllowed = false;
     return NS_OK;
   }
 #endif
 
   if (!aCSP) {
     *aAllowed = true;
     return NS_OK;
--- a/dom/security/nsContentSecurityUtils.cpp
+++ b/dom/security/nsContentSecurityUtils.cpp
@@ -10,28 +10,30 @@
 
 #include "nsIContentSecurityPolicy.h"
 #include "nsIURI.h"
 
 #include "mozilla/dom/Document.h"
 
 /*
  * Performs a Regular Expression match, optionally returning the results.
+ * This function is not safe to use OMT.
  *
  * @param aPattern      The regex pattern
  * @param aString       The string to compare against
  * @param aOnlyMatch    Whether we want match results or only a true/false for
  * the match
  * @param aMatchResult  Out param for whether or not the pattern matched
  * @param aRegexResults Out param for the matches of the regex, if requested
  * @returns nsresult indicating correct function operation or error
  */
 nsresult RegexEval(const nsAString& aPattern, const nsAString& aString,
                    bool aOnlyMatch, bool& aMatchResult,
                    nsTArray<nsString>* aRegexResults = nullptr) {
+  MOZ_ASSERT(NS_IsMainThread());
   aMatchResult = false;
 
   AutoJSAPI jsapi;
   jsapi.Init();
 
   JSContext* cx = jsapi.cx();
   AutoDisableJSInterruptCallback disabler(cx);
 
@@ -142,30 +144,36 @@ FilenameType nsContentSecurityUtils::Fil
   static NS_NAMED_LITERAL_CSTRING(kChromeURI, "chromeuri");
   static NS_NAMED_LITERAL_CSTRING(kResourceURI, "resourceuri");
   static NS_NAMED_LITERAL_CSTRING(kSingleString, "singlestring");
   static NS_NAMED_LITERAL_CSTRING(kMozillaExtension, "mozillaextension");
   static NS_NAMED_LITERAL_CSTRING(kOtherExtension, "otherextension");
   static NS_NAMED_LITERAL_CSTRING(kSuspectedUserChromeJS,
                                   "suspectedUserChromeJS");
   static NS_NAMED_LITERAL_CSTRING(kOther, "other");
+  static NS_NAMED_LITERAL_CSTRING(kOtherWorker, "other-on-worker");
   static NS_NAMED_LITERAL_CSTRING(kRegexFailure, "regexfailure");
 
   static NS_NAMED_LITERAL_STRING(kUCJSRegex, "(.+).uc.js\\?*[0-9]*$");
   static NS_NAMED_LITERAL_STRING(kExtensionRegex, "extensions/(.+)@(.+)!(.+)$");
   static NS_NAMED_LITERAL_STRING(kSingleFileRegex, "^[a-zA-Z0-9.?]+$");
 
   // resource:// and chrome://
   if (StringBeginsWith(fileName, NS_LITERAL_STRING("chrome://"))) {
     return FilenameType(kChromeURI, Some(fileName));
   }
   if (StringBeginsWith(fileName, NS_LITERAL_STRING("resource://"))) {
     return FilenameType(kResourceURI, Some(fileName));
   }
 
+  if (!NS_IsMainThread()) {
+    // We can't do Regex matching off the main thread; so just report.
+    return FilenameType(kOtherWorker, Nothing());
+  }
+
   // Extension
   bool regexMatch;
   nsTArray<nsString> regexResults;
   nsresult rv = RegexEval(kExtensionRegex, fileName, /* aOnlyMatch = */ false,
                           regexMatch, &regexResults);
   if (NS_FAILED(rv)) {
     return FilenameType(kRegexFailure, Nothing());
   }
@@ -196,105 +204,154 @@ FilenameType nsContentSecurityUtils::Fil
   }
   if (regexMatch) {
     return FilenameType(kSuspectedUserChromeJS, Nothing());
   }
 
   return FilenameType(kOther, Nothing());
 }
 
+class EvalUsageNotificationRunnable final : public Runnable {
+ public:
+  EvalUsageNotificationRunnable(bool aIsSystemPrincipal,
+                                NS_ConvertUTF8toUTF16& aFileNameA,
+                                uint64_t aWindowID, uint32_t aLineNumber,
+                                uint32_t aColumnNumber)
+      : mozilla::Runnable("EvalUsageNotificationRunnable"),
+        mIsSystemPrincipal(aIsSystemPrincipal),
+        mFileNameA(aFileNameA),
+        mWindowID(aWindowID),
+        mLineNumber(aLineNumber),
+        mColumnNumber(aColumnNumber) {}
+
+  NS_IMETHOD Run() override {
+    nsContentSecurityUtils::NotifyEvalUsage(
+        mIsSystemPrincipal, mFileNameA, mWindowID, mLineNumber, mColumnNumber);
+    return NS_OK;
+  }
+
+  void Revoke() {}
+
+ private:
+  bool mIsSystemPrincipal;
+  NS_ConvertUTF8toUTF16 mFileNameA;
+  uint64_t mWindowID;
+  uint32_t mLineNumber;
+  uint32_t mColumnNumber;
+};
+
 /* static */
 bool nsContentSecurityUtils::IsEvalAllowed(JSContext* cx,
-                                           nsIPrincipal* aSubjectPrincipal,
+                                           bool aIsSystemPrincipal,
                                            const nsAString& aScript) {
   // This allowlist contains files that are permanently allowed to use
-  // eval()-like functions. It is supposed to be restricted to files that are
+  // eval()-like functions. It will ideally be restricted to files that are
   // exclusively used in testing contexts.
   static nsLiteralCString evalAllowlist[] = {
       // Test-only third-party library
       NS_LITERAL_CSTRING("resource://testing-common/sinon-7.2.7.js"),
       // Test-only third-party library
       NS_LITERAL_CSTRING("resource://testing-common/ajv-4.1.1.js"),
       // Test-only utility
       NS_LITERAL_CSTRING("resource://testing-common/content-task.js"),
 
+      // Tracked by Bug 1584605
+      NS_LITERAL_CSTRING("resource:///modules/translation/cld-worker.js"),
+
+      // require.js implements a script loader for workers. It uses eval
+      // to load the script; but injection is only possible in situations
+      // that you could otherwise control script that gets executed, so
+      // it is okay to allow eval() as it adds no additional attack surface.
+      // Bug 1584564 tracks requiring safe usage of require.js
+      NS_LITERAL_CSTRING("resource://gre/modules/workers/require.js"),
+
       // The Browser Toolbox/Console
       NS_LITERAL_CSTRING("debugger"),
   };
 
   // We also permit two specific idioms in eval()-like contexts. We'd like to
   // elminate these too; but there are in-the-wild Mozilla privileged extensions
   // that use them.
   static NS_NAMED_LITERAL_STRING(sAllowedEval1, "this");
   static NS_NAMED_LITERAL_STRING(sAllowedEval2,
                                  "function anonymous(\n) {\nreturn this\n}");
 
-  bool systemPrincipal = aSubjectPrincipal->IsSystemPrincipal();
-  if (systemPrincipal &&
+  if (aIsSystemPrincipal &&
       StaticPrefs::security_allow_eval_with_system_principal()) {
     MOZ_LOG(
         sCSMLog, LogLevel::Debug,
         ("Allowing eval() %s because allowing pref is "
          "enabled",
-         (systemPrincipal ? "with System Principal" : "in parent process")));
+         (aIsSystemPrincipal ? "with System Principal" : "in parent process")));
     return true;
   }
 
   if (XRE_IsE10sParentProcess() &&
       StaticPrefs::security_allow_eval_in_parent_process()) {
     MOZ_LOG(sCSMLog, LogLevel::Debug,
             ("Allowing eval() in parent process because allowing pref is "
              "enabled"));
     return true;
   }
 
-  if (!systemPrincipal && !XRE_IsE10sParentProcess()) {
+  if (!aIsSystemPrincipal && !XRE_IsE10sParentProcess()) {
     // Usage of eval we are unconcerned with.
     return true;
   }
 
-  // This preference is a file used for autoconfiguration of Firefox
-  // by administrators. It has also been (ab)used by the userChromeJS
-  // project to run legacy-style 'extensions', some of which use eval,
-  // all of which run in the System Principal context.
-  nsAutoString jsConfigPref;
-  Preferences::GetString("general.config.filename", jsConfigPref);
-  if (!jsConfigPref.IsEmpty()) {
-    MOZ_LOG(
-        sCSMLog, LogLevel::Debug,
-        ("Allowing eval() %s because of "
-         "general.config.filename",
-         (systemPrincipal ? "with System Principal" : "in parent process")));
-    return true;
-  }
+  // We only perform checks of these two preferences on the Main Thread
+  // (because a String-based preference checks is only safe on Main Thread.)
+  // The consequence of this is that if a user is using userChromeJS _and_
+  // the scripts they use start a worker and that worker uses eval - we will
+  // enter this function, skip over these pref checks that would normally cause
+  // us to allow the eval usage - and we will block it.
+  // While not ideal, we do not officially support userChromeJS, and hopefully
+  // the usage of workers and eval in workers is even lower that userChromeJS
+  // usage.
+  if (NS_IsMainThread()) {
+    // This preference is a file used for autoconfiguration of Firefox
+    // by administrators. It has also been (ab)used by the userChromeJS
+    // project to run legacy-style 'extensions', some of which use eval,
+    // all of which run in the System Principal context.
+    nsAutoString jsConfigPref;
+    Preferences::GetString("general.config.filename", jsConfigPref);
+    if (!jsConfigPref.IsEmpty()) {
+      MOZ_LOG(sCSMLog, LogLevel::Debug,
+              ("Allowing eval() %s because of "
+               "general.config.filename",
+               (aIsSystemPrincipal ? "with System Principal"
+                                   : "in parent process")));
+      return true;
+    }
 
-  // This preference is better known as userchrome.css which allows
-  // customization of the Firefox UI. Believe it or not, you can also
-  // use XBL bindings to get it to run Javascript in the same manner
-  // as userChromeJS above, so even though 99.9% of people using
-  // userchrome.css aren't doing that, we're still going to need to
-  // disable the eval() assertion for them.
-  if (Preferences::GetBool(
-          "toolkit.legacyUserProfileCustomizations.stylesheets")) {
-    MOZ_LOG(
-        sCSMLog, LogLevel::Debug,
-        ("Allowing eval() %s because of "
-         "toolkit.legacyUserProfileCustomizations.stylesheets",
-         (systemPrincipal ? "with System Principal" : "in parent process")));
-    return true;
+    // This preference is better known as userchrome.css which allows
+    // customization of the Firefox UI. Believe it or not, you can also
+    // use XBL bindings to get it to run Javascript in the same manner
+    // as userChromeJS above, so even though 99.9% of people using
+    // userchrome.css aren't doing that, we're still going to need to
+    // disable the eval() assertion for them.
+    if (Preferences::GetBool(
+            "toolkit.legacyUserProfileCustomizations.stylesheets")) {
+      MOZ_LOG(sCSMLog, LogLevel::Debug,
+              ("Allowing eval() %s because of "
+               "toolkit.legacyUserProfileCustomizations.stylesheets",
+               (aIsSystemPrincipal ? "with System Principal"
+                                   : "in parent process")));
+      return true;
+    }
   }
 
   // We permit these two common idioms to get access to the global JS object
   if (!aScript.IsEmpty() &&
       (aScript == sAllowedEval1 || aScript == sAllowedEval2)) {
     MOZ_LOG(
         sCSMLog, LogLevel::Debug,
         ("Allowing eval() %s because a key string is "
          "provided",
-         (systemPrincipal ? "with System Principal" : "in parent process")));
+         (aIsSystemPrincipal ? "with System Principal" : "in parent process")));
     return true;
   }
 
   // Check the allowlist for the provided filename. getFilename is a helper
   // function
   nsAutoCString fileName;
   uint32_t lineNumber = 0, columnNumber = 0;
   JS::AutoFilename rawScriptFilename;
@@ -313,38 +370,70 @@ bool nsContentSecurityUtils::IsEvalAllow
     fileName = std::move(fileName_);
   } else {
     fileName = NS_LITERAL_CSTRING("unknown-file");
   }
 
   NS_ConvertUTF8toUTF16 fileNameA(fileName);
   for (const nsLiteralCString& allowlistEntry : evalAllowlist) {
     if (fileName.Equals(allowlistEntry)) {
-      MOZ_LOG(
-          sCSMLog, LogLevel::Debug,
-          ("Allowing eval() %s because the containing "
-           "file is in the allowlist",
-           (systemPrincipal ? "with System Principal" : "in parent process")));
+      MOZ_LOG(sCSMLog, LogLevel::Debug,
+              ("Allowing eval() %s because the containing "
+               "file is in the allowlist",
+               (aIsSystemPrincipal ? "with System Principal"
+                                   : "in parent process")));
       return true;
     }
   }
 
+  // Send Telemetry and Log to the Console
+  uint64_t windowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
+  if (NS_IsMainThread()) {
+    nsContentSecurityUtils::NotifyEvalUsage(aIsSystemPrincipal, fileNameA,
+                                            windowID, lineNumber, columnNumber);
+  } else {
+    auto runnable = new EvalUsageNotificationRunnable(
+        aIsSystemPrincipal, fileNameA, windowID, lineNumber, columnNumber);
+    NS_DispatchToMainThread(runnable);
+  }
+
   // Log to MOZ_LOG
   MOZ_LOG(sCSMLog, LogLevel::Warning,
           ("Blocking eval() %s from file %s and script "
            "provided %s",
-           (systemPrincipal ? "with System Principal" : "in parent process"),
+           (aIsSystemPrincipal ? "with System Principal" : "in parent process"),
            fileName.get(), NS_ConvertUTF16toUTF8(aScript).get()));
 
+  // Maybe Crash
+#ifdef DEBUG
+  MOZ_CRASH_UNSAFE_PRINTF(
+      "Blocking eval() %s from file %s and script provided "
+      "%s",
+      (aIsSystemPrincipal ? "with System Principal" : "in parent process"),
+      fileName.get(), NS_ConvertUTF16toUTF8(aScript).get());
+#endif
+
+  // Do not enforce eval usage blocking on Worker threads; because this is
+  // new behavior and we want to be conservative so we don't accidently break
+  // Nightly. Bug 1584602 will enforce things.
+  return !NS_IsMainThread();
+}
+
+/* static */
+void nsContentSecurityUtils::NotifyEvalUsage(bool aIsSystemPrincipal,
+                                             NS_ConvertUTF8toUTF16& aFileNameA,
+                                             uint64_t aWindowID,
+                                             uint32_t aLineNumber,
+                                             uint32_t aColumnNumber) {
   // Send Telemetry
   Telemetry::EventID eventType =
-      systemPrincipal ? Telemetry::EventID::Security_Evalusage_Systemcontext
-                      : Telemetry::EventID::Security_Evalusage_Parentprocess;
+      aIsSystemPrincipal ? Telemetry::EventID::Security_Evalusage_Systemcontext
+                         : Telemetry::EventID::Security_Evalusage_Parentprocess;
 
-  FilenameType fileNameType = FilenameToEvalType(fileNameA);
+  FilenameType fileNameType = FilenameToEvalType(aFileNameA);
   mozilla::Maybe<nsTArray<EventExtraEntry>> extra;
   if (fileNameType.second().isSome()) {
     extra = Some<nsTArray<EventExtraEntry>>({EventExtraEntry{
         NS_LITERAL_CSTRING("fileinfo"),
         NS_ConvertUTF16toUTF8(fileNameType.second().value())}});
   } else {
     extra = Nothing();
   }
@@ -353,62 +442,50 @@ bool nsContentSecurityUtils::IsEvalAllow
     Telemetry::SetEventRecordingEnabled(NS_LITERAL_CSTRING("security"), true);
   }
   Telemetry::RecordEvent(eventType, mozilla::Some(fileNameType.first()), extra);
 
   // Report an error to console
   nsCOMPtr<nsIConsoleService> console(
       do_GetService(NS_CONSOLESERVICE_CONTRACTID));
   if (!console) {
-    return false;
+    return;
   }
   nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
   if (!error) {
-    return false;
+    return;
   }
   nsCOMPtr<nsIStringBundle> bundle;
   nsCOMPtr<nsIStringBundleService> stringService =
       mozilla::services::GetStringBundleService();
   if (!stringService) {
-    return false;
+    return;
   }
   stringService->CreateBundle(
       "chrome://global/locale/security/security.properties",
       getter_AddRefs(bundle));
   if (!bundle) {
-    return false;
+    return;
   }
   nsAutoString message;
-  AutoTArray<nsString, 1> formatStrings = {fileNameA};
+  AutoTArray<nsString, 1> formatStrings = {aFileNameA};
   nsresult rv = bundle->FormatStringFromName("RestrictBrowserEvalUsage",
                                              formatStrings, message);
   if (NS_FAILED(rv)) {
-    return false;
+    return;
   }
 
-  uint64_t windowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
-  rv = error->InitWithWindowID(message, fileNameA, EmptyString(), lineNumber,
-                               columnNumber, nsIScriptError::errorFlag,
-                               "BrowserEvalUsage", windowID,
+  rv = error->InitWithWindowID(message, aFileNameA, EmptyString(), aLineNumber,
+                               aColumnNumber, nsIScriptError::errorFlag,
+                               "BrowserEvalUsage", aWindowID,
                                true /* From chrome context */);
   if (NS_FAILED(rv)) {
-    return false;
+    return;
   }
   console->LogMessage(error);
-
-  // Maybe Crash
-#ifdef DEBUG
-  MOZ_CRASH_UNSAFE_PRINTF(
-      "Blocking eval() %s from file %s and script provided "
-      "%s",
-      (systemPrincipal ? "with System Principal" : "in parent process"),
-      fileName.get(), NS_ConvertUTF16toUTF8(aScript).get());
-#endif
-
-  return false;
 }
 
 #if defined(DEBUG)
 /* static */
 void nsContentSecurityUtils::AssertAboutPageHasCSP(Document* aDocument) {
   // We want to get to a point where all about: pages ship with a CSP. This
   // assertion ensures that we can not deploy new about: pages without a CSP.
   // Please note that any about: page should not use inline JS or inline CSS,
--- a/dom/security/nsContentSecurityUtils.h
+++ b/dom/security/nsContentSecurityUtils.h
@@ -15,17 +15,21 @@ class Document;
 }  // namespace dom
 }  // namespace mozilla
 
 typedef mozilla::Pair<nsCString, mozilla::Maybe<nsString>> FilenameType;
 
 class nsContentSecurityUtils {
  public:
   static FilenameType FilenameToEvalType(const nsString& fileName);
-  static bool IsEvalAllowed(JSContext* cx, nsIPrincipal* aSubjectPrincipal,
+  static bool IsEvalAllowed(JSContext* cx, bool aIsSystemPrincipal,
                             const nsAString& aScript);
+  static void NotifyEvalUsage(bool aIsSystemPrincipal,
+                              NS_ConvertUTF8toUTF16& aFileNameA,
+                              uint64_t aWindowID, uint32_t aLineNumber,
+                              uint32_t aColumnNumber);
 
 #if defined(DEBUG)
   static void AssertAboutPageHasCSP(mozilla::dom::Document* aDocument);
 #endif
 };
 
 #endif /* nsContentSecurityUtils_h___ */
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "RuntimeService.h"
 
 #include "nsAutoPtr.h"
+#include "nsContentSecurityUtils.h"
 #include "nsIChannel.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsICookieService.h"
 #include "mozilla/dom/Document.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIObserverService.h"
 #include "nsIPrincipal.h"
@@ -590,29 +591,34 @@ class LogViolationDetailsRunnable final 
  private:
   ~LogViolationDetailsRunnable() {}
 };
 
 bool ContentSecurityPolicyAllows(JSContext* aCx, JS::HandleValue aValue) {
   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
   worker->AssertIsOnWorkerThread();
 
+  JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, aValue));
+  if (NS_WARN_IF(!jsString)) {
+    JS_ClearPendingException(aCx);
+    return false;
+  }
+
+  nsAutoJSString scriptSample;
+  if (NS_WARN_IF(!scriptSample.init(aCx, jsString))) {
+    JS_ClearPendingException(aCx);
+    return false;
+  }
+
+  if (!nsContentSecurityUtils::IsEvalAllowed(aCx, worker->UsesSystemPrincipal(),
+                                             scriptSample)) {
+    return false;
+  }
+
   if (worker->GetReportCSPViolations()) {
-    JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, aValue));
-    if (NS_WARN_IF(!jsString)) {
-      JS_ClearPendingException(aCx);
-      return false;
-    }
-
-    nsAutoJSString scriptSample;
-    if (NS_WARN_IF(!scriptSample.init(aCx, jsString))) {
-      JS_ClearPendingException(aCx);
-      return false;
-    }
-
     nsString fileName;
     uint32_t lineNum = 0;
     uint32_t columnNum = 0;
 
     JS::AutoFilename file;
     if (JS::DescribeScriptedCaller(aCx, &file, &lineNum, &columnNum) &&
         file.get()) {
       fileName = NS_ConvertUTF8toUTF16(file.get());
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -6907,24 +6907,24 @@
 - name: security.csp.reporting.script-sample.max-length
   type: int32_t
   value: 40
   mirror: always
 
 # Allowed by default so it doesn't affect Thunderbird/SeaMonkey, but
 # not allowed for Firefox Desktop in firefox.js
 - name: security.allow_eval_with_system_principal
-  type: bool
+  type: RelaxedAtomicBool
   value: true
   mirror: always
 
 # Allowed by default so it doesn't affect Thunderbird/SeaMonkey, but
 # not allowed for Firefox Desktop in firefox.js
 - name: security.allow_eval_in_parent_process
-  type: bool
+  type: RelaxedAtomicBool
   value: true
   mirror: always
 
 # Whether strict file origin policy is in effect. "False" is traditional.
 - name: security.fileuri.strict_origin_policy
   type: RelaxedAtomicBool
   value: true
   mirror: always
--- a/toolkit/components/ctypes/tests/chrome/test_ctypes.xul
+++ b/toolkit/components/ctypes/tests/chrome/test_ctypes.xul
@@ -1,16 +1,17 @@
 <?xml version="1.0"?>
 <!-- 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/. -->
 
 <window title="DOM Worker Threads Test"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        onload="test();">
+        onload="test();"
+        onunload="onunload();">
 
   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
   <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
   <script src="chrome://mochikit/content/chrome-harness.js"/>
 
   <script type="application/javascript">
   <![CDATA[
     const {ctypes} = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
@@ -66,16 +67,18 @@
         unicodeFile.remove(false);
       } catch (e) {}
     }
 
     function test()
     {
       SimpleTest.waitForExplicitFinish();
 
+      Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+
       var dir = getCurrentDir(location.path);
       ok(dir.exists() && dir.isDirectory(), "Chrome test dir doesn't exist?!");
       setupLibs(dir);
 
       var worker = new ChromeWorker("ctypes_worker.js");
       worker.onmessage = function(event) {
         is(event.data, "Done!", "Wrong message!");
         cleanupLibs(dir);
@@ -92,16 +95,21 @@
         worker.terminate();
         cleanupLibs(dir);
         SimpleTest.finish();
       }
 
       worker.postMessage({dir: dir.path, os: Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS});
     }
 
+    function onunload()
+    {
+      Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+    }
+
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
     <p id="display"></p>
     <div id="content" style="display:none;"></div>
     <pre id="test"></pre>
   </body>
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -1703,16 +1703,18 @@ security:
       Expected values are:
         chromeuri - chrome:// file
         resourceuri - resource:// file
         singlestring - A single file or string with no slashes
         mozillaextension - An extension claiming to be from *mozilla.org
         otherextension - Another extension not from Mozilla
         suspectedUserChromeJS - A filepath ending in .uc.js
         other - Unknown
+        other-on-worker - We cannot do a regex; it is not chromeuri or resourceuri, but
+                          could be any other.
         regexfailure - Our Regex Matching code threw an error
       The fileinfo key may contain additional information about the file that caused the eval()
       depending on the above value. Resource, Chrome, and SingleString will contain the full value.
       Extensions will contain the full value; however .xpi! will be shortened to !,
       shield.mozilla.org! to s! and mozilla.org! to m!.  UserChromeJS, Other, and Regexfailure should
       have no value.
     expiry_version: "never"
     notification_emails: