Read the docs
TI-2025-43543 Aliases GHSA-m9c9-mc2h-9wjw

Lodestar snappy checksum issue

Priority LOW 38CWE-354Published · Updated

Key facts

Attack vector
—
Privileges required
—
User interaction
—
Attack complexity
—
Impact type
—
Confidentiality · integrity · availability
—
Fix status
Fixed

No CVSS vector from any source. Fix status from GitHub.

Summary

### 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 ```

Why it matters now

Fix Fixed @lodestar/reqresp (npm): 1.25.0

SSVC decision points

Not available: CISA has not published SSVC decision points for this record.

Affected products

VendorProductAffected versionsFixed inStatusSource
—@lodestar/reqresp (npm)before 1.25.01.25.0Patch availableGHSA-m9c9-mc2h-9wjw
  • @lodestar/reqresp (npm) Vendor— Affected before 1.25.0 Fixed in 1.25.0 Patch available GHSA-m9c9-mc2h-9wjw

Remediation

  • Fix · GHSA-m9c9-mc2h-9wjw @lodestar/reqresp (npm) Fixed in 1.25.0 Advisory →

Timeline

  1. 2026-10-03 Threatist record created from GHSA-m9c9-mc2h-9wjw

References

Sources

This product uses the NVD API but is not endorsed or certified by the NVD. Sources and licences