
PEACH
Un cadre d’isolation des locataires
RE2.prototype.split (and String.prototype.split with an RE2 separator) runs in O(n²) time when the leftmost match is zero-width and lies ahead of the cursor. This happens with \b and \B, and with ^/$ under the m flag. Each such match makes the loop emit the single character at the cursor, step forward one character, and search again from there, which finds the same distant match again. A 200,000-character subject (~600 KB of ordinary CJK text) blocks the event loop for about 81 seconds. The built-in engine takes 1 ms. The output is also wrong: one piece per character instead of one piece per boundary.
This is the same empty-match iteration family as CVE-2026-68499 (GHSA-6hxr-mr5r-9836). That advisory named split's empty-match guard as the safe model for its fix, but that guard is the root cause here.
// lib/split.cc (1.27.0), WrappedRE2::Split
while (byteIndex < str.size && re2->regexp.Match(str, byteIndex, str.size, RE2::UNANCHORED, &groups[0], groups.size()))
{
if (match.size()) { /* split at the match */ }
else
{
// zero-width match: push ONE character at byteIndex, advance ONE character,
// regardless of where the match actually is
size_t sym_size = getUtf8CharSize(str.data[byteIndex], str.size - byteIndex);
pieces.push_back(re2::StringPiece(str.data + byteIndex, sym_size));
byteIndex += sym_size;
}
}When the empty match is at position m > byteIndex, the match position is ignored. Every iteration rescans [byteIndex, m], so a subject of n characters with one boundary at the end costs about n²/2 character steps. RE2's \b is ASCII-only, so any run of non-ASCII text (Chinese, Japanese, Cyrillic, emoji), whitespace or punctuation is a run with no boundary inside it.
const RE2 = require('re2'); // 1.27.0
const s = '中文文本测试内容'.repeat(25000) + 'a'; // 200,001 chars
console.time('re2'); s.split(new RE2('\\b')); console.timeEnd('re2'); // ~81 s
console.time('native'); s.split(/\b/u); console.timeEnd('native'); // ~1 ms
' a'.split(new RE2('\\b')); // [" ", " ", "a"] native: [" ", "a"]
'abc'.split(new RE2('$', 'm')); // ["a", "b", "c"] native: ["abc"]Measured on a clean npm install re2@1.27.0 (stock prebuilt, Node 26, x86-64 Linux):
| subject (CJK + "a") | re2 1.27.0 | built-in |
|---|---|---|
| 25,000 chars | 1.3 s | 0 ms |
| 50,000 | 5.0 s | 0 ms |
| 100,000 | 20.4 s | 0 ms |
| 200,000 | 80.9 s | 1 ms |
Time grows 4× per doubling. Other triggers: ' '.repeat(200000) + 'a' with \b (26.7 s), and 'x'.repeat(100000) + '\n' with new RE2('^', 'm') (6.9 s). | ||
| <img width="1122" height="173" alt="image" src="https://github.com/user-attachments/assets/33c6a797-11e8-4b5e-bd8f-56de1670db69" /> |
Denial of service. The call is synchronous native code, so it blocks the whole event loop, and nothing in JS (a timer, try/catch) can interrupt it. The pattern is usually fixed by the developer, so the only precondition is that an application splits attacker-controlled text with \b, \B or a multiline anchor. split(/\b/) is a common way to tokenize words, and the trigger is any long run of non-ASCII text, which is ordinary input for non-English users. A single request of about 1 MB costs minutes of CPU, and repeated requests keep a worker blocked. Bounded per request: unlike CVE-2026-68499 it terminates, and memory stays linear.
Implement ECMAScript SplitMatcher semantics. Keep p (end of the last split) and q (search start) separate. Use each match at its real position. When an empty match is at p, or any match is at the very end, advance q by one code point instead of splitting. q never moves backwards, so the loop is linear. Patch: q3_split_quadratic.patch (applies to 1.27.0; the 1.27.0 test suite passes). It also fixes the output: split(/\b/), ^/$ (m), (a)? and (a)| now match the built-in engine.
size_t p = 0, q = 0;
while (q < str.size && re2->regexp.Match(str, q, str.size, RE2::UNANCHORED, &groups[0], groups.size()))
{
size_t m = match.data() - str.data, e = m + match.size();
if (m >= str.size) break; // a match at the end never splits
if (e == p) { q = m + getUtf8CharSize(str.data[m], str.size - m); continue; } // empty match at p
pieces.push_back(re2::StringPiece(str.data + p, m - p));
if (pieces.size() >= limit) break;
pieces.insert(pieces.end(), groups.begin() + 1, groups.end());
if (pieces.size() >= limit) break;
p = q = e;
}
if (str.size && pieces.size() < limit) pieces.push_back(re2::StringPiece(str.data + p, str.size - p));Workaround: don't split untrusted text with \b, \B or multiline anchors. Use matchAll with a consuming pattern such as /\w+|\W+/g instead.
Source: NVD
Évaluation gratuite des vulnérabilités
Évaluez vos pratiques de sécurité cloud dans 9 domaines de sécurité afin d’évaluer votre niveau de risque et d’identifier les lacunes dans vos défenses.
Obtenez une démo personnalisée
"La meilleure expérience utilisateur que j’ai jamais vue, offre une visibilité totale sur les workloads cloud."
"Wiz fournit une interface unique pour voir ce qui se passe dans nos environnements cloud."
"Nous savons que si Wiz identifie quelque chose comme critique, c’est qu’il l’est réellement."