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.