Title: [289892] trunk
Revision
289892
Author
shvaikal...@gmail.com
Date
2022-02-16 08:58:48 -0800 (Wed, 16 Feb 2022)

Log Message

REGRESSION(r287293): EventListener::wasCreatedFromMarkup() is incorrect after replaceJSFunctionForAttributeListener()
https://bugs.webkit.org/show_bug.cgi?id=236618
<rdar://88696673>

Reviewed by Chris Dumez.

Source/WebCore:

After r287293, if an inline event handler was replaced with a JSFunction, its execution was
still disallowed by the CSP policy.

This change fixes detection of inline event handlers (ones that were created from markup)
by introducing JSEventListener::m_wasCreatedFromMarkup and unsetting it during replacement
of an attribute event listener.

Since no virtual calls are added to the hot path, the Speedometer2/Inferno-TodoMVC performance
gain is kept. Also, a virtual call is removed from JSEventListener::handleEvent(), which is nice.
`sizeof(JSEventListener)` is unchanged.

Test: http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced.html

* bindings/js/JSErrorHandler.cpp:
(WebCore::JSErrorHandler::JSErrorHandler):
* bindings/js/JSEventListener.cpp:
(WebCore::JSEventListener::JSEventListener):
(WebCore::JSEventListener::create):
(WebCore::JSEventListener::replaceJSFunctionForAttributeListener):
* bindings/js/JSEventListener.h:
(WebCore::JSEventListener::wasCreatedFromMarkup const):
(WebCore::JSEventListener::wasCreatedFromMarkup):
* bindings/js/JSLazyEventListener.cpp:
(WebCore::JSLazyEventListener::JSLazyEventListener):
* bindings/js/JSLazyEventListener.h:
* dom/EventListener.h:
(WebCore::EventListener::wasCreatedFromMarkup const): Deleted.
* dom/EventListenerMap.cpp:
(WebCore::removeFirstListenerCreatedFromMarkup):
(WebCore::copyListenersNotCreatedFromMarkupToTarget):
* dom/EventTarget.cpp:
(WebCore::EventTarget::addEventListener):
* svg/SVGElement.cpp:
(WebCore::SVGElement::removeEventListener):

LayoutTests:

* http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced-expected.txt: Added.
* http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (289891 => 289892)


--- trunk/LayoutTests/ChangeLog	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/LayoutTests/ChangeLog	2022-02-16 16:58:48 UTC (rev 289892)
@@ -1,3 +1,14 @@
+2022-02-16  Alexey Shvayka  <ashva...@apple.com>
+
+        REGRESSION(r287293): EventListener::wasCreatedFromMarkup() is incorrect after replaceJSFunctionForAttributeListener()
+        https://bugs.webkit.org/show_bug.cgi?id=236618
+        <rdar://88696673>
+
+        Reviewed by Chris Dumez.
+
+        * http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced-expected.txt: Added.
+        * http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced.html: Added.
+
 2022-02-16  Ali Juma  <aj...@chromium.org>
 
         Floating point exception in RenderListBox::numVisibleItems

Added: trunk/LayoutTests/http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced-expected.txt (0 => 289892)


--- trunk/LayoutTests/http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced-expected.txt	2022-02-16 16:58:48 UTC (rev 289892)
@@ -0,0 +1,3 @@
+CONSOLE MESSAGE: Refused to execute a script for an inline event handler because 'unsafe-inline' appears in neither the script-src directive nor the default-src directive of the Content Security Policy.
+CONSOLE MESSAGE: PASS: clicked is 1
+This test checks that if an inline handler was replaced with a JSFunction, CSP doesn't prevent it from being invoked. It passes if there is one SecurityError and 'PASS' message, with no 'FAIL' logs appearing.

Added: trunk/LayoutTests/http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced.html (0 => 289892)


--- trunk/LayoutTests/http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced.html	2022-02-16 16:58:48 UTC (rev 289892)
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<head>
+<body>
+  <script>
+    document.head.innerHTML = `<meta http-equiv="Content-Security-Policy" content="default-src 'self'">`;
+
+    if (window.testRunner)
+      testRunner.dumpAsText();
+
+    var clicked = 0;
+    var a = document.createElement('a')
+    a.setAttribute('onclick', 'throw new Error("FAIL: should be unreached!")');
+    a.click();
+
+    a._onclick_ = function() { clicked++ };
+    a.click();
+
+    if (clicked === 1) {
+      console.log(`PASS: clicked is 1`);
+    } else {
+      console.log(`FAIL: clicked was ${clicked}, should be 1`);
+    }
+  </script>
+  <p>This test checks that if an inline handler was replaced with a JSFunction, CSP doesn't prevent it from being invoked. It passes if there is one SecurityError and 'PASS' message, with no 'FAIL' logs appearing.</p>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (289891 => 289892)


