← Back to Research
Research · July 22, 2026

How a zero-value GetBlockHeaders request underflows to serve go-ethereum's entire header chain (CVE-2024-32972)

Simon Morley·7 min read

A go-ethereum (geth) peer that sends an eth-protocol GetBlockHeaders request with amount (the wire's count field) set to 0 triggers an integer underflow in the header-serving path: count-1 wraps from 0 to UINT64_MAX, bypassing the node's maxHeadersServe cap and causing it to serve headers from the requested block all the way back to genesis. In versions before 1.13.15 this drives the responding node to build an oversized in-memory response — a memory-exhaustion denial of service (CVE-2024-32972, GHSA-4xc9-8hmq-j652, CVSS 7.5 High). This is a publicly disclosed go-ethereum vulnerability, reported by DongHan Kim via the Ethereum bug bounty program and fixed upstream in 1.13.15 — NullRabbit did not discover it. NullRabbit reproduced the wire-level attack request against a self-owned lab node and shipped it as a labelled detection-training bundle; we did not independently measure the resulting memory growth.

Why this matters

Any go-ethereum execution-layer node with P2P networking enabled — full nodes, RPC-serving infrastructure, and the execution-client half of a post-merge validator pair — accepts inbound eth protocol connections from any peer that completes the standard devp2p Hello + eth Status handshake. No authentication, elevated privilege, or user interaction is required (CVSS vector AV:N/AC:L/PR:N/UI:N): the request is a normal-shaped protocol message with one field set to a value (0) that a legitimate client has no reason to send.

Because the trigger is a single field in a single request rather than a volumetric flood, the attack does not need sustained traffic to reach the vulnerable code path — one connection, one malformed GetBlockHeaders message, is enough to start the oversized serving loop described below. The GHSA states plainly that "no workarounds have been made public" for versions prior to the fix; the only documented remedy is upgrading past 1.13.15.

How the attack works

  • Entry point. The eth protocol's GetBlockHeaders request (devp2p message, eth code 0x03), sent over a standard RLPx connection after a completed devp2p Hello and eth Status handshake. Remote, unauthenticated, reachable by any connecting peer.
  • Preconditions. A target running go-ethereum before v1.13.15 with P2P networking reachable (standard geth default: TCP/UDP 30303). No prior state or special peer standing is needed beyond completing the handshake.
  • Processing path. The handler resolves the request through BlockChain.GetHeadersFrom(number, count uint64) (core/blockchain_reader.go), which is called with count-1 as its count argument (per the GHSA: chain.GetHeadersFrom(num+count-1, count-1)). When the attacker sets amount (count) to 0, the subtraction count-1 underflows the unsigned 64-bit type, wrapping from 0 to 18,446,744,073,709,551,615 (UINT64_MAX). That oversized count bypasses the node's maxHeadersServe limit, and the read range extends from the requested origin back to the genesis block.
  • Resource / trust boundary crossed. Server-side memory. The fix (go-ethereum PR #29534, merged for v1.13.15) confirms the code-level gap directly: core/rawdb/accessors_chain.go's ReadHeaderRange previously called db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 0) — the trailing 0 is the response's byte-size ceiling, i.e. none. v1.13.15 changes that argument to 2*1024*1024 (a 2 MB sanity cap). Before the fix, a single peer-supplied integer controlled how much of the freezer/ancient store the node would read into memory for one response, with no independent size bound.
  • Resulting behaviour. The responding node accumulates an oversized set of headers in memory while serving the request. Per the GHSA/CVSS, the consequence is that "a vulnerable node can be made to consume very large amounts of memory" — an availability/DoS effect. This is not memory corruption, not remote code execution, and not a consensus fault.

Affected systems

  • Chain. Ethereum (execution layer, devp2p/eth protocol P2P surface).
  • Implementation. go-ethereum (geth). No other execution-client implementation is asserted here — this brief is scoped to the geth codebase named in the GHSA.
  • Component / method. eth protocol GetBlockHeaders request handler -> BlockChain.GetHeadersFrom -> core/rawdb.ReadHeaderRange / AncientRange.
  • Affected versions. go-ethereum prior to 1.13.15 (per the GHSA and the NVD record).
  • Fixed version. 1.13.15 and onwards, via go-ethereum PR #29534 (ReadHeaderRange's AncientRange call gains a 2 MB / 2*1024*1024-byte response-size cap).
  • Severity. CVSS 3.1 base score 7.5 (High) — vector AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (NVD); CWE-400 (Uncontrolled Resource Consumption), also per the NVD record. The GHSA itself rates the advisory "High" and lists no CWE. Published 6 May 2024.
  • Credit. "This issue was disclosed responsibly by DongHan Kim via the Ethereum bug bounty program" (GHSA).
  • NullRabbit's reproduction target. The lab reproduction runs against a self-owned, privately-provisioned geth node reachable over devp2p/RLPx (target_env: privnet-geth-devp2p). Per the reproducer's own documented design, the malicious request is emitted and captured as wire traffic regardless of whether the target build is patched — a patched node simply disconnects rather than serving the oversized range. NullRabbit's bundles do not pin, or make any claim about, a specific vulnerable-vs-patched geth build; the affected/fixed version boundary above is the GHSA's, not an independent NullRabbit finding.

Evidence

  • Publicly documented (primary source). CVE-2024-32972 / GHSA-4xc9-8hmq-j652: mechanism (GetBlockHeaders amount=0 -> count-1 underflow to UINT64_MAX -> maxHeadersServe bypass -> serve-to-genesis -> memory exhaustion), affected versions (< 1.13.15), fixed version (1.13.15, PR #29534), CVSS 7.5 High / CWE-400 (NVD), credit to DongHan Kim via the Ethereum bug bounty program, and the statement that no workaround exists pre-upgrade.
  • Independently reproduced by NullRabbit (lab). A hand-rolled RLPx/ECIES client (chains/ethereum/lab/drivers/known_class_geth.py) completes a genuine devp2p Hello + eth Status handshake against a live geth node, then emits the exact malicious shape: devp2p message 0x13 (eth GetBlockHeaders) with payload [reqID, [origin, amount=0, skip=0, reverse=1]]. The resulting wire traffic — handshake plus the malformed request — was captured as 12 pcap-only attack bundles across four traffic postures (distributed: 8 workers, ~224-248 connections; saturating: 16 workers, ~215-240 connections; mimicry: 3 workers, ~68-70 connections; low-volume: 2 workers, ~64-65 connections; each over 8 s, 3 repetitions per posture), against a self-owned lab target (fidelity: lab, target_authorisation: self-owned). These bundles carry packet capture only (packets_pcap: true; no host_parquet/responses_parquet) — they confirm the malicious request was constructed and delivered over a real handshake, not that the target's memory usage was observed to spike.
  • Measured under controlled conditions. Not established. NullRabbit's reproduction did not instrument or record the target node's memory consumption; the memory-exhaustion impact is asserted by the public disclosure, not independently measured here.
  • Inferred but not reproduced. None beyond the above.

Operator actions

  • The documented fix. Upgrade go-ethereum to 1.13.15 or later. This is the only remedy the GHSA names; per the advisory, "no workarounds have been made public" for earlier versions.
  • Exposure check. Query a node's client version (admin_nodeInfo or the Geth/vX.Y.Z... string exposed to peers during handshake) and confirm it is >= 1.13.15. Any go-ethereum node below that version, reachable over P2P, is exposed as described.
  • Detection idea (NullRabbit's own, not a vendor recommendation). A GetBlockHeaders request with amount (count) = 0 has no legitimate purpose — a well-behaved client never asks for zero headers. An operator or IDS able to inspect devp2p eth-protocol traffic could flag this exact field value as a strong signal of an attempted exploitation, independent of whether the target node is patched. This is a detection heuristic derived from the mechanism, not a mitigation documented by the vendor.

Canonical references

Related attacks

  • NRDAX-T0122 on Ethereum — geth_les_skip_negative. A different geth protocol (LES, not eth) and a different public CVE (CVE-2018-12018, "EPoD"), but the same shape of bug: a header-serving request handler (GetBlockHeadersMsg) that does not validate an attacker-supplied integer field (Skip = -1, i.e. UINT64_MAX as an unsigned value) before using it, there causing an out-of-range access and a crash rather than an oversized serve. Fixed in geth 1.8.11.
  • NRDAX-T0071 on Ethereum — geth_ethash_memory_exhaustion_dos. Same memory_amp family and chain, a different public CVE (CVE-2021-42219): an excessive volume of block/header messages drives repeated ethash cache generation (consensus/ethash/algorithm.go) to the same end state — node memory exhaustion — via a different mechanism (repeated cache regeneration rather than a single oversized header-range read).

Track attacks affecting go-ethereum

New go-ethereum and Ethereum execution-layer findings are catalogued as they are reproduced. Track attacks affecting go-ethereum on NRDAX and in NullRabbit research.


Analysis plus reproducer. No weaponised proof-of-concept code.

ethereumgo-ethereumdevp2pmemory_ampp2pcve-replicationinfrastructure-security
Publication policy:Why we publish these findings →

Related Posts

One prior-knowledge h2c connection multiplexes N eth_getLogs past an L4 per-connection cap on go-ethereum's JSON-RPC port

·7 min read

go-ethereum's JSON-RPC port terminates cleartext HTTP/2 (h2c) by prior knowledge, so one TCP connection can multiplex N eth_getLogs streams past an L4 edge that only caps connections per IP (nginx stream limit_conn) — measured 20/20 streams and ~39 MB pulled through a single connection the edge counts as one, an amplification-multiplexing bypass of a connection cap, not of an L7 request rate limit.

researchethereumgo-ethereum+5 more