CVE-2025-43272
Overview
Background
- 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
- 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.
- 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.
- 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.
- 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.
- 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
Tokenizer::tokenizeSource/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 testLayoutTests/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
LayoutTests/fast/url/urlpattern-invalid-pattern.htmlSource/WebCore/Modules/url-pattern/URLPatternTokenizer.cpp
Audit Directions
- Audit every index used in URLPatternTokenizerIn 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 checksSearch the URLPattern module (URLPatternParser, URLPatternComponent) for ‘\’ escape handling and any
== length() - 1or+ 1cursor advances that assume a following codepoint exists after an escape or delimiter. - Find length()-1 underflow patternsGrep across WebCore string/URL parsers for
length() - 1andlength() - Ncomparisons 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 parsersDifferentially 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.