Bug 1473587 - CSP Violation events should have the correct sample for inline contexts, r=jorendorff, r=ckerschb
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 16 Jul 2018 17:58:04 +0200
changeset 818907 ca44c68906d0395259fbaca282c6e662abdcf481
parent 818906 d686b90e9c64c46ce0c49ab83edff387cda40a38
child 818908 1689d9068611d8635c4b2adc8d692f57c6bb8fc5
push id116388
push userrwood@mozilla.com
push dateMon, 16 Jul 2018 19:48:57 +0000
reviewersjorendorff, ckerschb
bugs1473587
milestone63.0a1
Bug 1473587 - CSP Violation events should have the correct sample for inline contexts, r=jorendorff, r=ckerschb
caps/nsScriptSecurityManager.cpp
caps/nsScriptSecurityManager.h
dom/base/nsJSTimeoutHandler.cpp
dom/events/EventListenerManager.cpp
dom/security/nsCSPContext.cpp
dom/security/nsCSPContext.h
dom/security/test/csp/file_report_chromescript.js
dom/security/test/csp/test_report.html
dom/workers/RuntimeService.cpp
js/public/Principals.h
js/src/builtin/Eval.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/GlobalObject.h
js/src/vm/JSFunction.cpp
testing/web-platform/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini
testing/web-platform/meta/content-security-policy/unsafe-hashes/script_event_handlers_allowed.html.ini
testing/web-platform/meta/content-security-policy/unsafe-hashes/script_event_handlers_denied_missing_unsafe_hashes.html.ini
testing/web-platform/tests/content-security-policy/securitypolicyviolation/targeting.html
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -466,17 +466,18 @@ NS_IMPL_ISUPPORTS(nsScriptSecurityManage
 
 ///////////////////////////////////////////////////
 // Methods implementing nsIScriptSecurityManager //
 ///////////////////////////////////////////////////
 
 ///////////////// Security Checks /////////////////
 
 bool
-nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx)
+nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx,
+                                                              JS::HandleValue aValue)
 {
     MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
     nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal();
     nsCOMPtr<nsIContentSecurityPolicy> csp;
     nsresult rv = subjectPrincipal->GetCsp(getter_AddRefs(csp));
     NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get CSP from principal.");
 
     // don't do anything unless there's a CSP
@@ -489,22 +490,32 @@ nsScriptSecurityManager::ContentSecurity
 
     if (NS_FAILED(rv))
     {
         NS_WARNING("CSP: failed to get allowsEval");
         return true; // fail open to not break sites.
     }
 
     if (reportViolation) {
+        JS::Rooted<JSString*> jsString(cx, JS::ToString(cx, aValue));
+        if (NS_WARN_IF(!jsString)) {
+          JS_ClearPendingException(cx);
+          return false;
+        }
+
+        nsAutoJSString scriptSample;
+        if (NS_WARN_IF(!scriptSample.init(cx, jsString))) {
+          JS_ClearPendingException(cx);
+          return false;
+        }
+
+        JS::AutoFilename scriptFilename;
         nsAutoString fileName;
         unsigned lineNum = 0;
         unsigned columnNum = 0;
-        NS_NAMED_LITERAL_STRING(scriptSample, "call to eval() or related function blocked by CSP");
-
-        JS::AutoFilename scriptFilename;
         if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineNum,
                                        &columnNum)) {
             if (const char *file = scriptFilename.get()) {
                 CopyUTF8toUTF16(nsDependentCString(file), fileName);
             }
         } else {
             MOZ_ASSERT(!JS_IsExceptionPending(cx));
         }
--- a/caps/nsScriptSecurityManager.h
+++ b/caps/nsScriptSecurityManager.h
@@ -83,17 +83,17 @@ public:
 private:
 
     // GetScriptSecurityManager is the only call that can make one
     nsScriptSecurityManager();
     virtual ~nsScriptSecurityManager();
 
     // Decides, based on CSP, whether or not eval() and stuff can be executed.
     static bool
