Byzantine validator can wedge per-sequence block slot with self-chosen leader round
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- go
- Domain
- distributed-systems, security
Research direction
Start in nonvalidator/non_validator.go at handleBlock around lines 185–209 and the finalization handling around lines 378–395; read simplex/epoch.go around lines 3684–3687 to understand LeaderForRound. Reproduce the junk-block sequence, then verify that a digest mismatch no longer prevents the genuine block and finalization from taking the direct path, using the existing flow as the completion check.
Written by the indexing model from the issue text.
Description
Details
handleBlock authenticates a proposed block only by checking that the sender equals LeaderForRound(epoch.nodes, bh.Round), where bh.Round is read from the unverified block itself. Any validator can therefore craft a junk block for any sequence within the MaxSequenceWindow, choose a Round value for which it is the leader (round mod n selects it), and have the block stored in incompleteSequences[seq]. The slot is first-writer-wins: once occupied, the genuine leader's real block for the same sequence is discarded at line 208 (incomplete.block != nil), and when the genuine finalization arrives the digest mismatch path (lines 385-395) neither evicts the junk block nor completes the pairing. The sequence can then only be committed via the replication fallback (ReceivedFutureFinalization -> request -> response -> processQuorumRound), adding request/response round-trips per poisoned sequence.
A single Byzantine validator can do this continuously for every upcoming sequence in the window, permanently degrading the non-validator's fast path so that it trails the network by replication latency for every block. Impact is limited to added latency/traffic (self-healing via replication; no halt, no incorrect data), hence LOW.
Evidence
- nonvalidator/non_validator.go:185–209
handleBlock accepts an unverified block if the sender is the leader of the round THE BLOCK ITSELF declares (bh.Round is attacker-chosen and unconstrained by the node's round view, so any validator can pick a round making itself leader), stores it first-writer-wins in incompleteSequences[bh.Seq], and line 208 then rejects any later (real) block for that sequence while the entry exists. - nonvalidator/non_validator.go:378–395
When the genuine QC-verified finalization arrives, it is stored (line 378) but the digest mismatch with the junk block only logs and returns; the junk block is not evicted, so the direct block+finalization fast path for that sequence can never complete and the node must fall back to replication round-trips. - simplex/epoch.go:3684–3687
LeaderForRound selects nodes[r % n] over the sorted epoch validator set; because the non-validator evaluates it on the attacker-chosen bh.Round, any validator can choose a round congruent to its own index and pass the leader check. The validator-side handler, by contrast, constrains the round to its current view and verifies a signed vote — protections the non-validator path lacks.
Impact
Only the direct commit fast path is disabled per sequence; the replication fallback recovers each sequence with extra round-trips, so the effect is sustained latency degradation and extra traffic, not loss of the service.
Reproduction steps
- Attacker must be a validator of a registered epoch (the leader check compares the sender to the leader derived from the attacker-chosen round). It sends one junk block message per target sequence within the window before the real proposal arrives; no other conditions are needed.
Recommended fix
The leader check trusts the round number declared by the unverified block, letting any validator impersonate 'the leader', and the per-sequence block slot is first-writer-wins with no eviction when the verified finalization's digest contradicts the stored block. Fix criteria: When a verified finalization's digest mismatches the stored block, the stored block must be replaced/evicted so the genuine block can still pair directly; and/or blocks should only be accepted for rounds consistent with the node's view. Verify that after a junk block is stored, delivery of the real block and finalization still commits via the direct path.
Severity: LOW
Status: Open
Category: Insufficient verification of data authenticity
CWE: CWE-345
Repository: ava-labs/Simplex
Branch: main
Date created: 2026-08-21
- Dominant language
- Go
- Stars
- 22
- Forks
- 4
- Avg merge
- 3d 1h
- Merged PRs (30d)
- 19
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ava-labs/Simplex
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 40/100
Maintainers usually reply within 1 day
-
Verification accepts aux info appends after the history is sufficient, changing the approval digestOpen
Difficulty 3/5 1-2 days Newbie friendliness 74/100
Maintainers usually reply within 1 day
All issues in ava-labs/Simplex
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
Maintainers usually reply within 1 day
-
bug go
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
genkit-ai/genkit#6761 · 1 comment ·
Maintainers usually reply within 2 days
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 87/100
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
github/github-mcp-server#3475 ·
Maintainers usually reply within 4 days