CVE-2026-65335
Overview
Background
- RegExp capture metadata
- m_numSubpatterns and m_rareData record how many capture groups a RegExp has; match results index the capture array by them.
- Soft-invalid RegExp
- A pattern YARR fails to compile (e.g. excessive nesting); its cached metadata can become inconsistent with the compiled bytecode.
- DFG NewRegExp constant folding
- Strength reduction can bake a RegExp into optimized code; doing so for an invalid RegExp propagates the inconsistency.
Root Cause Analysis
This fixes stale/inconsistent RegExp capture metadata that could be used against a mismatched compiled pattern, and prevents the DFG from baking in an invalid RegExp. A RegExp caches capture metadata (m_numSubpatterns and, in m_rareData, capture-group names and named-group indices). finishCreation set this metadata, but the later compile paths (byteCodeCompileIfNecessary, compile, compileMatchOnly) re-parsed the pattern and only updated m_atom/m_specificPattern — with an ASSERT(m_numSubpatterns == pattern.m_numSubpatterns) that is compiled out in release — WITHOUT refreshing the capture metadata. For a pattern that is ‘soft invalid’ (e.g. too deeply nested so YARR fails), the RegExp can end up with capture metadata inconsistent with the bytecode actually compiled, so match results index captures using stale m_numSubpatterns / m_rareData against a differently-shaped compiled pattern — an out-of-bounds / stale-capture access. Compounding this, DFG strength reduction converted a NewRegExp to a frozen RegExp constant without checking validity, and RegExpCache cached even invalid RegExps.
The fix factors updateMetadataFromPattern() and calls it in ALL compile paths so metadata is consistently refreshed from the actually-compiled pattern (guarding m_rareData creation with if (!m_rareData)); adds if (!regExp->isValid()) guards so DFGStrengthReductionPhase does not convert to NewRegExp for an invalid RegExp and RegExpCache::lookupOrCreate does not cache an invalid RegExp (returns it uncached).
The restored invariant is that capture metadata always matches the compiled bytecode and invalid RegExps are neither cached nor constant-folded into optimized code. The regression test builds a ~34000-deep nested pattern that soft-fails, then drives the NewRegExp path to expose stale captures.
Attack Path
- Create a soft-invalid RegExp Construct a pathologically nested pattern that YARR fails to compile (‘soft invalid’) so its cached capture metadata can diverge from the compiled bytecode.
- Constant-fold or reuse it Get the DFG to convert a NewRegExp to a frozen constant, or the RegExpCache to hand back the invalid RegExp.
- Match with stale captures Run the RegExp so match construction uses stale m_numSubpatterns / m_rareData against the mismatched compiled pattern.
- Out-of-bounds captures The stale capture count/indices drive an out-of-bounds or type-confused capture-array access, crashing the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
RegExp::updateMetadataFromPattern (+ compile paths)Source/JavaScriptCore/runtime/RegExp.cpp |
modified | Factors metadata refresh (m_atom, m_specificPattern, m_numSubpatterns, m_rareData capture names) and calls it in finishCreation, byteCodeCompileIfNecessary, compile and compileMatchOnly so capture metadata always matches the compiled pattern (guarding m_rareData with if (!m_rareData)). |
strengthReduction NewRegExp handlingSource/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp |
modified | Skips convertToNewRegExp when !regExp->isValid(), so an invalid RegExp is not frozen into optimized code. |
RegExpCache::lookupOrCreateSource/JavaScriptCore/runtime/RegExpCache.cpp |
modified | Returns an invalid RegExp without caching it (if (!regExp->isValid()) return regExp). |
Files Changed
JSTests/stress/new-regexp-untyped-soft-invalid-stale-captures.jsSource/JavaScriptCore/dfg/DFGStrengthReductionPhase.cppSource/JavaScriptCore/runtime/RegExp.cppSource/JavaScriptCore/runtime/RegExp.hSource/JavaScriptCore/runtime/RegExpCache.cpp
Audit Directions
- Metadata refresh completenessAudit RegExp compile/recompile paths to ensure all capture metadata (m_numSubpatterns, m_rareData) is refreshed together, not partially with an ASSERT.
- Invalid-object propagationGrep for isValid() checks (or their absence) wherever a RegExp is cached, frozen, or constant-folded into DFG/FTL code.