CVE-2026-65341

Overview

Advisory: Apple Security Advisory

Impact:

Description: Processing maliciously crafted web content may lead to memory corruption

Researchers: Henock Habte

Attribute Value
CVE CVE-2026-65341
Bugzilla 318405
Component WebCore
Bug Class LogicError
Severity medium
Commit 34249048d66d342f…
Advisory Apple Advisory

Root Cause Analysis

The issue was addressed with improved memory handling.

Files Changed

Source Files

  • Source/WebCore/svg/animation/SVGSMILElement.cpp

Test Files

  • LayoutTests/svg/animations/smil-seek-huge-repeat-count-crash-expected.txt
  • LayoutTests/svg/animations/smil-seek-huge-repeat-count-crash.html

Patch Preview

diff --git a/LayoutTests/svg/animations/smil-seek-huge-repeat-count-crash-expected.txt b/LayoutTests/svg/animations/smil-seek-huge-repeat-count-crash-expected.txt
new file mode 100644
index 000000000000..cd68e3612acf
--- /dev/null
+++ b/LayoutTests/svg/animations/smil-seek-huge-repeat-count-crash-expected.txt
@@ -0,0 +1,3 @@
+Passes if it does not crash.
+
+
diff --git a/LayoutTests/svg/animations/smil-seek-huge-repeat-count-crash.html b/LayoutTests/svg/animations/smil-seek-huge-repeat-count-crash.html
new file mode 100644
index 000000000000..c30fa119a3e8
--- /dev/null
+++ b/LayoutTests/svg/animations/smil-seek-huge-repeat-count-crash.html
@@ -0,0 +1,14 @@
+<body>
+    <p>Passes if it does not crash.</p>
+    <svg id="svg">
+        <rect width="100" height="100" fill="green">
+            <animate attributeName="x" from="0" to="10" dur="0.0001s" repeatCount="indefinite"/>
+        </rect>
+    </svg>
+    <script>
+        if (window.testRunner)
+            testRunner.dumpAsText();
+
+        svg.setCurrentTime(400000);
+    </script>
+</body>
diff --git a/Source/WebCore/svg/animation/SVGSMILElement.cpp b/Source/WebCore/svg/animation/SVGSMILElement.cpp
index 8bd88254df52..8a9d6c1670b1 100644
--- a/Source/WebCore/svg/animation/SVGSMILElement.cpp
+++ b/Source/WebCore/svg/animation/SVGSMILElement.cpp
@@ -1049,12 +1049,13 @@ float SVGSMILElement::calculateAnimationPercentAndRepeat(SMILTime elapsed, unsig
     SMILTime activeTime = elapsed - m_intervalBegin;
     SMILTime repeatingDuration = this->repeatingDuration();
 
+    // Clamp the page-controlled repeat count to prevent overflow.
     if ((elapsed >= m_intervalEnd && !repeatingDuration.isIndefinite()) || activeTime > repeatingDuration) {
-        repeat = static_cast<unsigned>(repeatingDuration.value() / simpleDuration.value());
-        if (!fmod(repeatingDuration.value(), simpleDuration.value()))
+        repeat = clampTo<unsigned>(repeatingDuration.value() / simpleDuration.value());
+        if (repeat && !fmod(repeatingDuration.value(), simpleDuration.value()))
             --repeat;
     } else
-        repeat = static_cast<unsigned>(activeTime.value() / simpleDuration.value());
+        repeat = clampTo<unsigned>(activeTime.value() / simpleDuration.value());
 
     double percent;
     if (elapsed >= m_intervalEnd || activeTime > repeatingDuration) {
@@ -1187,16 +1188,9 @@ bool SVGSMILElement::progress(SMILTime elapsed, SVGSMILElement& firstAnimation,
         if (m_activeState == Inactive || m_activeState == Frozen)
             smilEventSender().dispatchEventSoon(*this, eventNames().endEventEvent);
 
-        if (repeat) {
-            // We intentionally dispatch repeat - 1 events here because the first repeat
-            // event (for the initial loop) is sent elsewhere during continuous animation run.
-            // If repeat == 1, no events are dispatched here.
-            for (unsigned i = 0; i < repeat - 1; ++i)
-                smilEventSender().dispatchEventSoon(*this, eventNames().repeatEventEvent);
-
-            if (m_activeState == Inactive)
-                smilEventSender().dispatchEventSoon(*this, eventNames().repeatEventEvent);
-        }
+        // Coalesce the skipped repeat iterations into a single event instead of one per interval.
+        if (repeat > 1 || (repeat && m_activeState == Inactive))
+            smilEventSender().dispatchEventSoon(*this, eventNames().repeatEventEvent);
     }
 
     m_nextProgressTime = calculateNextProgressTime(elapsed);