Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto
#665 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
35/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
go

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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

Lenguaje dominante
Go
Estrellas
22
Forks
4
Merge medio
1 d 17 h
PR fusionados (30 d)
32

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de ava-labs/Simplex

Todos los issues de ava-labs/Simplex

Issues similares

Más issues de Go

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.