-    ContentSecurityPolicyPermitsJSAction(JSContext *cx);
+    ContentSecurityPolicyPermitsJSAction(JSContext *cx, JS::HandleValue aValue);
 
     static bool
     JSPrincipalsSubsume(JSPrincipals *first, JSPrincipals *second);
 
     nsresult
     Init();
 
     nsresult
--- a/dom/base/nsJSTimeoutHandler.cpp
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -159,17 +159,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler)
 
 static bool
-CheckCSPForEval(JSContext* aCx, nsGlobalWindowInner* aWindow, ErrorResult& aError)
+CheckCSPForEval(JSContext* aCx, nsGlobalWindowInner* aWindow,
+                const nsAString& aExpression, ErrorResult& aError)
 {
   // if CSP is enabled, and setTimeout/setInterval was called with a string,
   // disable the registration and log an error
   nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
   if (!doc) {
     // if there's no document, we don't have to do anything.
     return true;
   }
@@ -187,32 +188,28 @@ CheckCSPForEval(JSContext* aCx, nsGlobal
   bool allowsEval = true;
   bool reportViolation = false;
   aError = csp->GetAllowsEval(&reportViolation, &allowsEval);
   if (aError.Failed()) {
     return false;
   }
 
   if (reportViolation) {
-    // TODO : need actual script sample in violation report.
-    NS_NAMED_LITERAL_STRING(scriptSample,
-                            "call to eval() or related function blocked by CSP");
-
     // Get the calling location.
     uint32_t lineNum = 0;
     uint32_t columnNum = 0;
     nsAutoString fileNameString;
     if (!nsJSUtils::GetCallingLocation(aCx, fileNameString, &lineNum,
                                        &columnNum)) {
       fileNameString.AssignLiteral("unknown");
     }
 
     csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
                              nullptr, // triggering element
-                             fileNameString, scriptSample, lineNum, columnNum,
+                             fileNameString, aExpression, lineNum, columnNum,
                              EmptyString(), EmptyString());
   }
 
   return allowsEval;
 }
 
 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler()
   : mLineNo(0)
@@ -250,17 +247,17 @@ nsJSScriptTimeoutHandler::nsJSScriptTime
 {
   if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
     // This window was already closed, or never properly initialized,
     // don't let a timer be scheduled on such a window.
     aError.Throw(NS_ERROR_NOT_INITIALIZED);
     return;
   }
 
-  *aAllowEval = CheckCSPForEval(aCx, aWindow, aError);
+  *aAllowEval = CheckCSPForEval(aCx, aWindow, aExpression, aError);
   if (aError.Failed() || !*aAllowEval) {
     return;
   }
 
   Init(aCx);
 }
 
 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -853,40 +853,33 @@ EventListenerManager::SetEventHandler(ns
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
     // Perform CSP check
     nsCOMPtr<nsIContentSecurityPolicy> csp;
     rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp));
     NS_ENSURE_SUCCESS(rv, rv);
 
