GHSA-m9c9-mc2h-9wjw: @lodestar/reqresp Lodestar Snappy Checksum Issue Vulnerability

LOW
CWE-354
Published
Advisory Summary

GHSA-m9c9-mc2h-9wjw is a LOW lodestar snappy checksum issue vulnerability (CWE-354: Improper Validation of Integrity Check Value) affecting @lodestar/reqresp libraries in the npm ecosystem. The issue involves lodestar snappy checksum issue. Remediation is available by upgrading affected packages to version 1.25.0.

GHSA-m9c9-mc2h-9wjw at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-m9c9-mc2h-9wjw
Severity LOW
CVSS v3.1 Base Score N/A
CWE Classification CWE-354: Improper Validation of Integrity Check Value
Affected Product @lodestar/reqresp
Package Ecosystem npm
Published Date
Last Updated

How to Fix GHSA-m9c9-mc2h-9wjw

To remediate this vulnerability, upgrade all affected @lodestar/reqresp dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

@lodestar/reqresp fixed: 1.25.0
npm install @lodestar/reqresp@1.25.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls @lodestar/reqresp

How to Check if You Are Affected

To determine whether your Node.js application is affected by GHSA-m9c9-mc2h-9wjw, inspect your project dependencies using npm:

npm ls @lodestar/reqresp

Analyze the terminal output to locate any version of @lodestar/reqresp in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.25.0), your application is affected.

Note: The npm ls @lodestar/reqresp command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-354: Improper Validation of Integrity Check Value, where the product does not validate or incorrectly validates the integrity check values or "checksums" of a message. this may prevent it from detecting if the data has been modified or corrupted in transmission. In @lodestar/reqresp, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Impact Unintended permanent chain split affecting greater than or equal to 25% of the network, requiring hard fork (network partition requiring hard fork) Lodestar does not verify checksum in snappy framing uncompressed chunks. ### Vulnerability Details In Req/Resp protocol the messages are encoded by using ssz_snappy encoding, which is a snappy framing compression over ssz encoded message. In snappy framing format there are uncompressed chunks, each such chunk is prefixed with a checksum. Let's see how golang implementation parses such chunks - https://github.com/golang/snappy/blob/master/decode.go#L176 ``` case chunkTypeUncompressedData: // Section 4.3. Uncompressed data (chunk type 0x01). if chunkLen < checksumSize { r.err = ErrCorrupt return r.err } buf := r.buf[:checksumSize] if !r.readFull(buf, false) { return r.err } checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 // Read directly into r.decoded instead of via r.buf. n := chunkLen - checksumSize if n > len(r.decoded) { r.err = ErrCorrupt return r.err } if !r.readFull(r.decoded[:n], false) { return r.err } if crc(r.decoded[:n]) != checksum { r.err = ErrCorrupt return r.err } r.i, r.j = 0, n continue ``` As you can see, if checksum is incorrect, decoder fails and returns error. Now let's look at lodestar decoder https://github.com/ChainSafe/lodestar/blob/unstable/packages/reqresp/src/encodingStrategies/sszSnappy/snappyFrames/uncompress.ts#L17 ``` uncompress(chunk: Uint8ArrayList): Uint8ArrayList | null { this.buffer.append(chunk); const result = new Uint8ArrayList(); while (this.buffer.length > 0) { if (this.buffer.length < 4) break; const type = getChunkType(this.buffer.get(0)); const frameSize = getFrameSize(this.buffer, 1); if (this.buffer.length - 4 < frameSize) { break; } const data = this.buffer.subarray(4, 4 + frameSize); this.buffer.consume(4 + frameSize); if (!this.state.foundIdentifier && type !== ChunkType.IDENTIFIER) { throw "malformed input: must begin with an identifier"; } if (type === ChunkType.IDENTIFIER) { if (!Buffer.prototype.equals.call(data, IDENTIFIER)) { throw "malformed input: bad identifier"; } this.state.foundIdentifier = true; continue; } if (type === ChunkType.COMPRESSED) { result.append(uncompress(data.subarray(4))); } if (type === ChunkType.UNCOMPRESSED) { 1) result.append(data.subarray(4)); } } if (result.length === 0) { return null; } return result; } ``` As you can see, checksum is not verified, bytes are appended to 'result' ### Proof of Concept How to reproduce: get poc via [gist link](https://gist.github.com/gln7/aab55674431b1c8d42a59ccf9d7cbf60) and run it: ``` $ node dec1.mjs checking chunk type=255 checking chunk type=1 got uncompressed chunk.. Decompressed ok 124 bytes ```

GHSA-m9c9-mc2h-9wjw Affected @lodestar/reqresp Versions

1 component(s)
Fixed Versions:
1.25.0
Product / Package Ecosystem Affected Versions Fixed Version
@lodestar/reqresp
npm
< 1.25.0
1.25.0

References

Frequently Asked Questions

