GHSA-m9c9-mc2h-9wjw
Lodestar snappy checksum issue
Blast Radius
@lodestar/reqrespReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects npm packages — download data is not available via public APIs for these ecosystems.
Description
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 and run it:
$ node dec1.mjs
checking chunk type=255
checking chunk type=1
got uncompressed chunk..
Decompressed ok 124 bytes
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | @lodestar/reqresp | all versions | 1.25.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @lodestar/reqresp. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update @lodestar/reqresp to 1.25.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-m9c9-mc2h-9wjw is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-m9c9-mc2h-9wjw is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-m9c9-mc2h-9wjw. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-m9c9-mc2h-9wjw in your dependencies?
O3 detects GHSA-m9c9-mc2h-9wjw across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.