+    unsigned lineNum = 0;
+    unsigned columnNum = 0;
+
+    JSContext* cx = nsContentUtils::GetCurrentJSContext();
+    if (cx && !JS::DescribeScriptedCaller(cx, nullptr, &lineNum, &columnNum)) {
+      JS_ClearPendingException(cx);
+    }
+
     if (csp) {
-      // let's generate a script sample and pass it as aContent,
-      // it will not match the hash, but allows us to pass
-      // the script sample in aContent.
-      nsAutoString scriptSample, attr, tagName(NS_LITERAL_STRING("UNKNOWN"));
-      aName->ToString(attr);
-      nsCOMPtr<nsINode> domNode(do_QueryInterface(mTarget));
-      if (domNode) {
-        tagName = domNode->NodeName();
-      }
-      // build a "script sample" based on what we know about this element
-      scriptSample.Assign(attr);
-      scriptSample.AppendLiteral(" attribute on ");
-      scriptSample.Append(tagName);
-      scriptSample.AppendLiteral(" element");
-
       bool allowsInlineScript = true;
       rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
                                 EmptyString(), // aNonce
                                 true, // aParserCreated (true because attribute event handler)
                                 aElement,
-                                scriptSample,
-                                0,             // aLineNumber
-                                0,             // aColumnNumber
+                                aBody,
+                                lineNum,
+                                columnNum,
                                 &allowsInlineScript);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // return early if CSP wants us to block inline scripts
       if (!allowsInlineScript) {
         return NS_OK;
       }
     }
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -488,31 +488,24 @@ nsCSPContext::reportInlineViolation(nsCo
   nsCOMPtr<nsISupports> selfISupports(do_QueryInterface(selfICString));
 
   // use selfURI as the sourceFile
   nsAutoCString sourceFile;
   if (mSelfURI) {
     mSelfURI->GetSpec(sourceFile);
   }
 
-  nsAutoString codeSample(aContent);
-  // cap the length of the script sample
-  if (codeSample.Length() > ScriptSampleMaxLength()) {
-    codeSample.Truncate(ScriptSampleMaxLength());
-    codeSample.AppendLiteral("...");
-  }
-
   AsyncReportViolation(aTriggeringElement,
                        selfISupports,                      // aBlockedContentSource
                        mSelfURI,                           // aOriginalURI
                        aViolatedDirective,                 // aViolatedDirective
                        aViolatedPolicyIndex,               // aViolatedPolicyIndex
                        observerSubject,                    // aObserverSubject
                        NS_ConvertUTF8toUTF16(sourceFile),  // aSourceFile
-                       codeSample,                         // aScriptSample
+                       aContent,                           // aScriptSample
                        aLineNumber,                        // aLineNum
                        aColumnNumber);                     // aColumnNum
 }
 
 NS_IMETHODIMP
 nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
                               const nsAString& aNonce,
                               bool aParserCreated,
@@ -941,18 +934,29 @@ nsCSPContext::GatherSecurityPolicyViolat
     if (sourceURI) {
       nsAutoCString spec;
       sourceURI->GetSpecIgnoringRef(spec);
       aSourceFile = NS_ConvertUTF8toUTF16(spec);
     }
     aViolationEventInit.mSourceFile = aSourceFile;
   }
 
-  // sample
+  // sample, max 40 chars.
   aViolationEventInit.mSample = aScriptSample;
+  uint32_t length = aViolationEventInit.mSample.Length();
+  if (length > ScriptSampleMaxLength()) {
+    uint32_t desiredLength = ScriptSampleMaxLength();
+    // Don't cut off right before a low surrogate. Just include it.
+    if (NS_IS_LOW_SURROGATE(aViolationEventInit.mSample[desiredLength])) {
+      desiredLength++;
+    }
+    aViolationEventInit.mSample.Replace(ScriptSampleMaxLength(),
+                                        length - desiredLength,
+                                        nsContentUtils::GetLocalizedEllipsis());
+  }
 
   // disposition
   aViolationEventInit.mDisposition = mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag()
     ? mozilla::dom::SecurityPolicyViolationEventDisposition::Report
     : mozilla::dom::SecurityPolicyViolationEventDisposition::Enforce;
 
   // status-code
   uint16_t statusCode = 0;
