CVE-2026-67550
re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)
re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2 validates lastIndex against the UTF-8 byte length of a subject but uses it as a UTF-16 code-unit offset in exec, test, match, replace, and split, allowing an attacker-influenced lastIndex on a non-ASCII subject to trigger an out-of-bounds heap read and an uncatchable process crash, with limited heap information disclosure in some cases. This issue is fixed in 1.25.2.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
5.7
re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)
re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2 validates lastIndex against the UTF-8 byte length of a subject but uses it as a UTF-16 code-unit offset in exec, test, match, replace, and split, allowing an attacker-influenced lastIndex on a non-ASCII subject to trigger an out-of-bounds heap read and an uncatchable process crash, with limited heap information disclosure in some cases. This issue is fixed in 1.25.2.
- Record
CVE-2026-67550- CVSS 3.1
- 5.7 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H - Weakness
- CWE-125
- Updated
- 2026-11-02
EUVDEUVD-2026-51297
5.7
re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2 validates lastIndex against the UTF-8 byte length of a subject but uses it as a UTF-16 code-unit offset in exec, test, match, replace, and split, allowing an attacker-influenced lastIndex on a non-ASCII subject to trigger an out-of-bounds heap read and an uncatchable process crash, with limited heap information disclosure in some cases. This issue is fixed in 1.25.2.
- Record
EUVD-2026-51297- CVSS 3.1
- 5.7 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-ff84-5f28-78qj
5.7
re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)
Summary
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:200auto argLength = utf8Length(s, isolate);— UTF-8 byte countlib/addon.cc:209lastStringValue.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…
- Record
GHSA-ff84-5f28-78qj- CVSS 3.1
- 5.7 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H - Weakness
- CWE-125
- Updated
- 2026-11-02
NVD
5.7
re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2 validates lastIndex against the UTF-8 byte length of a subject but uses it as a UTF-16 code-unit offset in exec, test, match, replace, and split, allowing an attacker-influenced lastIndex on a non-ASCII subject to trigger an out-of-bounds heap read and an uncatchable process crash, with limited heap information disclosure in some cases. This issue is fixed in 1.25.2.
- Record
CVE-2026-67550- CVSS 3.1
- 5.7 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H - Weakness
- CWE-125
- Updated
- 2026-11-02
OSV
5.7
re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)
re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2 validates lastIndex against the UTF-8 byte length of a subject but uses it as a UTF-16 code-unit offset in exec, test, match, replace, and split, allowing an attacker-influenced lastIndex on a non-ASCII subject to trigger an out-of-bounds heap read and an uncatchable process crash, with limited heap information disclosure in some cases. This issue is fixed in 1.25.2.
- Record
CVE-2026-67550- CVSS 3.1
- 5.7 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H - Weakness
- CWE-125
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-67550
5.7
re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2 validates lastIndex against the UTF-8 byte length of a subject but uses it as a UTF-16 code-unit offset in exec, test, match, replace, and split, allowing an attacker-influenced lastIndex on a non-ASCII subject to trigger an out-of-bounds heap read and an uncatchable process crash, with limited heap information disclosure in some cases. This issue is fixed in 1.25.2.
- Record
DEBIAN-CVE-2026-67550- CVSS 3.1
- 5.7 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H - Updated
- 2026-11-02
OSVGHSA-ff84-5f28-78qj
5.7
re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)
Summary
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:200auto argLength = utf8Length(s, isolate);— UTF-8 byte countlib/addon.cc:209lastStringValue.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…
- Record
GHSA-ff84-5f28-78qj- CVSS 3.1
- 5.7 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H - Weakness
- CWE-125
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-67550
5.7
re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2 validates lastIndex against the UTF-8 byte length of a subject but uses it as a UTF-16 code-unit offset in exec, test, match, replace, and split, allowing an attacker-influenced lastIndex on a non-ASCII subject to trigger an out-of-bounds heap read and an uncatchable process crash, with limited heap information disclosure in some cases. This issue is fixed in 1.25.2.
- Record
UBUNTU-CVE-2026-67550- CVSS 3.1
- 5.7 Medium
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
re2npm |
through 1.25.1 | 1.25.2 |
node-re2Debian:12 |
All versions, plus listed versions 1.18.0+~cs2.13.8-11.19.2+~cs2.13.13-11.20.10+~cs2.13.13-11.20.12+~cs2.13.13-11.21.4+~cs2.13.13-1 Show 8 moreShow less1.21.4+~cs2.13.13-21.22.1+~cs1.3.5-11.23.3+~cs1.3.5-11.25.0+~cs1.6.0-11.25.0+~cs1.6.0-21.26.1+~cs1.7.0-11.26.1+~cs1.8.0-11.27.0+~cs1.8.1-1 |
None known |
node-re2Debian:13 |
All versions, plus listed versions 1.21.4+~cs2.13.13-21.22.1+~cs1.3.5-11.23.3+~cs1.3.5-11.25.0+~cs1.6.0-11.25.0+~cs1.6.0-21.26.1+~cs1.7.0-11.26.1+~cs1.8.0-11.27.0+~cs1.8.1-1 |
None known |
node-re2Debian:14 |
before 1.26.1+~cs1.7.0-1, plus listed versions 1.21.4+~cs2.13.13-21.22.1+~cs1.3.5-11.23.3+~cs1.3.5-11.25.0+~cs1.6.0-11.25.0+~cs1.6.0-2 |
1.26.1+~cs1.7.0-1 |
re2npm |
before 1.25.2 | 1.25.2 |
node-re2Ubuntu:20.04:LTS |
All versions, plus listed versions 1.10.4-11.10.5-11.10.5-1build1 |
None known |
node-re2Ubuntu:22.04:LTS |
All versions, plus listed versions 1.15.9+~cs1.8.18-11.15.9+~cs1.8.18-21.16.0+~cs2.12.8-21.17.1+~cs2.12.8-11.17.2+~cs2.13.8-11.17.3+~cs2.13.8-1 |
None known |
node-re2Ubuntu:24.04:LTS |
All versions, plus listed versions 1.18.0+~cs2.13.8-1build11.19.2+~cs2.13.13-1build11.20.10+~cs2.13.13-1 |
None known |
node-re2Ubuntu:26.10 |
All versions, plus listed versions 1.23.3+~cs1.3.5-11.25.0+~cs1.6.0-21.26.1+~cs1.7.0-1 |
None known |
node-re2Ubuntu:26.04:LTS |
All versions, plus listed versions 1.22.1+~cs1.3.5-11.22.1+~cs1.3.5-1build21.22.1+~cs1.3.5-1build31.23.3+~cs1.3.5-1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-67550 is a medium vulnerability (CVSS 5.7) in Uhop Node-re2, published July 30, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.16%, the 5th percentile. Fixed in re2 1.25.2.
References
github.com/uhop/node-re2/security/advisories/GHSA-ff84-5f28-78qj
github.com/uhop/node-re2/commit/56293de4fc0914d7bc35f92e98de25b0d9bb417d
github.com/uhop/node-re2/releases/tag/1.25.2
nvd.nist.gov/vuln/detail/CVE-2026-67550
github.com/advisories/GHSA-ff84-5f28-78qj
github.com/CVEProject/cvelistV5/tree/main/cves/2026/67xxx/CVE-2026-67550.json
Left out here: the rest of 2 source descriptions. The JSON record has them.