{"answers":{"affected":"2 open source packages ship an affected version, including @lodestar/reqresp.","exploited":"There is no known exploitation of GHSA-m9c9-mc2h-9wjw in the wild and no public exploit code.","fix":"Upgrade @lodestar/reqresp to 1.25.0 or later.","what":"GHSA-m9c9-mc2h-9wjw is a vulnerability in @lodestar/reqresp, published on 14 Jan 2025. An attacker can exploit it over the internet without logging in, with no action from the victim."},"fix_prompt":"Check this codebase for GHSA-m9c9-mc2h-9wjw and fix it if it is affected.\n\nWhat it is: GHSA-m9c9-mc2h-9wjw is a vulnerability in @lodestar/reqresp, published on 14 Jan 2025. An attacker can exploit it over the internet without logging in, with no action from the victim.\nExploitation: There is no known exploitation of GHSA-m9c9-mc2h-9wjw in the wild and no public exploit code.\n\nAffected packages and fixed versions:\n- @lodestar/reqresp (npm): affected all versions; fixed in 1.25.0\n- @lodestar/reqresp (npm): affected all versions; fixed in 1.25.0\n\nTask:\n1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.\n2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.\n3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.\n4. Run the tests and summarise what changed and why.\n5. If nothing in the project is affected, say so and change nothing.\n\nReference: https://insights.sbomify.com/GHSA-m9c9-mc2h-9wjw/ (Markdown: https://insights.sbomify.com/GHSA-m9c9-mc2h-9wjw/index.md)","id":"GHSA-m9c9-mc2h-9wjw","markdown":"https://insights.sbomify.com/GHSA-m9c9-mc2h-9wjw/index.md","name":"GHSA-m9c9-mc2h-9wjw","record":{"aliases":[],"analysis":{"component_type":"server_software","fix_type":"patch","impacts":["denial_of_service","other"],"network_reachable":"yes","non_default_config":false,"product_category":"Ethereum consensus client","requires_auth":"none","summary":"Lodestar fails to verify snappy checksums, allowing attackers to cause permanent chain splits and network partitions requiring hard forks.","user_interaction":"not_required","vuln_class":"other"},"analysis_generated_by":"qwen3.8-flash-next","assigner":null,"cpe":[],"cvss":{"score":null,"severity":null,"source":null,"vector":null,"version":null},"cwes":[],"description":"### 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 \u003c 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])\u003c\u003c8 | uint32(buf[2])\u003c\u003c16 | uint32(buf[3])\u003c\u003c24\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 \u003e 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 \u003e 0) {\n      if (this.buffer.length \u003c 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 \u003c 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 \u0026\u0026 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```","ecosystems":["npm"],"exploits":{"exploitdb":null,"first_seen":null,"github_poc":null,"max_poc_stars":null,"metasploit":null,"nuclei":null,"refs":[],"tier":null},"has_fix":true,"headline":"Lodestar snappy checksum issue","id":"GHSA-m9c9-mc2h-9wjw","indexable":true,"kev":null,"kind":"advisory","links":{"ecosystem":"npm","product":null,"products":[],"type":"other","year":"2025"},"n_packages":2,"n_references":null,"packages":[{"ecosystem":"npm","fixed":["1.25.0"],"introduced":["0"],"last_affected":[],"package":"@lodestar/reqresp","purl":null,"source":"GHSA-m9c9-mc2h-9wjw"},{"ecosystem":"npm","fixed":["1.25.0"],"introduced":["0"],"last_affected":[],"package":"@lodestar/reqresp","purl":"pkg:npm/%40lodestar/reqresp","source":"GHSA-m9c9-mc2h-9wjw"}],"products":null,"published":"2025-01-14","related":[],"sources":[{"class":"attribution","licence":"CC BY 4.0","name":"GitHub Advisory Database","prefix":"GHSA"}],"ssvc":{"automatable":null,"exploitation":null,"technical_impact":null},"state":null,"updated":null,"vendors":null},"summary":"Lodestar fails to verify snappy checksums, allowing attackers to cause permanent chain splits and network partitions requiring hard forks.","url":"https://insights.sbomify.com/GHSA-m9c9-mc2h-9wjw/"}