@@ -1306,22 +1310,23 @@ class CSPReportSenderRunnable final : pu
       // 3) log to console (one per policy violation)
       // if mBlockedContentSource is not a URI, it could be a string
       nsCOMPtr<nsISupportsCString> blockedString = do_QueryInterface(mBlockedContentSource);
 
       nsCString blockedDataStr;
 
       if (blockedURI) {
         blockedURI->GetSpec(blockedDataStr);
-        if (blockedDataStr.Length() > 40) {
+        if (blockedDataStr.Length() > nsCSPContext::ScriptSampleMaxLength()) {
           bool isData = false;
           rv = blockedURI->SchemeIs("data", &isData);
-          if (NS_SUCCEEDED(rv) && isData) {
-            blockedDataStr.Truncate(40);
-            blockedDataStr.AppendASCII("…");
+          if (NS_SUCCEEDED(rv) && isData &&
+              blockedDataStr.Length() > nsCSPContext::ScriptSampleMaxLength()) {
+            blockedDataStr.Truncate(nsCSPContext::ScriptSampleMaxLength());
+            blockedDataStr.Append(NS_ConvertUTF16toUTF8(nsContentUtils::GetLocalizedEllipsis()));
           }
         }
       } else if (blockedString) {
         blockedString->GetData(blockedDataStr);
       }
 
       if (blockedDataStr.Length() > 0) {
         nsString blockedDataChar16 = NS_ConvertUTF8toUTF16(blockedDataStr);
--- a/dom/security/nsCSPContext.h
+++ b/dom/security/nsCSPContext.h
@@ -123,16 +123,21 @@ class nsCSPContext : public nsIContentSe
     void clearLoadingPrincipal() {
       mLoadingPrincipal = nullptr;
     }
 
     nsWeakPtr GetLoadingContext(){
       return mLoadingContext;
     }
 
+    static uint32_t ScriptSampleMaxLength()
+    {
+      return std::max(sScriptSampleMaxLength, 0);
+    }
+
   private:
     bool permitsInternal(CSPDirective aDir,
                          mozilla::dom::Element* aTriggeringElement,
                          nsIURI* aContentLocation,
                          nsIURI* aOriginalURI,
                          const nsAString& aNonce,
                          bool aWasRedirected,
                          bool aIsPreload,
@@ -148,21 +153,16 @@ class nsCSPContext : public nsIContentSe
                                const nsAString& aContent,
                                const nsAString& aViolatedDirective,
                                uint32_t aViolatedPolicyIndex,
                                uint32_t aLineNumber,
                                uint32_t aColumnNumber);
 
     static int32_t sScriptSampleMaxLength;
 
-    static uint32_t ScriptSampleMaxLength()
-    {
-      return std::max(sScriptSampleMaxLength, 0);
-    }
-
     static bool sViolationEventsEnabled;
 
     nsString                                   mReferrer;
     uint64_t                                   mInnerWindowID; // used for web console logging
     nsTArray<nsCSPPolicy*>                     mPolicies;
     nsCOMPtr<nsIURI>                           mSelfURI;
     nsDataHashtable<nsCStringHashKey, int16_t> mShouldLoadCache;
     nsCOMPtr<nsILoadGroup>                     mCallingChannelLoadGroup;
--- a/dom/security/test/csp/file_report_chromescript.js
+++ b/dom/security/test/csp/file_report_chromescript.js
@@ -1,9 +1,12 @@
 ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+Cu.importGlobalProperties(["TextDecoder"]);
 
 const reportURI = "http://mochi.test:8888/foo.sjs";
 
 var openingObserver = {
   observe: function(subject, topic, data) {
     // subject should be an nsURI
     if (subject.QueryInterface == undefined)
       return;
@@ -22,26 +25,25 @@ var openingObserver = {
         var reportText = "{}";
         var uploadStream = subject.QueryInterface(Ci.nsIUploadChannel).uploadStream;
 
         if (uploadStream) {
           // get the bytes from the request body
           var binstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
           binstream.setInputStream(uploadStream);
 
-          var segments = [];
-          for (var count = uploadStream.available(); count; count = uploadStream.available()) {
-            var data = binstream.readBytes(count);
-            segments.push(data);
-          }
+          let bytes = NetUtil.readInputStream(binstream);
 
-          var reportText = segments.join("");
           // rewind stream as we are supposed to - there will be an assertion later if we don't.
           uploadStream.QueryInterface(Ci.nsISeekableStream).seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+
+          let textDecoder = new TextDecoder();
+          reportText = textDecoder.decode(bytes);
         }
+
         message.report = reportText;
       } catch (e) {
         message.error = e.toString();
       }
 
       sendAsyncMessage('opening-request-completed', message);
     }
   }
--- a/dom/security/test/csp/test_report.html
+++ b/dom/security/test/csp/test_report.html
@@ -52,17 +52,17 @@ window.checkResults = function(reportObj
 
   is(cspReport["violated-directive"], "default-src", "Incorrect violated-directive");
 
   is(cspReport["original-policy"], "default-src 'none' 'report-sample'; report-uri http://mochi.test:8888/foo.sjs",
      "Incorrect original-policy");
 
   is(cspReport["source-file"], docUri, "Incorrect source-file");
 
-  is(cspReport["script-sample"], "\n    var foo = \"propEscFoo\";\n    var bar...",
+  is(cspReport["script-sample"], "\n    var foo = \"propEscFoo\";\n    var bar…",
      "Incorrect script-sample");
 
   is(cspReport["line-number"], 7, "Incorrect line-number");
 }
 
 var chromeScriptUrl = SimpleTest.getTestFileURL("file_report_chromescript.js");
 var script = SpecialPowers.loadChromeScript(chromeScriptUrl);
 
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -581,57 +581,73 @@ InterruptCallback(JSContext* aCx)
   return worker->InterruptCallback(aCx);
 }
 
 class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable
 {
   nsString mFileName;
   uint32_t mLineNum;
   uint32_t mColumnNum;
+  nsString mScriptSample;
 
 public:
   LogViolationDetailsRunnable(WorkerPrivate* aWorker,
                               const nsString& aFileName,
                               uint32_t aLineNum,
-                              uint32_t aColumnNum)
+                              uint32_t aColumnNum,
+                              const nsAString& aScriptSample)
     : WorkerMainThreadRunnable(aWorker,
                                NS_LITERAL_CSTRING("RuntimeService :: LogViolationDetails"))
     , mFileName(aFileName)
     , mLineNum(aLineNum)
     , mColumnNum(aColumnNum)
+    , mScriptSample(aScriptSample)
   {
     MOZ_ASSERT(aWorker);
   }
 
   virtual bool MainThreadRun() override;
 
 private:
   ~LogViolationDetailsRunnable() {}
 };
 
 bool
-ContentSecurityPolicyAllows(JSContext* aCx)
+ContentSecurityPolicyAllows(JSContext* aCx, JS::HandleValue aValue)
 {
   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
   worker->AssertIsOnWorkerThread();
 
   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());
     } else {
       MOZ_ASSERT(!JS_IsExceptionPending(aCx));
     }
 
     RefPtr<LogViolationDetailsRunnable> runnable =
-        new LogViolationDetailsRunnable(worker, fileName, lineNum, columnNum);
+        new LogViolationDetailsRunnable(worker, fileName, lineNum, columnNum,
+                                        scriptSample);
 
     ErrorResult rv;
     runnable->Dispatch(Killing, rv);
     if (NS_WARN_IF(rv.Failed())) {
       rv.SuppressException();
     }
   }
 
