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 426737 ca44c68906d0395259fbaca282c6e662abdcf481
parent 426736 d686b90e9c64c46ce0c49ab83edff387cda40a38
child 426738 1689d9068611d8635c4b2adc8d692f57c6bb8fc5
push id105309
push useramarchesini@mozilla.com
push dateMon, 16 Jul 2018 15:58:42 +0000
treeherdermozilla-inbound@ca44c68906d0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, ckerschb
bugs1473587
milestone63.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 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);");