--- trunk/Source/WebCore/ChangeLog	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/ChangeLog	2022-02-16 16:58:48 UTC (rev 289892)
@@ -1,3 +1,46 @@
+2022-02-16  Alexey Shvayka  <ashva...@apple.com>
+
+        REGRESSION(r287293): EventListener::wasCreatedFromMarkup() is incorrect after replaceJSFunctionForAttributeListener()
+        https://bugs.webkit.org/show_bug.cgi?id=236618
+        <rdar://88696673>
+
+        Reviewed by Chris Dumez.
+
+        After r287293, if an inline event handler was replaced with a JSFunction, its execution was
+        still disallowed by the CSP policy.
+
+        This change fixes detection of inline event handlers (ones that were created from markup)
+        by introducing JSEventListener::m_wasCreatedFromMarkup and unsetting it during replacement
+        of an attribute event listener.
+
+        Since no virtual calls are added to the hot path, the Speedometer2/Inferno-TodoMVC performance
+        gain is kept. Also, a virtual call is removed from JSEventListener::handleEvent(), which is nice.
+        `sizeof(JSEventListener)` is unchanged.
+
+        Test: http/tests/security/contentSecurityPolicy/inline-event-handler-allowed-after-being-replaced.html
+
+        * bindings/js/JSErrorHandler.cpp:
+        (WebCore::JSErrorHandler::JSErrorHandler):
+        * bindings/js/JSEventListener.cpp:
+        (WebCore::JSEventListener::JSEventListener):
+        (WebCore::JSEventListener::create):
+        (WebCore::JSEventListener::replaceJSFunctionForAttributeListener):
+        * bindings/js/JSEventListener.h:
+        (WebCore::JSEventListener::wasCreatedFromMarkup const):
+        (WebCore::JSEventListener::wasCreatedFromMarkup):
+        * bindings/js/JSLazyEventListener.cpp:
+        (WebCore::JSLazyEventListener::JSLazyEventListener):
+        * bindings/js/JSLazyEventListener.h:
+        * dom/EventListener.h:
+        (WebCore::EventListener::wasCreatedFromMarkup const): Deleted.
+        * dom/EventListenerMap.cpp:
+        (WebCore::removeFirstListenerCreatedFromMarkup):
+        (WebCore::copyListenersNotCreatedFromMarkupToTarget):
+        * dom/EventTarget.cpp:
+        (WebCore::EventTarget::addEventListener):
+        * svg/SVGElement.cpp:
+        (WebCore::SVGElement::removeEventListener):
+
 2022-02-16  Antti Koivisto  <an...@apple.com>
 
         [CSS Container Queries] Size queries on unsupported axis should evaluate to unknown

Modified: trunk/Source/WebCore/bindings/js/JSErrorHandler.cpp (289891 => 289892)


--- trunk/Source/WebCore/bindings/js/JSErrorHandler.cpp	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/bindings/js/JSErrorHandler.cpp	2022-02-16 16:58:48 UTC (rev 289892)
@@ -49,7 +49,7 @@
 using namespace JSC;
 
 inline JSErrorHandler::JSErrorHandler(JSObject& listener, JSObject& wrapper, bool isAttribute, DOMWrapperWorld& world)
-    : JSEventListener(&listener, &wrapper, isAttribute, world)
+    : JSEventListener(&listener, &wrapper, isAttribute, CreatedFromMarkup::No, world)
 {
 }
 

Modified: trunk/Source/WebCore/bindings/js/JSEventListener.cpp (289891 => 289892)


