← THE INDEX  ·  WRITEUP

CVE-2024-32972: GetBlockHeaders Integer Underflow Causes Full Network Denial of Service

Sending Amount=0 in a GetBlockHeaders p2p message triggers a uint64 underflow to UINT64_MAX. The resulting allocation scales with chain height -- on Electroneum mainnet, a single connection is sufficient to OOM-kill the node.

Summary

Electroneum Smart Chain (etn-sc) is a fork of go-ethereum v1.10.18. In eth/protocols/eth/handlers.go, the serviceContiguousBlockHeaderQuery() function processes incoming GetBlockHeaders p2p messages. When an attacker sends a request with Amount=0 and a known block hash, the expression count-1 (where count is uint64(0)) wraps to UINT64_MAX (18446744073709551615).

This value is passed to GetHeadersFrom() in core/headerchain.go, which clamps it to the current chain height and then allocates an RLP-encoded slice for that many headers. On Electroneum mainnet (~13 million blocks at time of report), a single request triggers an allocation of approximately 7.8 GB, exceeding available memory on typical validator nodes and triggering an OOM kill.

The upstream fix (go-ethereum PR #29534, shipped in v1.13.15) adds a single zero-check guard. That fix was not present in etn-sc. No authentication is required: only TCP connectivity to the default p2p port (30303).

Electroneum uses IBFT consensus with approximately 30 validators. Crashing more than half halts block production for the entire network.

Impact

A single unauthenticated TCP connection to any etn-sc node's p2p port (30303) can OOM-kill that node. The attack requires no credentials, no on-chain state, and no special hardware.

By sending one packet per validator node, an attacker can halt the entire Electroneum network. IBFT requires a two-thirds quorum to produce blocks: crashing 16 of ~30 validators stops all block production. Because the attack is repeatable at essentially zero cost (one TCP connection and one p2p message), the network cannot recover by simply restarting nodes -- the attacker can re-crash them immediately on reconnection.

The allocation size grows with the chain: at 13 million blocks, roughly 7.8 GB per request. Growth is approximately 260 KB per day as new blocks are added.

Root cause

In eth/protocols/eth/handlers.go, the contiguous path in serviceContiguousBlockHeaderQuery():

count := query.Amount  // attacker sends 0, type is uint64
if count > maxHeadersServe {
    count = maxHeadersServe  // 0 < 1024, not triggered
}
// ...
descendants := chain.GetHeadersFrom(num+count-1, count-1)
//                                               ^^^^^^^
// count-1 with count=0: 0 - 1 wraps to 18446744073709551615 (UINT64_MAX)

In core/headerchain.go, GetHeadersFrom() clamps count to the current chain height but then iterates and allocates for that many headers. On mainnet, the clamp produces ~13 million, and the allocation follows.

The upstream fix is one line at the top of the function:

if query.Amount == 0 {
    return nil
}

This fix was present in go-ethereum v1.13.15 (PR #29534) but was not backported to etn-sc, which remained on the v1.10.18 codebase.

Proof of concept

The exploit implements the full RLPx ECIES handshake and etn/66 capability exchange before sending the malicious payload. All node identifiers and credentials have been replaced with placeholders.

Disclosure and fix

Reported to the Electroneum security team as a P1. The fix is to apply the upstream guard from go-ethereum PR #29534 at the top of serviceContiguousBlockHeaderQuery():

func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersPacket) []rlp.RawValue {
    count := query.Amount
    if count == 0 {
        return nil  // prevent uint64 underflow on count-1
    }
    ...

Beyond this fix, etn-sc should be audited against all go-ethereum security advisories issued since v1.10.18 (mid-2022). CVE-2023-40591 (unbounded goroutine spawn on ping flood) is one confirmed additional issue affecting the same codebase.