{"id":"GHSA-rgwj-5xj2-c3m3","revision":null,"records":[{"revision":"0bd485cc1d8c424dad8a53fb9302f1ca","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-rgwj-5xj2-c3m3","aliases":[],"title":"MySQL2: Unbounded zlib inflate in compressed MySQL protocol handler allows decompression-bomb DoS","description":"## Vulnerability Details\n\n**File**: `lib/compressed_protocol.js`\n**Line**: 43 (`zlib.inflate(body, (err, data) => { ... })` inside `handleCompressedPacket`)\n\n### Root Cause\nWhen a connection is created with `compress: true` (and the server advertises `CLIENT_COMPRESS`), every incoming packet is unwrapped by `handleCompressedPacket()` in `lib/compressed_protocol.js`, which calls:\n\n```js\nzlib.inflate(body, (err, data) => { ... });\n```\n\nNo options object (in particular, no `maxOutputLength`) is passed. Node's zlib convenience methods default `maxOutputLength` to `buffer.kMaxLength`, which on this platform is `Number.MAX_SAFE_INTEGER` — i.e. effectively unbounded until the process runs out of memory. The 3-byte \"length of payload before compression\" field in the compressed-packet header is read (`packet.readInt24()`) but is only used to branch on `!== 0`; it is never used to cap or validate the actual inflate output size, and the real decompressed size is determined purely by the attacker-supplied deflate stream.\n\nBecause DEFLATE can reach compression ratios over 1000:1 for crafted repetitive input, an attacker who controls (or MITMs, on a non-TLS connection) the MySQL server endpoint can send a single small compressed packet that expands to gigabytes in the client's memory — a classic decompression-bomb / \"zip bomb\" applied to MySQL's client-compression protocol.\n\n### Attack Scenario\n1. Application connects with `mysql2`/`mysql2/promise` using `compress: true` (a documented option for reducing bandwidth, commonly used for cloud/WAN DB connections).\n2. The connection target is attacker-controlled or attacker-compromised, or an attacker MITMs a non-TLS connection.\n3. Right after authentication succeeds, the malicious endpoint sends one crafted compressed packet whose deflate stream is small on the wire (hundreds of KB) but decompresses to several GB.\n4. `zlib.inflate()` starts allocating memory for the full decompressed output with no ceiling.\n5. The Node.js process's RSS grows uncontrolled until OOM-kill or crash — no query needs to be issued by the client; the malicious packet alone is enough.\n\n### Impact\nDenial of Service of the client application (process crash / OOM) — not the database itself. No authentication bypass or data exposure. Requires `compress: true` plus a malicious/compromised server or MITM position.\n\n### Vulnerable Code\n```js\nfunction handleCompressedPacket(packet) {\n  const connection = this;\n  const deflatedLength = packet.readInt24();\n  const body = packet.readBuffer();\n\n  if (deflatedLength !== 0) {\n    connection.inflateQueue.push((task) => {\n      zlib.inflate(body, (err, data) => {\n        if (err) {\n          connection._handleNetworkError(err);\n          return;\n        }\n        connection._bumpCompressedSequenceId(packet.numPackets);\n        connection._inflatedPacketsParser.execute(data);\n        task.done();\n      });\n    });\n  } else {\n    ...\n  }\n}\n```\n\n### Recommended Fix\n```js\nconst MAX_INFLATED_PACKET_SIZE = 1 * 1024 * 1024 * 1024; // e.g. 1 GiB, ideally configurable\n\nzlib.inflate(body, { maxOutputLength: MAX_INFLATED_PACKET_SIZE }, (err, data) => {\n  if (err) {\n    connection._handleNetworkError(err);\n    return;\n  }\n  ...\n});\n```\n`maxOutputLength` makes `zlib.inflate` abort with `ERR_BUFFER_TOO_LARGE` as soon as the decompressed size would exceed the cap, routing into the exact same (already-existing) `err` → `connection._handleNetworkError(err)` path, so no new error-handling logic is required.\n\n### Verification\nDynamically confirmed on v3.23.0 (HEAD) using a minimal rogue \"MySQL server\" built on node-mysql2's own server-mode helpers (`mysql.createServer`, `Packets.Handshake`, `connection.writeOk()`). The rogue server completes a real handshake advertising `CLIENT_COMPRESS`, then writes one raw compressed frame (509,604 bytes on the wire — a zlib deflate of 500 MB of zero bytes, ratio 1028.8:1) directly to the socket. A normal `mysql.createConnection({ ..., compress: true })` victim client — which never issues any query — had its RSS grow from 74.3 MB to 1115.0 MB after receiving that single packet, before erroring out with `PROTOCOL_UNEXPECTED_PACKET` once the client tried to parse the inflated zero-filled buffer as MySQL packets. The memory allocation happens unconditionally before any content validation.","state":0,"publishedAt":"2026-08-31T22:36:20+00:00","modifiedAt":"2026-09-10T03:51:14.5825989+00:00","affected":[{"ecosystem":"npm","name":"mysql2","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"3.23.1"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/sidorares/node-mysql2/security/advisories/GHSA-rgwj-5xj2-c3m3","https://github.com/sidorares/node-mysql2/commit/7c48343c95ecc80d30d6ebfa67875947b7d848d2","https://github.com/sidorares/node-mysql2","https://github.com/sidorares/node-mysql2/releases/tag/v3.23.1"],"sourceUrl":"https://github.com/advisories/GHSA-rgwj-5xj2-c3m3","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-rgwj-5xj2-c3m3","identifiers":["GHSA-rgwj-5xj2-c3m3"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-15T06:30:52.8313382+00:00","complete":true,"records":1781,"lastAttemptAt":"2026-11-15T06:30:52.8313382+00:00","lastSuccessAt":"2026-11-15T06:30:52.9739799+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.9739809+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-7de4d7559a5263003f03d9f70577ab06f92ba4178309f26fce53e22a4a959294-5e215490d55c14930215665213ddac0f30153138ec392bed5f309d985bc5e0a1","complete":true,"records":425770,"lastAttemptAt":"2026-11-15T06:30:52.1466825+00:00","lastSuccessAt":"2026-11-15T06:30:52.1389052+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.1466825+00:00","snapshotPublishedAt":"2026-11-15T06:27:22+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-5a4abd14253544b78d8c3044915082da","complete":false,"records":13821,"lastAttemptAt":"2026-11-15T06:39:25.4859854+00:00","lastSuccessAt":"2026-11-15T06:39:24.47+00:00","error":"64 identifier lookups failed; latest failures include AccessDenied (HTTP 403). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-15T06:39:24.47+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13810,"expectedIdentifiers":13874,"failedIdentifiers":64},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-15T06:30:52.8313382+00:00","complete":true,"records":1125,"lastAttemptAt":"2026-11-15T06:30:52.8313382+00:00","lastSuccessAt":"2026-11-15T06:30:52.9749685+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.9749715+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-35abf047bdfe46089a52c4c139c37942","complete":false,"records":13833,"lastAttemptAt":"2026-11-15T06:30:58.1452571+00:00","lastSuccessAt":"2026-11-15T05:33:23.837+00:00","error":"12 identifier lookups failed; latest failures include HttpError (HTTP 503). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-15T05:33:23.837+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13862,"expectedIdentifiers":13874,"failedIdentifiers":12},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-64eea06dbd27de8464d5dbce1b78cc794cc59e2d901516ae0d3ea76e624cdae0","complete":true,"records":9955,"lastAttemptAt":"2026-11-15T06:30:52.7627278+00:00","lastSuccessAt":"2026-11-14T13:57:23.1208135+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.7627278+00:00","snapshotPublishedAt":"2026-11-14T13:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-f4424d7d4861b1af1404bd15abcb83f295dcd003412a9340ca99dc8200aa148a","complete":true,"records":2014,"lastAttemptAt":"2026-11-15T06:30:52.191726+00:00","lastSuccessAt":"2026-11-13T19:57:08.2864435+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.191726+00:00","snapshotPublishedAt":"2026-11-13T19:33:10+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-012aff9b2fb349d53e2c4bace6ca57956b6f5f9d82c1dc9fdc1c9b3c738f8977","complete":true,"records":3148,"lastAttemptAt":"2026-11-15T06:30:52.7798435+00:00","lastSuccessAt":"2026-11-14T04:57:03.8279646+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.7798435+00:00","snapshotPublishedAt":"2026-11-14T03:02:50+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-878f718ccd076f540460423958c8e379b139b997b45858275573ecc4d702fe9a","complete":true,"records":232474,"lastAttemptAt":"2026-11-15T06:30:52.6600161+00:00","lastSuccessAt":"2026-11-15T05:33:06.8241102+00:00","error":null,"lastVerifiedAt":"2026-11-15T06:30:52.6600161+00:00","snapshotPublishedAt":"2026-11-15T04:33:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}