Medium CVSS 6.5 webkit OOB 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may result in the disclosure of process memory
ComponentJSC YARR
Bug ClassOOB
Tracker308046
Fix commit2693828e8d73 (WebKit/WebKit) +39/-1
CWECWE-119, CWE-416 (Buffer bounds error, Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedArni Hardarson, Nathaniel Oh (@calysteon)
Disclosed2026-06-29

Background

YARR JIT
JavaScriptCore’s Yet Another Regex Runtime just-in-time compiler that emits native machine code for regular expressions after they run hot in the interpreter.
Backreference
A regex construct like \1 that matches the exact text previously captured by an earlier capturing group, requiring the engine to re-read and compare that captured substring.
checkedOffset vs inputPosition
checkedOffset is how many input units have been bounds-checked for the pattern from a given term onward, while inputPosition is the term’s position within the pattern; they coincide only when the term is last.
readCharacter offset
The displacement argument to YARR’s readCharacter helper that is applied relative to the current index register to compute which input character to load.
Surrogate pair (Unicode /u)
An astral code point such as U+10000 encoded as two UTF-16 units, which under the /u flag forces the wider Unicode read path in the JIT.

Root Cause Analysis

The bug lives in YarrGenerator::generateBackreference handling in Source/JavaScriptCore/yarr/YarrJIT.cpp, in the Unicode (16-bit / non-inlined) path for reading a pattern character during a backreference match. When the YARR JIT reads the character at the current back-reference position so it can compare it against the previously captured group, it called readCharacter(op.m_checkedOffset - term->inputPosition, character, patternIndex). readCharacter’s first argument is a negative-of-input-position style offset that is subtracted from the current index register to compute the address to read from; the correct value here is 0, because patternIndex already points at the exact position of the character being matched by the backreference.

The invariant that was violated is that the read offset passed to readCharacter for the backreference’s own character must be relative to patternIndex (i.e. zero additional displacement), not the term’s whole checkedOffset. m_checkedOffset is the number of input units that have been range-checked for the entire remaining pattern from this term onward, and inputPosition is the term’s position within that; the two are only equal when the backreference is the last term in the pattern. Whenever any term follows the backreference (e.g. a trailing literal like ‘c’), checkedOffset != inputPosition, so the stale expression produced a non-zero offset and the JIT read a pattern character from the wrong input index.

The fix replaces the expression with the constant 0, so the read is taken exactly at patternIndex. Because the erroneous offset could point outside the substring actually being matched (and, combined with surrogate-pair/Unicode width handling, outside the intended in-bounds region), the comparison used out-of-bounds or wrong string data, which both produced incorrect match results and could read process memory adjacent to the string buffer. The accompanying regression test regexp-backreference-unicode-offset.js confirms the trigger condition precisely: patterns like /(.)\1c/u (backreference plus trailing term) over surrogate-pair input, warmed up 500 times to force JIT compilation, whereas /(.)\1/u (no trailing term) already worked. This is why the fix restores the invariant: with offset 0 the backreference character is always read at its true position regardless of how many terms follow it.

Key insight
A single stale offset expression (op.m_checkedOffset - term->inputPosition instead of 0) meant the JIT read the backreference’s character from the wrong input index whenever any term followed the backreference; the character was already correctly addressed by patternIndex, so any additional displacement was pure error.

Attack Path

  1. Serve a crafted regex to the JIT Deliver JavaScript to the victim’s WebContent process that constructs a Unicode-flagged RegExp containing a backreference that is NOT the last term, e.g. /(.)\1c/u, so that the term’s checkedOffset differs from its inputPosition.
  2. Force YARR JIT compilation Execute the regex against input many times in a hot loop (the test uses 500 iterations) so YARR promotes it from the interpreter to the JIT, activating the vulnerable generateBackreference Unicode path.
  3. Drive the miscomputed read offset Match against strings containing surrogate-pair (astral) characters like \u{10000} so the Unicode/16-bit read path is taken and readCharacter is invoked with the stale op.m_checkedOffset - term->inputPosition displacement instead of 0.
  4. Read out-of-position / out-of-bounds character The JIT reads a pattern character from the wrong index; when the miscomputed index lands past the matched region or the string buffer, the comparison consumes memory outside the intended string data.
  5. Exfiltrate leaked bytes via match results Observe match success/failure and captured substrings across many crafted inputs to infer the values of the out-of-bounds bytes, disclosing adjacent WebContent heap memory to the page (inference: the exact leak-back channel depends on how the mismatched bytes influence observable match outcomes).

Impact Assessment

The primitive is an out-of-bounds/incorrect read of string-adjacent memory in the WebContent process, matching the advisory’s ‘disclosure of process memory’. It is a relative read whose displacement is derived from the pattern’s checkedOffset, not a fully attacker-controlled pointer, so it is closer to an information-leak/oracle than a direct write; there is no evidence in the diff of memory corruption or a write primitive. A leak like this is valuable as an ASLR/heap-layout defeat that could be chained with a separate memory-corruption bug, but on its own it is confined to reading WebContent memory and does not by itself yield code execution. It is contained within the sandboxed WebContent process.

Changed Functions

FunctionChangeNotes
YarrGenerator::generateBackreference (Unicode/non-inlined pattern-character read)
Source/JavaScriptCore/yarr/YarrJIT.cpp
modified Changed readCharacter(op.m_checkedOffset - term->inputPosition, character, patternIndex) to readCharacter(0, character, patternIndex); the backreference character must be read at patternIndex with zero extra displacement, not offset by the whole term checkedOffset.
regexp-backreference-unicode-offset.js (regression test)
JSTests/stress/regexp-backreference-unicode-offset.js
added New stress test exercising /(.)\1c/u and variants over surrogate pairs with JIT warm-up, covering the checkedOffset != inputPosition case (trailing term) that was broken and the checkedOffset == inputPosition case that already worked.

Files Changed

  • JSTests/stress/regexp-backreference-unicode-offset.js
  • Source/JavaScriptCore/yarr/YarrJIT.cpp

Audit Directions

  • Other readCharacter offset expressions in YarrJIT
    Grep YarrJIT.cpp for readCharacter( and readSurrogatePair/tryReadUnicodeChar call sites and audit every callsite that passes op.m_checkedOffset - term->inputPosition (or checkedOffset arithmetic) as the offset; confirm the displacement matches whether the index register already points at the target position.
  • checkedOffset/inputPosition arithmetic across term generators
    Search generate*/backtrack* methods in YarrJIT.cpp for the pattern m_checkedOffset - inputPosition and check each for the same last-term-only assumption, especially in the Unicode 16-bit vs inlined-latin1 branches that are easy to keep out of sync.
  • Interpreter/JIT parity for backreferences
    Compare YarrInterpreter.cpp backreference matching against the JIT to ensure both read the backreference character at the same computed index; divergence between interpreter and JIT results for /(.)\1X/u style patterns is a tell for offset bugs.

Original Bug Report

The reporter's bug is still restricted on the tracker.