@@ -2633,22 +2649,20 @@ RuntimeService::Observe(nsISupports* aSu
 
 bool
 LogViolationDetailsRunnable::MainThreadRun()
 {
   AssertIsOnMainThread();
 
   nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP();
   if (csp) {
-    NS_NAMED_LITERAL_STRING(scriptSample,
-        "Call to eval() or related function blocked by CSP.");
     if (mWorkerPrivate->GetReportCSPViolations()) {
       csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
                                nullptr, // triggering element
-                               mFileName, scriptSample, mLineNum, mColumnNum,
+                               mFileName, mScriptSample, mLineNum, mColumnNum,
                                EmptyString(), EmptyString());
     }
   }
 
   return true;
 }
 
 NS_IMETHODIMP
--- a/js/public/Principals.h
+++ b/js/public/Principals.h
@@ -60,20 +60,20 @@ JS_DropPrincipals(JSContext* cx, JSPrinc
 // Return whether the first principal subsumes the second. The exact meaning of
 // 'subsumes' is left up to the browser. Subsumption is checked inside the JS
 // engine when determining, e.g., which stack frames to display in a backtrace.
 typedef bool
 (* JSSubsumesOp)(JSPrincipals* first, JSPrincipals* second);
 
 /*
  * Used to check if a CSP instance wants to disable eval() and friends.
- * See js_CheckCSPPermitsJSAction() in jsobj.
+ * See GlobalObject::isRuntimeCodeGenEnabled() in vm/GlobalObject.cpp.
  */
 typedef bool
-(* JSCSPEvalChecker)(JSContext* cx);
+(* JSCSPEvalChecker)(JSContext* cx, JS::HandleValue value);
 
 struct JSSecurityCallbacks {
     JSCSPEvalChecker           contentSecurityPolicyAllows;
     JSSubsumesOp               subsumes;
 };
 
 extern JS_PUBLIC_API(void)
 JS_SetSecurityCallbacks(JSContext* cx, const JSSecurityCallbacks* callbacks);
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -217,17 +217,17 @@ static bool
 EvalKernel(JSContext* cx, HandleValue v, EvalType evalType, AbstractFramePtr caller,
            HandleObject env, jsbytecode* pc, MutableHandleValue vp)
 {
     MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
     MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
     MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
     AssertInnerizedEnvironmentChain(cx, *env);
 
-    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, cx->global())) {
+    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, cx->global())) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
 
     // ES5 15.1.2.1 step 1.
     if (!v.isString()) {
         vp.set(v);
         return true;
@@ -322,17 +322,18 @@ EvalKernel(JSContext* cx, HandleValue v,
 bool
 js::DirectEvalStringFromIon(JSContext* cx,
                             HandleObject env, HandleScript callerScript,
                             HandleValue newTargetValue, HandleString str,
                             jsbytecode* pc, MutableHandleValue vp)
 {
     AssertInnerizedEnvironmentChain(cx, *env);
 
-    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, cx->global())) {
+    RootedValue v(cx, StringValue(str));
+    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, cx->global())) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
 
     // ES5 15.1.2.1 steps 2-8.
 
     RootedLinearString linearStr(cx, str->ensureLinear(cx));
     if (!linearStr)
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -674,27 +674,33 @@ GlobalObject::initSelfHostingBuiltins(JS
            InitBareBuiltinCtor(cx, global, JSProto_TypedArray) &&
            InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
            InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
            InitBareSymbolCtor(cx, global) &&
            DefineFunctions(cx, global, builtins, AsIntrinsic);
 }
 
 /* static */ bool
