Released September 15, 2025

CVE-2025-43272

CVSS 6.5 severity medium CWE-119 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Overview

Impact:

Description: Processing maliciously crafted web content may lead to an unexpected Safari crash

Researchers: Big Bear

Key insight. A classic wrong-variable off-by-one: the escape end-of-input guard compared the global cursor m_index instead of the in-regex cursor regexPosition, so a pattern ending in a lone backslash slipped past validation and let the tokenizer index beyond the input buffer.

Patch Details

diff --git a/LayoutTests/fast/url/urlpattern-invalid-pattern.html b/LayoutTests/fast/url/urlpattern-invalid-pattern.html
index f81cda2fc7b7..9b6c5aff56f8 100644
--- a/LayoutTests/fast/url/urlpattern-invalid-pattern.html
+++ b/LayoutTests/fast/url/urlpattern-invalid-pattern.html
@@ -5,5 +5,6 @@
 test(() => {
   assert_throws_js(TypeError, () => { new URLPattern(new URL('https://example.org/%(')); } );
   assert_throws_js(TypeError, () => { new URLPattern(new URL('https://example.org/%((')); } );
+  assert_throws_js(TypeError, () => { new URLPattern('(\\'); } );
 }, `Test unclosed token`);
 </script>
diff --git a/Source/WebCore/Modules/url-pattern/URLPatternTokenizer.cpp b/Source/WebCore/Modules/url-pattern/URLPatternTokenizer.cpp
index 71214612cf59..c8af6a4ef216 100644
--- a/Source/WebCore/Modules/url-pattern/URLPatternTokenizer.cpp
+++ b/Source/WebCore/Modules/url-pattern/URLPatternTokenizer.cpp
@@ -193,7 +193,7 @@ ExceptionOr<Vector<Token>> Tokenizer::tokenize()
                 }
 
                 if (m_codepoint == '\\') {
-                    if (m_index == m_input.length() - 1) {
+                    if (regexPosition == m_input.length() - 1) {
                         maybeException = processTokenizingError(regexStart, m_index, "No character is provided after escape."_s);
                         hasError = true;
                         break;

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2025-43272
Bugzilla 294550
Component WebCore
Bug Class OOB
Severity medium
Commit fa85413077accf8c…
Advisory Apple Advisory

Terminology

  • URLPattern — A Web API for matching and parsing URLs against a pattern syntax that supports regex groups; its constructor tokenizes untrusted string input from JavaScript.
  • Tokenizer / m_index — URLPatternTokenizer walks the input codepoint by codepoint; m_index is the global scan cursor into m_input across the whole pattern.
  • regexPosition — A position tracked specifically while scanning inside a regex group, which is the correct cursor for reasoning about whether the current escape is the group’s / input’s last character.
  • Escape validation — A backslash must be followed by a character to escape; a trailing backslash with no following character is malformed and must raise an error before any read of the next codepoint.
  • TypeError surfacing — Tokenizer errors are returned as an ExceptionOr and thrown to JS as a TypeError; failing to set the error means malformed input is processed instead of rejected.

Root Cause Analysis

The patch touches Tokenizer::tokenize() in Source/WebCore/Modules/url-pattern/URLPatternTokenizer.cpp, the tokenizer that parses URLPattern regex-group syntax as part of the URLPattern Web API. Inside the branch that handles a backslash escape character (m_codepoint == ‘\’), the code must detect the malformed case where the escape is the final character of the input, i.e. there is no character after the backslash to escape. The vulnerable check read if (m_index == m_input.length() - 1), comparing the tokenizer’s global scan index m_index against the last-index position. The fix changes it to if (regexPosition == m_input.length() - 1), comparing the position tracked while scanning inside the regex group instead. The violated invariant is that the end-of-input / no-trailing-character check for an escape must use the cursor that actually points at the backslash within the current regex scan; m_index is the wrong cursor in this context, so the equality test fails to fire when the backslash truly is the last codepoint. When the check does not fire, tokenize() does not record the ‘No character is provided after escape.’ error and does not break, so parsing continues and the code advances past the backslash to read the (non-existent) escaped codepoint, indexing at or beyond m_input.length(). The added layout test new URLPattern('(\\') — an unterminated regex group whose last character is a lone backslash — is precisely the input where m_index and regexPosition diverge, so before the fix this input drove the tokenizer past the end of the string rather than throwing a TypeError. The fix restores the invariant that a trailing escape is diagnosed as an error (surfaced as a TypeError to JS) instead of being consumed, preventing the out-of-bounds read of the input buffer and the resulting crash.

Attack Path

1. Reach the URLPattern tokenizer

From attacker-controlled JavaScript in a page, call the URLPattern constructor, e.g. new URLPattern(’(\’) or an equivalent pattern whose regex portion ends in a backslash.

2. Enter the regex-group escape branch

The tokenizer scans into the ‘(’ regex group and reaches the trailing backslash, entering the m_codepoint == ‘\’ branch that is supposed to validate a following character exists.

3. Defeat the end-of-input check

Because the check used m_index rather than regexPosition, the condition m_index == m_input.length() - 1 evaluates false for this input, so the ‘No character is provided after escape’ error is skipped and parsing does not break.

4. Read past the buffer

The tokenizer advances to consume the escaped character that does not exist, indexing at or beyond m_input.length() and reading out of bounds of the input string, corrupting parser state or dereferencing invalid memory.

5. Crash the renderer

The out-of-bounds access produces the unexpected Safari (WebContent) crash described in the advisory; no further primitive is established by the commit.

Impact Assessment

The primitive is an out-of-bounds read of the pattern input string driven entirely from a single JavaScript constructor call, and the advisory scopes the impact to an unexpected crash. Realistically this is a controlled crash / denial of service rather than a strong corruption primitive: an OOB read one or a few elements past a heap-allocated string can leak adjacent bytes into parser decisions in theory, but nothing in the diff demonstrates a controllable read length or a write, so escalation toward RCE is not established. The bug is confined to the WebContent (renderer) process that runs the URLPattern implementation and remains behind the WebContent sandbox.

Audit Directions

Audit every index used in URLPatternTokenizer

In URLPatternTokenizer.cpp grep for m_index, regexPosition, and m_input.length() and verify each length/last-character comparison uses the cursor appropriate to its scanning context, especially other break/error branches inside tokenize().

Hunt sibling escape/EOF checks

Search the URLPattern module (URLPatternParser, URLPatternComponent) for ‘\’ escape handling and any == length() - 1 or + 1 cursor advances that assume a following codepoint exists after an escape or delimiter.

Find length()-1 underflow patterns

Grep across WebCore string/URL parsers for length() - 1 and length() - N comparisons where length could be zero or where the compared index is not the loop cursor, a recurring source of off-by-one OOB reads.

Fuzz new URL-adjacent parsers

Differentially fuzz URLPattern and related recently-added parsers with truncated escapes, unbalanced groups, and trailing backslashes to surface variants of consume-past-end behavior before validation.

Changed Functions

Function File Change Note
Tokenizer::tokenize Source/WebCore/Modules/url-pattern/URLPatternTokenizer.cpp modified In the backslash-escape handling, the end-of-input guard was fixed from comparing m_index to comparing regexPosition against m_input.length()-1, so a trailing escape is correctly diagnosed as an error instead of being consumed past the buffer end.
urlpattern-invalid-pattern test LayoutTests/fast/url/urlpattern-invalid-pattern.html modified Adds assert_throws_js(TypeError, () => new URLPattern(’(\’)) to lock in that an unclosed group ending in a lone backslash throws rather than crashing.

Files Changed

Source Files

  • Source/WebCore/Modules/url-pattern/URLPatternTokenizer.cpp

Test Files

  • LayoutTests/fast/url/urlpattern-invalid-pattern.html

Regression Test

The fix ships this proof-of-concept / regression test:

diff --git a/LayoutTests/fast/url/urlpattern-invalid-pattern.html b/LayoutTests/fast/url/urlpattern-invalid-pattern.html
index f81cda2fc7b7..9b6c5aff56f8 100644
--- a/LayoutTests/fast/url/urlpattern-invalid-pattern.html
+++ b/LayoutTests/fast/url/urlpattern-invalid-pattern.html
@@ -5,5 +5,6 @@
 test(() => {
   assert_throws_js(TypeError, () => { new URLPattern(new URL('https://example.org/%(')); } );
   assert_throws_js(TypeError, () => { new URLPattern(new URL('https://example.org/%((')); } );
+  assert_throws_js(TypeError, () => { new URLPattern('(\\'); } );
 }, `Test unclosed token`);
 </script>