--- trunk/Source/WebCore/bindings/js/JSEventListener.cpp	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/bindings/js/JSEventListener.cpp	2022-02-16 16:58:48 UTC (rev 289892)
@@ -46,10 +46,12 @@
 namespace WebCore {
 using namespace JSC;
 
-JSEventListener::JSEventListener(JSObject* function, JSObject* wrapper, bool isAttribute, DOMWrapperWorld& isolatedWorld)
+JSEventListener::JSEventListener(JSObject* function, JSObject* wrapper, bool isAttribute, CreatedFromMarkup createdFromMarkup, DOMWrapperWorld& isolatedWorld)
     : EventListener(JSEventListenerType)
+    , m_isAttribute(isAttribute)
+    , m_wasCreatedFromMarkup(createdFromMarkup == CreatedFromMarkup::Yes)
+    , m_isInitialized(false)
     , m_wrapper(wrapper)
-    , m_isAttribute(isAttribute)
     , m_isolatedWorld(isolatedWorld)
 {
     if (function) {
@@ -63,7 +65,7 @@
 
 Ref<JSEventListener> JSEventListener::create(JSC::JSObject& listener, JSC::JSObject& wrapper, bool isAttribute, DOMWrapperWorld& world)
 {
-    return adoptRef(*new JSEventListener(&listener, &wrapper, isAttribute, world));
+    return adoptRef(*new JSEventListener(&listener, &wrapper, isAttribute, CreatedFromMarkup::No, world));
 }
 
 JSObject* JSEventListener::initializeJSFunction(ScriptExecutionContext&) const
@@ -77,6 +79,7 @@
     ASSERT(function);
     ASSERT(wrapper);
 
+    m_wasCreatedFromMarkup = false;
     m_jsFunction = Weak { function };
     if (m_isInitialized)
         ASSERT(m_wrapper.get() == wrapper);

Modified: trunk/Source/WebCore/bindings/js/JSEventListener.h (289891 => 289892)


--- trunk/Source/WebCore/bindings/js/JSEventListener.h	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/bindings/js/JSEventListener.h	2022-02-16 16:58:48 UTC (rev 289892)
@@ -45,6 +45,8 @@
     // Returns true if this event listener was created for an event handler attribute, like "onload" or "onclick".
     bool isAttribute() const final { return m_isAttribute; }
 
+    bool wasCreatedFromMarkup() const { return m_wasCreatedFromMarkup; }
+
     JSC::JSObject* ensureJSFunction(ScriptExecutionContext&) const;
     DOMWrapperWorld& isolatedWorld() const { return m_isolatedWorld; }
 
@@ -58,6 +60,10 @@
     String functionName() const;
 
     void replaceJSFunctionForAttributeListener(JSC::JSObject* function, JSC::JSObject* wrapper);
+    static bool wasCreatedFromMarkup(const EventListener& listener)
+    {
+        return is<JSEventListener>(listener) && downcast<JSEventListener>(listener).wasCreatedFromMarkup();
+    }
 
 private:
     virtual JSC::JSObject* initializeJSFunction(ScriptExecutionContext&) const;
@@ -68,16 +74,20 @@
     virtual String code() const { return String(); }
 
 protected:
-    JSEventListener(JSC::JSObject* function, JSC::JSObject* wrapper, bool isAttribute, DOMWrapperWorld&);
+    enum class CreatedFromMarkup : bool { No, Yes };
+
+    JSEventListener(JSC::JSObject* function, JSC::JSObject* wrapper, bool isAttribute, CreatedFromMarkup, DOMWrapperWorld&);
     void handleEvent(ScriptExecutionContext&, Event&) override;
     void setWrapperWhenInitializingJSFunction(JSC::VM&, JSC::JSObject* wrapper) const { m_wrapper = JSC::Weak<JSC::JSObject>(wrapper); }
 
 private:
+    bool m_isAttribute : 1;
+    bool m_wasCreatedFromMarkup : 1;
+
+    mutable bool m_isInitialized : 1;
     mutable JSC::Weak<JSC::JSObject> m_jsFunction;
     mutable JSC::Weak<JSC::JSObject> m_wrapper;
-    mutable bool m_isInitialized { false };
 
-    bool m_isAttribute;
     Ref<DOMWrapperWorld> m_isolatedWorld;
 };
 

Modified: trunk/Source/WebCore/bindings/js/JSLazyEventListener.cpp (289891 => 289892)


--- trunk/Source/WebCore/bindings/js/JSLazyEventListener.cpp	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/bindings/js/JSLazyEventListener.cpp	2022-02-16 16:58:48 UTC (rev 289892)
@@ -68,7 +68,7 @@
 }
 
 JSLazyEventListener::JSLazyEventListener(CreationArguments&& arguments, const URL& sourceURL, const TextPosition& sourcePosition)
-    : JSEventListener(nullptr, arguments.wrapper, true, mainThreadNormalWorld())
+    : JSEventListener(nullptr, arguments.wrapper, true, CreatedFromMarkup::Yes, mainThreadNormalWorld())
     , m_functionName(arguments.attributeName.localName().string())
     , m_eventParameterName(eventParameterName(arguments.shouldUseSVGEventName))
     , m_code(arguments.attributeValue)

Modified: trunk/Source/WebCore/bindings/js/JSLazyEventListener.h (289891 => 289892)


--- trunk/Source/WebCore/bindings/js/JSLazyEventListener.h	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/bindings/js/JSLazyEventListener.h	2022-02-16 16:58:48 UTC (rev 289892)
@@ -53,7 +53,6 @@
 #endif
 
     JSC::JSObject* initializeJSFunction(ScriptExecutionContext&) const final;
-    bool wasCreatedFromMarkup() const final { return true; }
 
     String m_functionName;
     const String& m_eventParameterName;

Modified: trunk/Source/WebCore/dom/EventListener.h (289891 => 289892)


--- trunk/Source/WebCore/dom/EventListener.h	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/dom/EventListener.h	2022-02-16 16:58:48 UTC (rev 289892)
@@ -52,7 +52,6 @@
     virtual ~EventListener() = default;
     virtual bool operator==(const EventListener&) const = 0;
     virtual void handleEvent(ScriptExecutionContext&, Event&) = 0;
-    virtual bool wasCreatedFromMarkup() const { return false; }
 
     virtual void visitJSFunction(JSC::AbstractSlotVisitor&) { }
     virtual void visitJSFunction(JSC::SlotVisitor&) { }

Modified: trunk/Source/WebCore/dom/EventListenerMap.cpp (289891 => 289892)


--- trunk/Source/WebCore/dom/EventListenerMap.cpp	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/dom/EventListenerMap.cpp	2022-02-16 16:58:48 UTC (rev 289892)
@@ -36,6 +36,7 @@
 #include "AddEventListenerOptions.h"
 #include "Event.h"
 #include "EventTarget.h"
+#include "JSEventListener.h"
 #include <wtf/MainThread.h>
 #include <wtf/StdLibExtras.h>
 #include <wtf/Vector.h>
@@ -191,7 +192,7 @@
 static void removeFirstListenerCreatedFromMarkup(EventListenerVector& listenerVector)
 {
     bool foundListener = listenerVector.removeFirstMatching([] (const auto& registeredListener) {
-        if (registeredListener->callback().wasCreatedFromMarkup()) {
+        if (JSEventListener::wasCreatedFromMarkup(registeredListener->callback())) {
             registeredListener->markAsRemoved();
             return true;
         }
@@ -220,7 +221,7 @@
 {
     for (auto& registeredListener : listenerVector) {
         // Event listeners created from markup have already been transfered to the shadow tree during cloning.
-        if (registeredListener->callback().wasCreatedFromMarkup())
+        if (JSEventListener::wasCreatedFromMarkup(registeredListener->callback()))
             continue;
         target->addEventListener(eventType, registeredListener->callback(), registeredListener->useCapture());
     }

Modified: trunk/Source/WebCore/dom/EventTarget.cpp (289891 => 289892)


--- trunk/Source/WebCore/dom/EventTarget.cpp	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/dom/EventTarget.cpp	2022-02-16 16:58:48 UTC (rev 289892)
@@ -90,7 +90,7 @@
     if (!passive.has_value() && Quirks::shouldMakeEventListenerPassive(*this, eventType, listener.get()))
         passive = true;
 
-    bool listenerCreatedFromScript = listener->type() == EventListener::JSEventListenerType && !listener->wasCreatedFromMarkup();
+    bool listenerCreatedFromScript = is<JSEventListener>(listener) && !downcast<JSEventListener>(listener.get()).wasCreatedFromMarkup();
 
     if (!ensureEventTargetData().eventListenerMap.add(eventType, listener.copyRef(), { options.capture, passive.value_or(false), options.once }))
         return false;

Modified: trunk/Source/WebCore/svg/SVGElement.cpp (289891 => 289892)


--- trunk/Source/WebCore/svg/SVGElement.cpp	2022-02-16 16:54:55 UTC (rev 289891)
+++ trunk/Source/WebCore/svg/SVGElement.cpp	2022-02-16 16:58:48 UTC (rev 289892)
@@ -34,6 +34,7 @@
 #include "HTMLElement.h"
 #include "HTMLNames.h"
 #include "HTMLParserIdioms.h"
+#include "JSEventListener.h"
 #include "RenderAncestorIterator.h"
 #include "RenderSVGResourceFilter.h"
 #include "RenderSVGResourceMasker.h"
@@ -462,7 +463,7 @@
             continue;
 
         // This case can only be hit for event listeners created from markup
-        ASSERT(listener.wasCreatedFromMarkup());
+        ASSERT(JSEventListener::wasCreatedFromMarkup(listener));
 
         // If the event listener 'listener' has been created from markup and has been fired before
         // then JSLazyEventListener::parseCode() has been called and m_jsFunction of that listener
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to