-GlobalObject::isRuntimeCodeGenEnabled(JSContext* cx, Handle<GlobalObject*> global)
+GlobalObject::isRuntimeCodeGenEnabled(JSContext* cx, HandleValue code,
+                                      Handle<GlobalObject*> global)
 {
     HeapSlot& v = global->getSlotRef(RUNTIME_CODEGEN_ENABLED);
     if (v.isUndefined()) {
         /*
          * If there are callbacks, make sure that the CSP callback is installed
-         * and that it permits runtime code generation, then cache the result.
+         * and that it permits runtime code generation.
          */
         JSCSPEvalChecker allows = cx->runtime()->securityCallbacks->contentSecurityPolicyAllows;
-        Value boolValue = BooleanValue(!allows || allows(cx));
-        v.set(global, HeapSlot::Slot, RUNTIME_CODEGEN_ENABLED, boolValue);
+        if (allows)
+          return allows(cx, code);
+
+        // Let's cache the result only if the contentSecurityPolicyAllows callback is not set. In
+        // this way, contentSecurityPolicyAllows callback is executed each time, with the current
+        // HandleValue code.
+        v.set(global, HeapSlot::Slot, RUNTIME_CODEGEN_ENABLED, JS::TrueValue());
     }
     return !v.isFalse();
 }
 
 /* static */ JSFunction*
 GlobalObject::createConstructor(JSContext* cx, Native ctor, JSAtom* nameArg, unsigned length,
                                 gc::AllocKind kind, const JSJitInfo* jitInfo)
 {
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -734,17 +734,18 @@ class GlobalObject : public NativeObject
 
     bool hasRegExpStatics() const;
     static RegExpStatics* getRegExpStatics(JSContext* cx,
                                            Handle<GlobalObject*> global);
     RegExpStatics* getAlreadyCreatedRegExpStatics() const;
 
     static JSObject* getOrCreateThrowTypeError(JSContext* cx, Handle<GlobalObject*> global);
 
-    static bool isRuntimeCodeGenEnabled(JSContext* cx, Handle<GlobalObject*> global);
+    static bool isRuntimeCodeGenEnabled(JSContext* cx, HandleValue code,
+                                        Handle<GlobalObject*> global);
 
     static bool getOrCreateEval(JSContext* cx, Handle<GlobalObject*> global,
                                 MutableHandleObject eval);
 
     // Infallibly test whether the given value is the eval function for this global.
     bool valueIsEval(const Value& val);
 
     // Implemented in vm/Iteration.cpp.
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -1743,23 +1743,16 @@ const JSFunctionSpec js::function_method
 
 // ES2018 draft rev 2aea8f3e617b49df06414eb062ab44fad87661d3
 // 19.2.1.1.1 CreateDynamicFunction( constructor, newTarget, kind, args )
 static bool
 CreateDynamicFunction(JSContext* cx, const CallArgs& args, GeneratorKind generatorKind,
                       FunctionAsyncKind asyncKind)
 {
     // Steps 1-5.
-    // Block this call if security callbacks forbid it.
-    Handle<GlobalObject*> global = cx->global();
-    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, global)) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_FUNCTION);
-        return false;
-    }
-
     bool isGenerator = generatorKind == GeneratorKind::Generator;
     bool isAsync = asyncKind == FunctionAsyncKind::AsyncFunction;
 
     RootedScript maybeScript(cx);
     const char* filename;
     unsigned lineno;
     bool mutedErrors;
     uint32_t pcOffset;
