Ecosyste.ms: Advisories

An open API service providing security vulnerability metadata for many open source software ecosystems.

Security Advisories: GSA_kwCzR0hTQS1tOWM5LW1jMmgtOXdqd84ABDXn

Lodestar snappy checksum issue

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
Permalink: https://github.com/advisories/GHSA-m9c9-mc2h-9wjw
JSON: https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tOWM5LW1jMmgtOXdqd84ABDXn
Source: GitHub Advisory Database
Origin: Unspecified
Severity: Low
Classification: General
Published: 15 days ago
Updated: 15 days ago


Identifiers: GHSA-m9c9-mc2h-9wjw
References: Repository: https://github.com/ChainSafe/lodestar
Blast Radius: 0.0

Affected Packages

npm:@lodestar/reqresp
Dependent packages: 2
Dependent repositories: 1
Downloads: 4,462 last month
Affected Version Ranges: < 1.25.0
Fixed in: 1.25.0
All affected versions: 0.1.0, 1.3.0, 1.4.0, 1.4.1, 1.4.2, 1.4.3, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.7.1, 1.7.2, 1.8.0, 1.9.0, 1.9.1, 1.9.2, 1.10.0, 1.11.0, 1.11.1, 1.11.2, 1.11.3, 1.12.0, 1.12.1, 1.13.0, 1.14.0, 1.15.0, 1.15.1, 1.16.0, 1.17.0, 1.18.0, 1.18.1, 1.19.0, 1.20.0, 1.20.1, 1.20.2, 1.21.0, 1.22.0, 1.23.0, 1.23.1
All unaffected versions: