CVE-2025-43265
Overview
Background
- YARR JIT
- JavaScriptCore’s JIT-compiled regular-expression matcher.
- BMP / non-BMP / surrogate pair
- Non-BMP code points are encoded as two UTF-16 units (a surrogate pair); reading one advances the index by 2, not 1.
- firstCharacterAdditionalReadSize
- A register holding how much extra to advance the index after reading a possible surrogate pair on a failed match.
- Sentinel latching
- The removed scheme used a sentinel value and conditional moves to set the advance once; its state could desynchronize.
Root Cause Analysis
YarrJIT’s Unicode fast path has an optimization (YARR_JIT_UNICODE_CAN_INCREMENT_INDEX_FOR_NON_BMP) that, after reading a non-BMP code point encoded as a surrogate pair, advances the subject index by 2 instead of 1 on a failed match. It implemented this with a register, firstCharacterAdditionalReadSize, initialized to a sentinel (0x4) and then conditionally changed to 0 (BMP) or 1 (non-BMP) using moveConditionallyTest32/addOneConditionally against the sentinel, intended to latch the value on the first read and not change it again. That sentinel-conditional scheme was buggy: under the right sequence the additional-read-size could be left at a wrong value, so the matcher advanced the index by the wrong amount and computed an out-of-bounds subject position, reading characters past the end of the string. Since regexp match results and internal offsets derive from that read, the bug discloses adjacent internal memory (the advisory: ‘may disclose internal states of the app’).
The fix removes the sentinel dance entirely and unconditionally sets firstCharacterAdditionalReadSize to 1 for the non-BMP-optimization case (jit.move(TrustedImm32(1), ...)), deleting the fragile conditional latching in both the fast (tryReadUnicodeCharImpl) and slow (tryReadUnicodeCharSlowImpl) generators.
The restored invariant is that the surrogate-pair index advance is a fixed, correct amount rather than a stateful conditional that could desynchronize.
Attack Path
- Craft a Unicode regexp Build a /u regexp whose JIT path uses the non-BMP first-character read optimization.
- Feed surrogate-pair input Match against a subject with non-BMP characters positioned so the sentinel-conditional additional-read-size is left wrong.
- Advance index out of bounds The matcher adds the wrong additional read size and computes a subject position past the string end.
- Disclose memory Out-of-bounds character reads leak adjacent internal memory into match results in the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
tryReadUnicodeCharImpl (YarrJIT)Source/JavaScriptCore/yarr/YarrJIT.cpp |
modified | Replaces the sentinel-based conditional latching of firstCharacterAdditionalReadSize with an unconditional move of 1 for the non-BMP optimization, removing the buggy stateful advance. |
tryReadUnicodeCharSlowImpl (YarrJIT)Source/JavaScriptCore/yarr/YarrJIT.cpp |
modified | Removes the matching sentinel-conditional code in the slow path and the additionalReadSizeSentinel constant. |
Files Changed
Source/JavaScriptCore/yarr/YarrJIT.cpp
Audit Directions
- Other conditional index advancesgrep YarrJIT for moveConditionallyTest/addOneConditionally on index/read-size registers where a stateful sentinel could desync.
- Non-BMP / surrogate handlingAudit surrogate-pair read paths (backreferences, character classes) for correct fixed index advances and bounds checks.
- OOB read surfacesReview how match offsets derived from JIT reads are bounds-checked against the subject length before use.