@@ -1849,16 +1842,24 @@ CreateDynamicFunction(JSContext* cx, con
     // The parser only accepts two byte strings.
     if (!sb.ensureTwoByteChars())
         return false;
 
     RootedString functionText(cx, sb.finishString());
     if (!functionText)
         return false;
 
+    // Block this call if security callbacks forbid it.
+    Handle<GlobalObject*> global = cx->global();
+    RootedValue v(cx, StringValue(functionText));
+    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, global)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_FUNCTION);
+        return false;
+    }
+
     /*
      * NB: (new Function) is not lexically closed by its caller, it's just an
      * anonymous function in the top-level scope that its constructor inhabits.
      * Thus 'var x = 42; f = new Function("return x"); print(f())' prints 42,
      * and so would a call to f from another top-level's script or function.
      */
     RootedAtom anonymousAtom(cx, cx->names().anonymous);
 
--- a/testing/web-platform/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini
+++ b/testing/web-platform/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini
@@ -4,20 +4,16 @@
     expected: FAIL
 
   [Inline event handlers should have a sample.]
     expected: FAIL
 
   [JavaScript URLs in iframes should have a sample.]
     expected: TIMEOUT
 
-  [eval() should not have a sample.]
-    expected: TIMEOUT
-
   [eval() should have a sample.]
     expected: TIMEOUT
 
   [setInterval() should have a sample.]
     expected: TIMEOUT
 
   [setTimeout() should have a sample.]
     expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/unsafe-hashes/script_event_handlers_allowed.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[script_event_handlers_allowed.html]
