{"id":"CVE-2026-67550","aliases":["DEBIAN-CVE-2026-67550","EUVD-2026-51297","GHSA-ff84-5f28-78qj","UBUNTU-CVE-2026-67550"],"status":"published","title":"re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)","description":"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.","severity":{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00161,"percentile":0.05368,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-125"],"published":"2026-07-30T19:59:55Z","modified":"2026-10-28T11:17:16Z","fixes":[{"ecosystem":"npm","package":"re2","fixed_in":["1.25.2"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"node-re2","fixed_in":["1.26.1+~cs1.7.0-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.25.1"},{"fixed":"1.25.2"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"node-re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.18.0+~cs2.13.8-1","1.19.2+~cs2.13.13-1","1.20.10+~cs2.13.13-1","1.20.12+~cs2.13.13-1","1.21.4+~cs2.13.13-1","1.21.4+~cs2.13.13-2","1.22.1+~cs1.3.5-1","1.23.3+~cs1.3.5-1","1.25.0+~cs1.6.0-1","1.25.0+~cs1.6.0-2","1.26.1+~cs1.7.0-1","1.26.1+~cs1.8.0-1","1.27.0+~cs1.8.1-1"]},{"source":"osv","ecosystem":"Debian:13","package":"node-re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.21.4+~cs2.13.13-2","1.22.1+~cs1.3.5-1","1.23.3+~cs1.3.5-1","1.25.0+~cs1.6.0-1","1.25.0+~cs1.6.0-2","1.26.1+~cs1.7.0-1","1.26.1+~cs1.8.0-1","1.27.0+~cs1.8.1-1"]},{"source":"osv","ecosystem":"Debian:14","package":"node-re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.26.1+~cs1.7.0-1"}]}],"versions":["1.21.4+~cs2.13.13-2","1.22.1+~cs1.3.5-1","1.23.3+~cs1.3.5-1","1.25.0+~cs1.6.0-1","1.25.0+~cs1.6.0-2"]},{"source":"osv","ecosystem":"npm","package":"re2","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.25.2"}]}]},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"node-re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.10.4-1","1.10.5-1","1.10.5-1build1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"node-re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.15.9+~cs1.8.18-1","1.15.9+~cs1.8.18-2","1.16.0+~cs2.12.8-2","1.17.1+~cs2.12.8-1","1.17.2+~cs2.13.8-1","1.17.3+~cs2.13.8-1"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"node-re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.18.0+~cs2.13.8-1build1","1.19.2+~cs2.13.13-1build1","1.20.10+~cs2.13.13-1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"node-re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.23.3+~cs1.3.5-1","1.25.0+~cs1.6.0-2","1.26.1+~cs1.7.0-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"node-re2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.22.1+~cs1.3.5-1","1.22.1+~cs1.3.5-1build2","1.22.1+~cs1.3.5-1build3","1.23.3+~cs1.3.5-1"]}],"products":[{"vendor":"uhop","product":"node-re2"}],"references":[{"url":"https://github.com/uhop/node-re2/security/advisories/GHSA-ff84-5f28-78qj","tags":["x_refsource_CONFIRM","exploit","advisory","web","report"]},{"url":"https://github.com/uhop/node-re2/commit/56293de4fc0914d7bc35f92e98de25b0d9bb417d","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/uhop/node-re2/releases/tag/1.25.2","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-67550","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-ff84-5f28-78qj"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/67xxx/CVE-2026-67550.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-67550","tags":["advisory"]},{"url":"https://github.com/uhop/node-re2","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-67550","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-67550","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-67550","title":"re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)","description":"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.","status":"published","published":"2026-07-30T19:59:55.916Z","modified":"2026-07-31T23:07:18.352Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium","assessor":"cna"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T14:20:06.562031Z"}],"euvd":[{"id":"EUVD-2026-51297","description":"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.","status":"published","published":"2026-07-30T19:59:55Z","modified":"2026-07-31T23:07:18Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T18:02:25.159179Z"}],"ghsa":[{"id":"GHSA-ff84-5f28-78qj","title":"re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)","description":"## Summary\n\n`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).\n\n## Root cause\n\nThe subject wrapper stores the UTF-8 **byte** length in `StrVal::length`:\n\n- `lib/addon.cc:200` `auto argLength = utf8Length(s, isolate);` — UTF-8 **byte** count\n- `lib/addon.cc:209` `lastStringValue.reset(buffer, argSize, argLength, startFrom, false, isAscii);`\n\n`setIndex` then validates the (UTF-16) `lastIndex` against that byte length and walks the buffer by character count:\n\n```cpp\n// lib/addon.cc:229\nvoid StrVal::setIndex(size_t newIndex) {\n    isValidIndex = newIndex \u003c= length;   // length == UTF-8 BYTE length, not UTF-16 length\n    if (!isValidIndex) { index = newIndex; byteIndex = 0; return; }\n    ...\n    // addon.cc:263\n    byteIndex = index \u003c newIndex\n        ? getUtf16PositionByCounter(data, byteIndex, newIndex - index)\n        : getUtf16PositionByCounter(data, 0, newIndex);\n    index = newIndex;\n}\n```\n\n`getUtf16PositionByCounter` reads `data[from]` and advances by the UTF-8 char size with **no check of `from` against the buffer size**:\n\n```cpp\n// lib/wrapped_re2.h:264\ninline size_t getUtf16PositionByCounter(const char *data, size_t from, size_t n) {\n    for (; n \u003e 0; --n) {\n        size_t s = getUtf8CharSize(data[from]);   // \u003c-- OOB read once `from` passes the buffer end\n        from += s;\n        if (s == 4 \u0026\u0026 n \u003e= 2) --n;\n    }\n    return from;\n}\n```\n\n`lastIndex` is user-settable to any positive integer (capped only at `\u003e= 0`, no upper bound):\n\n```cpp\n// lib/accessors.cc:166\nNAN_SETTER(WrappedRE2::SetLastIndex) {\n    ...\n    int n = value-\u003eNumberValue(...).FromMaybe(0);\n    re2-\u003elastIndex = n \u003c= 0 ? 0 : n;   // no upper bound relative to the subject\n}\n```\n\nFor 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`.\n\n## Proof of concept\n\n**Minimal (AddressSanitizer, deterministic OOB read):**\n\n```js\nconst RE2 = require('re2');\nconst re = new RE2('a', 'y');   // sticky; 'g' also works\nre.lastIndex = 3;               // 3 \u003c= byteLen(4) passes the guard; only 2 real chars exist\nre.exec('éé');                  // U+00E9 = 2 bytes each\n```\n\nBuilt with `-fsanitize=address`, this aborts with:\n\n```\nERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1\n  #0 getUtf16PositionByCounter   wrapped_re2.h:268\n  #1 StrVal::reset               addon.cc:277\n  #2 WrappedRE2::prepareArgument addon.cc:209\n  #3 WrappedRE2::Exec            exec.cc:17\n```\n\nThe overflowed region is the subject buffer allocated by `node::Buffer::New` at `addon.cc:205`.\n\n**Real-world impact on the shipped prebuilt binary (no ASAN) — uncatchable crash:**\n\n```js\nconst RE2 = require('re2');\nconst s = '中'.repeat(40000000);        // UTF-16 length 40M, UTF-8 bytes 120M\nconst re = new RE2('a', 'y');\nre.lastIndex = Buffer.byteLength(s) - 1; // passes the byte-length guard, far exceeds real char count\nre.exec(s);                              // walks into unmapped memory -\u003e SIGSEGV (exit 139)\n```\n\n`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 re2@1.25.1` (latest): stock prebuilt → SIGSEGV; ASAN build → the heap-buffer-overflow read above.\n\n## Impact\n\n- **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).\n- **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.\n\nThis 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.\n\n## Suggested fix\n\nTwo independent hardenings; either closes the crash, both is safest:\n\n1. **Bound the walk** so it can never read past the buffer:\n\n```cpp\ninline size_t getUtf16PositionByCounter(const char *data, size_t size, size_t from, size_t n) {\n    for (; n \u003e 0 \u0026\u0026 from \u003c size; --n) {\n        size_t s = getUtf8CharSize(data[from]);\n        from += s;\n        if (s == 4 \u0026\u0026 n \u003e= 2) --n;\n    }\n    return from \u003e size ? size : from;\n}\n```\n(thread `size` through the two call sites in `StrVal::setIndex`).\n\n2. **Validate `lastIndex` against the true UTF-16 length**, not the UTF-8 byte length — e.g. store `s-\u003eLength()` (UTF-16 units) as the value compared in `isValidIndex = newIndex \u003c= \u003cutf16Length\u003e`, so an out-of-range `lastIndex` takes the existing `!isValidIndex` early-return path.\n\n## Resolution\n\nFixed in re2 1.25.2.\n\n`lastIndex` is now validated against the subject's UTF-16 length instead of its\nUTF-8 byte length (`lib/addon.cc`), so an out-of-range `lastIndex` is rejected\nbefore the buffer is walked. As defense in depth, the code-unit walk\n(`getUtf16PositionByCounter` in `lib/wrapped_re2.h`) is now bounded by the\nbuffer size and can no longer read past the end.\n\n**Remediation:** upgrade to `re2@1.25.2` or later.\n\n**Workaround** (if you cannot upgrade): do not assign `lastIndex` from untrusted\ninput, or clamp it to the subject's string length (`str.length`) before calling\n`exec`/`test`/`match`/`replace`/`split` on a non-ASCII subject.","status":"published","published":"2026-07-31T16:53:16Z","modified":"2026-07-31T16:53:16Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T14:40:55.903445Z"}],"nvd":[{"id":"CVE-2026-67550","description":"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.","status":"published","published":"2026-07-30T20:18:15.03Z","modified":"2026-10-23T22:10:00.16Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium","assessor":"secondary"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T14:12:33.955581Z"}],"osv":[{"id":"CVE-2026-67550","title":"re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)","description":"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.","status":"published","published":"2026-07-30T19:59:55.916Z","modified":"2026-10-28T11:17:16.040103385Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T15:45:14.46177Z"},{"id":"DEBIAN-CVE-2026-67550","description":"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.","status":"published","published":"2026-07-30T20:18:15.03Z","modified":"2026-10-24T10:01:16.979733293Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T15:46:01.68444Z"},{"id":"GHSA-ff84-5f28-78qj","title":"re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex` on a non-ASCII subject → uncatchable process crash (DoS)","description":"## Summary\n\n`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).\n\n## Root cause\n\nThe subject wrapper stores the UTF-8 **byte** length in `StrVal::length`:\n\n- `lib/addon.cc:200` `auto argLength = utf8Length(s, isolate);` — UTF-8 **byte** count\n- `lib/addon.cc:209` `lastStringValue.reset(buffer, argSize, argLength, startFrom, false, isAscii);`\n\n`setIndex` then validates the (UTF-16) `lastIndex` against that byte length and walks the buffer by character count:\n\n```cpp\n// lib/addon.cc:229\nvoid StrVal::setIndex(size_t newIndex) {\n    isValidIndex = newIndex \u003c= length;   // length == UTF-8 BYTE length, not UTF-16 length\n    if (!isValidIndex) { index = newIndex; byteIndex = 0; return; }\n    ...\n    // addon.cc:263\n    byteIndex = index \u003c newIndex\n        ? getUtf16PositionByCounter(data, byteIndex, newIndex - index)\n        : getUtf16PositionByCounter(data, 0, newIndex);\n    index = newIndex;\n}\n```\n\n`getUtf16PositionByCounter` reads `data[from]` and advances by the UTF-8 char size with **no check of `from` against the buffer size**:\n\n```cpp\n// lib/wrapped_re2.h:264\ninline size_t getUtf16PositionByCounter(const char *data, size_t from, size_t n) {\n    for (; n \u003e 0; --n) {\n        size_t s = getUtf8CharSize(data[from]);   // \u003c-- OOB read once `from` passes the buffer end\n        from += s;\n        if (s == 4 \u0026\u0026 n \u003e= 2) --n;\n    }\n    return from;\n}\n```\n\n`lastIndex` is user-settable to any positive integer (capped only at `\u003e= 0`, no upper bound):\n\n```cpp\n// lib/accessors.cc:166\nNAN_SETTER(WrappedRE2::SetLastIndex) {\n    ...\n    int n = value-\u003eNumberValue(...).FromMaybe(0);\n    re2-\u003elastIndex = n \u003c= 0 ? 0 : n;   // no upper bound relative to the subject\n}\n```\n\nFor 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`.\n\n## Proof of concept\n\n**Minimal (AddressSanitizer, deterministic OOB read):**\n\n```js\nconst RE2 = require('re2');\nconst re = new RE2('a', 'y');   // sticky; 'g' also works\nre.lastIndex = 3;               // 3 \u003c= byteLen(4) passes the guard; only 2 real chars exist\nre.exec('éé');                  // U+00E9 = 2 bytes each\n```\n\nBuilt with `-fsanitize=address`, this aborts with:\n\n```\nERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1\n  #0 getUtf16PositionByCounter   wrapped_re2.h:268\n  #1 StrVal::reset               addon.cc:277\n  #2 WrappedRE2::prepareArgument addon.cc:209\n  #3 WrappedRE2::Exec            exec.cc:17\n```\n\nThe overflowed region is the subject buffer allocated by `node::Buffer::New` at `addon.cc:205`.\n\n**Real-world impact on the shipped prebuilt binary (no ASAN) — uncatchable crash:**\n\n```js\nconst RE2 = require('re2');\nconst s = '中'.repeat(40000000);        // UTF-16 length 40M, UTF-8 bytes 120M\nconst re = new RE2('a', 'y');\nre.lastIndex = Buffer.byteLength(s) - 1; // passes the byte-length guard, far exceeds real char count\nre.exec(s);                              // walks into unmapped memory -\u003e SIGSEGV (exit 139)\n```\n\n`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 re2@1.25.1` (latest): stock prebuilt → SIGSEGV; ASAN build → the heap-buffer-overflow read above.\n\n## Impact\n\n- **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).\n- **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.\n\nThis 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.\n\n## Suggested fix\n\nTwo independent hardenings; either closes the crash, both is safest:\n\n1. **Bound the walk** so it can never read past the buffer:\n\n```cpp\ninline size_t getUtf16PositionByCounter(const char *data, size_t size, size_t from, size_t n) {\n    for (; n \u003e 0 \u0026\u0026 from \u003c size; --n) {\n        size_t s = getUtf8CharSize(data[from]);\n        from += s;\n        if (s == 4 \u0026\u0026 n \u003e= 2) --n;\n    }\n    return from \u003e size ? size : from;\n}\n```\n(thread `size` through the two call sites in `StrVal::setIndex`).\n\n2. **Validate `lastIndex` against the true UTF-16 length**, not the UTF-8 byte length — e.g. store `s-\u003eLength()` (UTF-16 units) as the value compared in `isValidIndex = newIndex \u003c= \u003cutf16Length\u003e`, so an out-of-range `lastIndex` takes the existing `!isValidIndex` early-return path.\n\n## Resolution\n\nFixed in re2 1.25.2.\n\n`lastIndex` is now validated against the subject's UTF-16 length instead of its\nUTF-8 byte length (`lib/addon.cc`), so an out-of-range `lastIndex` is rejected\nbefore the buffer is walked. As defense in depth, the code-unit walk\n(`getUtf16PositionByCounter` in `lib/wrapped_re2.h`) is now bounded by the\nbuffer size and can no longer read past the end.\n\n**Remediation:** upgrade to `re2@1.25.2` or later.\n\n**Workaround** (if you cannot upgrade): do not assign `lastIndex` from untrusted\ninput, or clamp it to the subject's string length (`str.length`) before calling\n`exec`/`test`/`match`/`replace`/`split` on a non-ASCII subject.","status":"published","published":"2026-07-31T16:53:16Z","modified":"2026-07-31T17:00:20.236795716Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T15:46:46.39441Z"},{"id":"UBUNTU-CVE-2026-67550","description":"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.","status":"published","published":"2026-07-30T20:18:00Z","modified":"2026-10-17T13:00:38.685353348Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T16:56:53.710209Z"}]}}
