epochDigestCounter grows unbounded under attacker-chosen sequence keys that are never pruned
- Dominant language
- Go
- Stars
- 22
- Forks
- 4
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 34
Description
## Details
The epochDigestCounter records sealing-block attestations in sealingBlockResponses, a map keyed by the sequence number read from the sender's UNVERIFIED block header (bh.Seq). A malicious validator can send a stream of ReplicationResponses, each containing a fabricated quorum round (block with a non-nil SealingBlockInfo, an unknown epoch number, and a digest-consistent fake finalization so it passes VerifyQCConsistentWithBlock) with a distinct, arbitrarily large Seq. Each message allocates a new outer map entry plus inner map (sender -> digest). Cleanup (removeOldEpochs) deletes only keys strictly below the epoch of the last indexed block, so keys chosen near 2^64 are never removed for the process lifetime — the memory is a permanent leak rather than transient buffering, growing linearly and without bound in the number of attacker messages. The counter is unreachable for non-validator senders (validators.Contains(from) gate), so validator privileges are required.
## Evidence
1. [nonvalidator/epochs.go:182–190](https://github.com/ava-labs/Simplex/blob/main/nonvalidator/epochs.go#L182-L190)
collectedSealingBlockInfo creates a new sealingBlockResponses[newEpoch] map entry for every distinct bh.Seq taken from the sender's unverified block header; a validator can send quorum rounds with arbitrary distinct Seq values, each allocating a persistent entry.
2. [nonvalidator/epochs.go:208–213](https://github.com/ava-labs/Simplex/blob/main/nonvalidator/epochs.go#L208-L213)
removeOldEpochs only deletes keys strictly below minEpochToKeep (the epoch of the last indexed block). Keys chosen near MaxUint64 are never pruned for the lifetime of the process.
3. [nonvalidator/non\_validator.go:532–540](https://github.com/ava-labs/Simplex/blob/main/nonvalidator/non_validator.go#L532-L540)
Reachable from handleQrFromUnknownEpoch for any quorum round whose block carries a SealingBlockInfo and an unknown epoch; the only gate is that the sender is in the current validator set.
## Impact
Persistent, unprunable memory growth on the non-validator, eventually degrading or crashing the process; each entry is small so exhaustion requires a large number of messages, and a restart clears the state. No confidentiality or integrity impact.
## Reproduction steps
1. Attacker must be a current validator (membership check against Comm.Validators()). It streams replication responses with fabricated sealing blocks carrying distinct huge sequence numbers; every message permanently allocates map state on the non-validator. Impact accumulates with sustained traffic since per-entry size is small.
## Recommended fix
sealingBlockResponses is keyed by an unvalidated, attacker-chosen sequence number, has no cap on the number of tracked epochs, and its pruning predicate (strictly below the current epoch) never removes keys above the chain's progress. Fix criteria: Bound the number of tracked prospective epochs (e.g., only track sequences within a window above the current tip, or cap entries per sender) and prune keys that cannot correspond to real epochs. Verify a validator streaming distinct huge sequence numbers cannot grow the map beyond a fixed bound.
---
**Severity:** LOW
**Status:** Open
**Category:** Allocation without limits
**CWE:** [CWE-770](https://cwe.mitre.org/data/definitions/770.html)
**Repository:** ava-labs/Simplex
**Branch:** main
**Date created:** 2026-08-21
---
Contributor guide
No contributing guide indexed for this repository
Research direction
Read nonvalidator/epochs.go around collectedSealingBlockInfo and removeOldEpochs, then trace the handleQrFromUnknownEpoch path in nonvalidator/non_validator.go. Reproduce the validator-message sequence described in the issue and inspect sealingBlockResponses growth. Done means distinct attacker-chosen sequence numbers cannot make tracked state exceed a fixed bound and normal epoch cleanup still works.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- distributed-systems, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100