-  [Test that the inline event handler is allowed to run]
-    expected: FAIL
-
--- a/testing/web-platform/meta/content-security-policy/unsafe-hashes/script_event_handlers_denied_missing_unsafe_hashes.html.ini
+++ b/testing/web-platform/meta/content-security-policy/unsafe-hashes/script_event_handlers_denied_missing_unsafe_hashes.html.ini
@@ -1,4 +1,5 @@
 [script_event_handlers_denied_missing_unsafe_hashes.html]
+  expected: TIMEOUT
   [Test that the inline event handler is not allowed to run]
-    expected: FAIL
+    expected: NOTRUN
 
--- a/testing/web-platform/tests/content-security-policy/securitypolicyviolation/targeting.html
+++ b/testing/web-platform/tests/content-security-policy/securitypolicyviolation/targeting.html
@@ -30,33 +30,40 @@
             }))
             .then(t.step_func(e => {
                 assert_equals(e.blockedURI, "inline");
                 assert_equals(e.target, document.querySelector('#block5'));
                 return watcher.wait_for('securitypolicyviolation');
             }))
             .then(t.step_func(e => {
                 assert_equals(e.blockedURI, "inline");
-                assert_equals(e.lineNumber, 135);
-                assert_equals(e.columnNumber, 7);
-                assert_equals(e.target, document, "Disconnected elements target the document");
+                assert_equals(e.lineNumber, 118);
+                assert_equals(e.columnNumber, 4);
+                assert_equals(e.target, document, "Elements created in this document, but pushed into a same-origin frame trigger on that frame's document, not on this frame's document.");
                 return watcher.wait_for('securitypolicyviolation');
             }))
             .then(t.step_func(e => {
                 assert_equals(e.blockedURI, "inline");
-                assert_equals(e.lineNumber, 146);
-                assert_equals(e.columnNumber, 7);
-                assert_equals(e.target, document, "Elements disconnected after triggering target the document.");
+                assert_equals(e.lineNumber, 131);
+                assert_equals(e.columnNumber, 4);
+                assert_equals(e.target, document, "Elements created in this document, but pushed into a same-origin frame trigger on that frame's document, not on this frame's document.");
                 return watcher.wait_for('securitypolicyviolation');
             }))
             .then(t.step_func(e => {
                 assert_equals(e.blockedURI, "inline");
-                assert_equals(e.lineNumber, 160);
-                assert_equals(e.columnNumber, 7);
-                assert_equals(e.target, document, "Elements in DocumentFragments target the document");
+                assert_equals(e.lineNumber, 139);
+                assert_equals(e.columnNumber, 4);
+                assert_equals(e.target, document, "Inline event handlers for disconnected elements target the document.");
+                return watcher.wait_for('securitypolicyviolation');
+            }))
+            .then(t.step_func(e => {
+                assert_equals(e.blockedURI, "inline");
+                assert_equals(e.lineNumber, 0);
+                assert_equals(e.columnNumber, 0);
+                assert_equals(e.target, document, "Inline event handlers for elements disconnected after triggering target the document.");
             }))
             .then(t.step_func_done(_ => {
                 unexecuted_test.done();
             }));
     }, "Inline violations target the right element.");
 
 </script>
 <!-- Inline block with no nonce. -->
@@ -117,17 +124,16 @@
     }));
     document.querySelector('#block6').contentDocument.addEventListener('securitypolicyviolation', t.step_func_done(e => {
       events++;
       assert_equals(e.blockedURI, "inline");
       assert_equals(e.target, d);
       assert_equals(events, 2);
     }));
     document.querySelector('#block6').contentDocument.body.appendChild(d);
-    d.click();
   }, "Elements created in this document, but pushed into a same-origin frame trigger on that frame's document, not on this frame's document.");
 </script>
 
 <!-- Disconnected inline event handler -->
 <script nonce="abc">
   async_test(t => {
     var d = document.createElement("div");
     d.setAttribute("onclick", "void(0);");