Replicated notarized block dropped when the leader refills the round, stalling the validator
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 35/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- go
- Ambito
- distributed-systems, security
Direzione di ricerca
Start with simplex/epoch.go, especially processNotarizedBlock, handleBlockMessage, and createNotarizedBlockVerificationTask, then read the request handling in simplex/requestor.go. Reproduce the ordering race described in the issue by scheduling the notarized block task before storing a conflicting proposal. Done means the valid notarization is retained, the node advances to round R+1, and further proposals for R are ignored while the block is missing.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Details
A dishonest round leader, who must be a current validator, can keep an honest validator stuck in one round. With 4 validators, the stuck node plus the withholding leader leaves no quorum, so the chain stops. The cause is createNotarizedBlockVerificationTask: it drops a certified block when e.rounds[R] is refilled, and it does not ask for the block again. The attack is a race, not a sure lock.
flowchart TD
n1["dishonest round leader (validator)"]
n2["handleBlockMessage"]
n3["e.rounds[R] slot"]
n4["createNotarizedBlockVerificationTask"]
n5["victim validator stuck in round R"]
n6["re-request of the notarized block"]
n1 -->|"sends extra proposal for round R"| n2
n2 -->|"stores it, refilling the slot"| n3
n4 -->|"storeProposal refused"| n3
n4 -->|"gives up, node stays in round R"| n5
n6 -->|"not applied on this path"| n4
classDef actor stroke:#2563eb,stroke-width:2px
classDef vulnerable_code stroke:#dc2626,stroke-width:2px
classDef existing_control stroke:#16a34a,stroke-width:2px
classDef missing_control stroke:#dc2626,stroke-width:2px,stroke-dasharray:5 5
classDef asset stroke:#9333ea,stroke-width:2px
classDef component stroke:#6b7280,stroke-width:1px
class n1 actor
class n2 component
class n3 component
class n4 vulnerable_code
class n5 asset
class n6 missing_control
- the round leader sends a decoy block to the victim and the real block B to the other validators. The others notarize B, which means 2f+1 votes certify it, while the victim holds only the decoy.
- the victim's
processNotarizedBlockdeletes the conflicting unnotarized round and schedules a verification task. The task runs later on the single verification goroutine, so the slot is only empty at scheduling time, not when the task runs. (simplex/epoch.go:2166-2177) - the leader sends another proposal for the same round, queued ahead of the task.
handleBlockMessageaccepts it once the earlier task has cleared its in-flight flag, and nothing records that round R already has a notarization. (simplex/epoch.go:1984-1989) - the task from
createNotarizedBlockVerificationTaskfails to store B and returns the zero digest.storeProposalrefuses becausee.rounds[R]now holds a block. Unlike the verify-failure branch, this path does not callResendRoundRequest. (simplex/epoch.go:2412-2423) - the requestor sends no new request for round R. The request timeout was already cleared, and
observedSignedQuorumsends nothing oncehighestRequestedis at least R. (simplex/requestor.go:140-148) - the victim's empty-vote rebroadcast is the only way left to get B again, and each try faces the same race. Honest peers answer the rebroadcast with the notarization. A copy that finds the slot empty triggers a
BlockDigestRequestand a new task, which the leader can again try to beat. (simplex/epoch.go:1004-1015)
Evidence
- simplex/epoch.go:2412–2423
- simplex/epoch.go:2166–2177
- simplex/epoch.go:1984–1989
- simplex/epoch.go:431–446
- simplex/requestor.go:140–148
- simplex/epoch.go:3697–3705
- simplex/epoch.go:1004–1015
- simplex/util.go:150–185
Impact
The victim stays in round R and votes in no later round while the attacker keeps winning. With 4 validators the chain commits nothing during the stall. A single leader can divert up to f victims, and it can repeat the attack each time it leads. This is a race, not a guaranteed lock: a buffered copy replayed by maybeLoadFutureMessages deletes the leader's block again. A response processed while no leader task is queued ahead lets B through. Nothing is disclosed, and no conflicting block is notarized. A finalization cannot rescue the victim, because 2f honest finalize votes are below 2f+1.
Reproduction steps
- The attacker is a validator that leads round R. It needs no action from the victim. It sends the victim a decoy block and the other validators the real block B, then keeps sending the victim proposals for R. Each copy of B that reaches the victim, replicated or fetched by digest, is scheduled behind one of those proposals. The attacker must win this ordering race on every recovery attempt. It can bias the race. It can resend one block, which
OneTimeVerifierre-verifies instantly, or send distinct blocks to keep a task in flight. It also sees the victim's empty votes, so it knows when each rebroadcast cycle starts.
Recommended fix
(1) createNotarizedBlockVerificationTask refuses a verified block that carries a valid notarization for round R whenever e.rounds[R] holds any block, even an unnotarized one. It returns without re-requesting, so the certified block is lost and the node cannot leave R.
Fix criteria: A verified block with a valid notarization for round R ends up stored with that notarization, replacing any unnotarized block in the slot, or is re-requested until it is. Test it by scheduling the notarized-block task and storing a conflicting leader proposal before it runs. The node must persist the notarization and advance to R+1.
(2) After a mismatch deletes e.rounds[R], the node forgets that R already has a notarization. handleBlockMessage accepts new leader proposals for R, and the requestor's highestRequested gate stops later notarization copies from fetching the block.
Fix criteria: Once a valid notarization for round R is known, the node does not store or vote on further proposals for R. It keeps a request for the notarized block pending until the block is stored. A leader proposal for R sent after the mismatch is ignored. Each notarization copy received while the block is missing leads to a request or a live timeout.
Status on main
Re-checked against main at 90995e0 on 2026-10-26; still present:
In createNotarizedBlockVerificationTask (simplex/epoch.go:2419-2423), a storeProposal failure returns common.Digest{} without calling ResendRoundRequest. Only the verify-failure branch (epoch.go:2392-2398) and the queue-full branch (epoch.go:2200-2203) re-request. processNotarizedBlock deletes the conflicting round at scheduling time (epoch.go:2168-2177). handleBlockMessage (epoch.go:1915-2022) keeps no marker that round R has a known notarization, so a new leader proposal can refill e.rounds[R] before the task runs. No PR or issue found.
Severity: MEDIUM
Status: Open
Category: TOCTOU race condition
CWE: CWE-367
Repository: ava-labs/Simplex
Branch: main
Date created: 2026-10-26
Claude Security finding: https://claude.ai/security/findings/find_01V7wt29pkaVW2Seq83txZ3z
- Lingua principale
- Go
- Stelle
- 22
- Fork
- 4
- Merge medio
- 1g 17h
- PR unite (30g)
- 33
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di ava-labs/Simplex
-
low security
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
I maintainer di solito rispondono entro 1 giorno
-
Epoch discovery accepts a sealing block on a head count, letting low-stake validators forge epochsForse già presa @yacovm l’ha presa oggi. Apertamedium security
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
I maintainer di solito rispondono entro 1 giorno
-
Replicated empty notarization lets a previous-epoch Telock become the new epoch's chain tipForse già presa @yacovm l’ha presa 1 giorno fa. Apertamedium security
Difficoltà 4/5 3-5 giorni Idoneità per principianti 38/100
ava-labs/Simplex#663 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Failed notarization WAL write lets a node sign both a finalize vote and an empty voteForse già presa @yacovm l’ha presa 2 giorni fa. Apertamedium security
Difficoltà 4/5 3-5 giorni Idoneità per principianti 38/100
I maintainer di solito rispondono entro 1 giorno
-
Builder ignores parent's P-chain height, so a leader's maximal height stalls honest proposalsForse già presa @yacovm l’ha presa 2 giorni fa. Apertamedium security
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di ava-labs/Simplex
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
cloudevents/sdk-go#1346 ·
-
needs-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
kubernetes/cloud-provider-aws#1517 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
agent-butler-finding chore
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
jordansmall/spindrift#6221 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
I maintainer di solito rispondono entro 3 giorni