Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Replicated notarized block dropped when the leader refills the round, stalling the validator

Aperta
#665 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@yacovm ci sta già lavorando.

Dal 29/10/2026.

  • #680 di @yacovm — aperta

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

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

medium security

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
  1. 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.
  2. the victim's processNotarizedBlock deletes 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)
  3. the leader sends another proposal for the same round, queued ahead of the task. handleBlockMessage accepts 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)
  4. the task from createNotarizedBlockVerificationTask fails to store B and returns the zero digest. storeProposal refuses because e.rounds[R] now holds a block. Unlike the verify-failure branch, this path does not call ResendRoundRequest. (simplex/epoch.go:2412-2423)
  5. the requestor sends no new request for round R. The request timeout was already cleared, and observedSignedQuorum sends nothing once highestRequested is at least R. (simplex/requestor.go:140-148)
  6. 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 BlockDigestRequest and a new task, which the leader can again try to beat. (simplex/epoch.go:1004-1015)

Evidence

  1. simplex/epoch.go:2412–2423
  2. simplex/epoch.go:2166–2177
  3. simplex/epoch.go:1984–1989
  4. simplex/epoch.go:431–446
  5. simplex/requestor.go:140–148
  6. simplex/epoch.go:3697–3705
  7. simplex/epoch.go:1004–1015
  8. 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

  1. 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 OneTimeVerifier re-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

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di ava-labs/Simplex

Tutte le issue di ava-labs/Simplex

Issue simili

Altre issue su Go

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.