What is GHSA-m9c9-mc2h-9wjw?
GHSA-m9c9-mc2h-9wjw is a security vulnerability in @lodestar/reqresp involving Lodestar snappy checksum issue under CWE-354: Improper Validation of Integrity Check Value.
Which @lodestar/reqresp versions are affected?
Affected versions include releases prior to 1.25.0 across affected packages: @lodestar/reqresp.
What version fixes GHSA-m9c9-mc2h-9wjw?
Upgrading to 1.25.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @lodestar/reqresp affected?
Yes, @lodestar/reqresp is affected in releases prior to 1.25.0. Upgrade your dependency to 1.25.0 or later.
What CWE is associated with GHSA-m9c9-mc2h-9wjw?
GHSA-m9c9-mc2h-9wjw is classified under CWE-354: Improper Validation of Integrity Check Value (The product does not validate or incorrectly validates the integrity check values or "checksums" of a message. This may prevent it from detecting if the data has been modified or corrupted in transmission.).
How can I check npm dependencies for GHSA-m9c9-mc2h-9wjw?
Run the dependency tree command: 'npm ls @lodestar/reqresp'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-m9c9-mc2h-9wjw",
  "modified" : "2025-01-14T22:04:02Z",
  "published" : "2025-01-14T22:04:02Z",
  "aliases" : [ ],
  "summary" : "Lodestar snappy checksum issue",
  "details" : "### Impact\nUnintended permanent chain split affecting greater than or equal to 25% of the network, requiring hard fork (network partition requiring hard fork)\n\nLodestar does not verify checksum in snappy framing uncompressed chunks.\n\n### Vulnerability Details\nIn Req/Resp protocol the messages are encoded by using ssz_snappy encoding, which is a snappy framing compression over ssz encoded message.\n\nIn snappy framing format there are uncompressed chunks, each such chunk is prefixed with a checksum.\n\nLet's see how golang implementation parses such chunks - https://github.com/golang/snappy/blob/master/decode.go#L176\n\n```\n\tcase chunkTypeUncompressedData:\n\t\t\t// Section 4.3. Uncompressed data (chunk type 0x01).\n\t\t\tif chunkLen < checksumSize {\n\t\t\t\tr.err = ErrCorrupt\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tbuf := r.buf[:checksumSize]\n\t\t\tif !r.readFull(buf, false) {\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tchecksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24\n\t\t\t// Read directly into r.decoded instead of via r.buf.\n\t\t\tn := chunkLen - checksumSize\n\t\t\tif n > len(r.decoded) {\n\t\t\t\tr.err = ErrCorrupt\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tif !r.readFull(r.decoded[:n], false) {\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tif crc(r.decoded[:n]) != checksum {\n\t\t\t\tr.err = ErrCorrupt\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tr.i, r.j = 0, n\n\t\t\tcontinue\n```\n\nAs you can see, if checksum is incorrect, decoder fails and returns error.\n\nNow let's look at lodestar decoder https://github.com/ChainSafe/lodestar/blob/unstable/packages/reqresp/src/encodingStrategies/sszSnappy/snappyFrames/uncompress.ts#L17\n\n```\nuncompress(chunk: Uint8ArrayList): Uint8ArrayList | null {\n    this.buffer.append(chunk);\n    const result = new Uint8ArrayList();\n    while (this.buffer.length > 0) {\n      if (this.buffer.length < 4) break;\n\n      const type = getChunkType(this.buffer.get(0));\n      const frameSize = getFrameSize(this.buffer, 1);\n\n      if (this.buffer.length - 4 < frameSize) {\n        break;\n      }\n\n      const data = this.buffer.subarray(4, 4 + frameSize);\n      this.buffer.consume(4 + frameSize);\n\n      if (!this.state.foundIdentifier && type !== ChunkType.IDENTIFIER) {\n        throw \"malformed input: must begin with an identifier\";\n      }\n\n      if (type === ChunkType.IDENTIFIER) {\n        if (!Buffer.prototype.equals.call(data, IDENTIFIER)) {\n          throw \"malformed input: bad identifier\";\n        }\n        this.state.foundIdentifier = true;\n        continue;\n      }\n\n      if (type === ChunkType.COMPRESSED) {\n        result.append(uncompress(data.subarray(4)));\n      }\n      if (type === ChunkType.UNCOMPRESSED) {\n1)        result.append(data.subarray(4));\n      }\n    }\n    if (result.length === 0) {\n      return null;\n    }\n    return result;\n  }\n```\n\nAs you can see, checksum is not verified, bytes are appended to 'result'\n\n### Proof of Concept\n\nHow to reproduce:\n\nget poc via [gist link](https://gist.github.com/gln7/aab55674431b1c8d42a59ccf9d7cbf60) and run it:\n\n```\n$ node dec1.mjs \nchecking chunk type=255\nchecking chunk type=1\ngot uncompressed chunk..\nDecompressed ok 124 bytes\n```\n",
  "severity" : [ ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@lodestar/reqresp"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.25.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/ChainSafe/lodestar/security/advisories/GHSA-m9c9-mc2h-9wjw"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/ChainSafe/lodestar/commit/18a0d681dbcc51fb2ac9456f31e91f4e31a18300"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/ChainSafe/lodestar"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-354" ],
    "severity" : "LOW",
    "github_reviewed" : true,
    "github_reviewed_at" : "2025-01-14T22:04:02Z",
    "nvd_published_at" : null
  }
}