{"id":"CVE-2026-68499","aliases":["DEBIAN-CVE-2026-68499","EUVD-2026-51298","GHSA-6hxr-mr5r-9836","UBUNTU-CVE-2026-68499"],"status":"published","title":"re2: Global `String.prototype.match` with an empty-matchable pattern never advances → infinite loop with unbounded native memory growth (DoS)","description":"re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.","severity":{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00184,"percentile":0.08018,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-835"],"published":"2026-07-30T20:07:01Z","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-6hxr-mr5r-9836","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-68499","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-6hxr-mr5r-9836"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/68xxx/CVE-2026-68499.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-68499","tags":["advisory"]},{"url":"https://github.com/uhop/node-re2","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-68499","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-68499","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-68499","title":"re2: Global `String.prototype.match` with an empty-matchable pattern never advances → infinite loop with unbounded native memory growth (DoS)","description":"re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.","status":"published","published":"2026-07-30T20:07:01.147Z","modified":"2026-07-31T19:19:08.646Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna"}],"cwes":["CWE-835"],"updated_at":"2026-11-02T13:45:59.110885Z"}],"euvd":[{"id":"EUVD-2026-51298","description":"re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.","status":"published","published":"2026-07-30T20:07:01Z","modified":"2026-07-31T19:19:08Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T18:02:25.522444Z"}],"ghsa":[{"id":"GHSA-6hxr-mr5r-9836","title":"re2: Global `String.prototype.match` with an empty-matchable pattern never advances → infinite loop with unbounded native memory growth (DoS)","description":"## Summary\n\n`String.prototype.match` with a **global** `RE2` collects all matches in a native loop that advances the cursor by the match length. A **zero-width (empty) match** has length 0, so the cursor never advances: the same empty match is found forever and appended to an ever-growing native vector. Any pattern that can match the empty string (`a*`, `b?`, `x{0,3}`, `(a)|`, `(?:)`, …) therefore causes an infinite loop with unbounded memory growth. The call is synchronous native code, so it blocks the entire event loop and cannot be interrupted by `try/catch`, `AbortController`, `--max-old-space-size`, or timers — the process must be killed externally. This diverges from the built-in engine, where `'xxxx'.match(/a*/g)` returns a finite array.\n\n## Root cause\n\n```cpp\n// lib/match.cc:44 — global branch of WrappedRE2::Match\nwhile (re2-\u003eregexp.Match(str, byteIndex, str.size, anchor, \u0026match, 1)) {\n    groups.push_back(match);\n    byteIndex = match.data() - str.data + match.size();   // += 0 for a zero-width match\n}\n```\n\nWhen `match.size() == 0`, `byteIndex` is unchanged, so the next iteration matches the same empty position again; `groups` grows without bound. The other iteration paths already guard this: `lib/split.cc:50-55` advances by `getUtf8CharSize` on an empty match, and `exec` advances `lastIndex`. Only this global `Match` loop is missing the guard.\n\n## Proof of concept\n\n```js\nconst RE2 = require('re2');\n'x'.match(new RE2('a*', 'g'));   // never returns; grows memory until OOM\n// also: 'b?', 'x{0,3}', '(a)|', 'c*d*', '(?:)'; empty subject '' triggers it too\n```\n\nCompare with the built-in engine, which terminates:\n\n```js\n'xxxx'.match(/a*/g);   // -\u003e [\"\", \"\", \"\", \"\", \"\"]\n```\n\nMeasured on a clean `npm install re2@1.25.1` (latest), stock prebuilt binary: resident memory grew **~550 MB → 2.3 GB in ~3 seconds** at 100% CPU, and the process had to be `SIGKILL`ed externally.\n\n## Impact\n\nDenial of service. Reachable remotely and without authentication wherever an application runs a **global** `RE2` through `String.prototype.match` and either the pattern or the subject is attacker-influenced — e.g. a user-supplied regular expression, or a fixed empty-matchable pattern applied to user input. Because the loop blocks the event loop and exhausts memory in seconds, a single request can wedge a worker and, via memory exhaustion, affect the whole host.\n\n## Suggested fix\n\nMirror the empty-match handling already present in `split.cc`: when the match is zero-width, advance the cursor by one code point.\n\n```cpp\n// lib/match.cc, inside the global while-loop\ngroups.push_back(match);\nsize_t off = match.data() - str.data;\nif (match.size()) {\n    byteIndex = off + match.size();\n} else {\n    byteIndex = off + (off \u003c str.size ? getUtf8CharSize(str.data[off]) : 1);\n}\n```\n\n## Resolution\n\nFixed in re2 1.25.2.\n\nThe global match loop in `lib/match.cc` now advances the cursor by one Unicode\ncode point when a match is zero-width, so a pattern that can match the empty\nstring terminates with a finite result identical to the built-in engine\n(`'xxxx'.match(/a*/g)` returns five empty strings). This mirrors the guard\nalready present in `split`.\n\n**Remediation:** upgrade to `re2@1.25.2` or later.\n\n**Workaround** (if you cannot upgrade): do not run a global `RE2` through\n`String.prototype.match` when the pattern is attacker-influenced or can match\nthe empty string. Iterate with `matchAll`/`exec`, or use the non-global form;\nboth already advanced the cursor correctly.","status":"published","published":"2026-07-31T16:53:08Z","modified":"2026-07-31T16:53:11Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-835"],"updated_at":"2026-11-02T14:40:55.903445Z"}],"nvd":[{"id":"CVE-2026-68499","description":"re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.","status":"published","published":"2026-07-30T21:18:12.87Z","modified":"2026-10-23T22:10:00.16Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"secondary"}],"cwes":["CWE-835"],"updated_at":"2026-11-02T14:12:33.955581Z"}],"osv":[{"id":"CVE-2026-68499","title":"re2: Global `String.prototype.match` with an empty-matchable pattern never advances → infinite loop with unbounded native memory growth (DoS)","description":"re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.","status":"published","published":"2026-07-30T20:07:01.147Z","modified":"2026-10-28T11:17:16.401204511Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-835"],"updated_at":"2026-11-02T15:45:14.808935Z"},{"id":"DEBIAN-CVE-2026-68499","description":"re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.","status":"published","published":"2026-07-30T21:18:12.87Z","modified":"2026-10-24T10:00:13.028478288Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T15:46:02.094661Z"},{"id":"GHSA-6hxr-mr5r-9836","title":"re2: Global `String.prototype.match` with an empty-matchable pattern never advances → infinite loop with unbounded native memory growth (DoS)","description":"## Summary\n\n`String.prototype.match` with a **global** `RE2` collects all matches in a native loop that advances the cursor by the match length. A **zero-width (empty) match** has length 0, so the cursor never advances: the same empty match is found forever and appended to an ever-growing native vector. Any pattern that can match the empty string (`a*`, `b?`, `x{0,3}`, `(a)|`, `(?:)`, …) therefore causes an infinite loop with unbounded memory growth. The call is synchronous native code, so it blocks the entire event loop and cannot be interrupted by `try/catch`, `AbortController`, `--max-old-space-size`, or timers — the process must be killed externally. This diverges from the built-in engine, where `'xxxx'.match(/a*/g)` returns a finite array.\n\n## Root cause\n\n```cpp\n// lib/match.cc:44 — global branch of WrappedRE2::Match\nwhile (re2-\u003eregexp.Match(str, byteIndex, str.size, anchor, \u0026match, 1)) {\n    groups.push_back(match);\n    byteIndex = match.data() - str.data + match.size();   // += 0 for a zero-width match\n}\n```\n\nWhen `match.size() == 0`, `byteIndex` is unchanged, so the next iteration matches the same empty position again; `groups` grows without bound. The other iteration paths already guard this: `lib/split.cc:50-55` advances by `getUtf8CharSize` on an empty match, and `exec` advances `lastIndex`. Only this global `Match` loop is missing the guard.\n\n## Proof of concept\n\n```js\nconst RE2 = require('re2');\n'x'.match(new RE2('a*', 'g'));   // never returns; grows memory until OOM\n// also: 'b?', 'x{0,3}', '(a)|', 'c*d*', '(?:)'; empty subject '' triggers it too\n```\n\nCompare with the built-in engine, which terminates:\n\n```js\n'xxxx'.match(/a*/g);   // -\u003e [\"\", \"\", \"\", \"\", \"\"]\n```\n\nMeasured on a clean `npm install re2@1.25.1` (latest), stock prebuilt binary: resident memory grew **~550 MB → 2.3 GB in ~3 seconds** at 100% CPU, and the process had to be `SIGKILL`ed externally.\n\n## Impact\n\nDenial of service. Reachable remotely and without authentication wherever an application runs a **global** `RE2` through `String.prototype.match` and either the pattern or the subject is attacker-influenced — e.g. a user-supplied regular expression, or a fixed empty-matchable pattern applied to user input. Because the loop blocks the event loop and exhausts memory in seconds, a single request can wedge a worker and, via memory exhaustion, affect the whole host.\n\n## Suggested fix\n\nMirror the empty-match handling already present in `split.cc`: when the match is zero-width, advance the cursor by one code point.\n\n```cpp\n// lib/match.cc, inside the global while-loop\ngroups.push_back(match);\nsize_t off = match.data() - str.data;\nif (match.size()) {\n    byteIndex = off + match.size();\n} else {\n    byteIndex = off + (off \u003c str.size ? getUtf8CharSize(str.data[off]) : 1);\n}\n```\n\n## Resolution\n\nFixed in re2 1.25.2.\n\nThe global match loop in `lib/match.cc` now advances the cursor by one Unicode\ncode point when a match is zero-width, so a pattern that can match the empty\nstring terminates with a finite result identical to the built-in engine\n(`'xxxx'.match(/a*/g)` returns five empty strings). This mirrors the guard\nalready present in `split`.\n\n**Remediation:** upgrade to `re2@1.25.2` or later.\n\n**Workaround** (if you cannot upgrade): do not run a global `RE2` through\n`String.prototype.match` when the pattern is attacker-influenced or can match\nthe empty string. Iterate with `matchAll`/`exec`, or use the non-global form;\nboth already advanced the cursor correctly.","status":"published","published":"2026-07-31T16:53:08Z","modified":"2026-07-31T17:00:21.73131012Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-835"],"updated_at":"2026-11-02T15:46:39.618611Z"},{"id":"UBUNTU-CVE-2026-68499","description":"re2 provides Node.js bindings for Google's RE2 regular expression engine. Prior to 1.25.2, re2's String.prototype.match implementation with a global RE2 pattern that can match the empty string fails to advance its native matching cursor in lib/match.cc, causing an infinite loop and unbounded native memory growth that blocks the event loop and can exhaust host memory. This issue is fixed in 1.25.2.","status":"published","published":"2026-07-30T21:18:00Z","modified":"2026-10-17T12:59:20.679302329Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T16:56:57.159276Z"}]}}
