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

Failed notarization WAL write lets a node sign both a finalize vote and an empty vote

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

Los mantenedores suelen responder en 1 día

@yacovm ya está trabajando en esto.

Desde el 27/10/2026.

  • #672 de @yacovm — abierto

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
38/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
go

Línea de trabajo

Read simplex/epoch.go around persistNotarization, persistAndBroadcastNotarization, rebroadcastPastFinalizeVotes, and triggerEmptyBlockNotarization. Trace how a failed WAL.Append affects in-memory state and the two signing paths; then inspect the relevant epoch tests for WAL failure injection. Done when a failed append prevents the node from signing both vote types for the same round.

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

Descripción

medium security

Details

A validator that hits one failed disk write can sign both a finalize vote and an empty vote for the same round. That breaks a rule Simplex safety depends on, and up to f dishonest validators can then form both a finalization and an empty notarization for that round. It needs a transient write-ahead log (WAL) fault on the node plus a relayed finalization for an earlier round. The cause is that persistAndBroadcastNotarization returns the WAL.Append error without halting the epoch or undoing the in-memory notarization.

flowchart TD
  n1["peer relaying an earlier finalization"]
  n2["rebroadcastPastFinalizeVotes"]
  n3["triggerEmptyBlockNotarization"]
  n4["round timer from monitorProgress"]
  n5["safety of round r (no finalize vote and empty vote together)"]
  n6["halt via haltedError after failed append"]
  n1 -->|"triggers finalize vote signing"| n2
  n2 -->|"signs finalize vote for r"| n5
  n4 -->|"fires for r"| n3
  n3 -->|"signs empty vote for r"| n5
  n6 -->|"not set, node keeps running"| n2
  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 vulnerable_code
  class n3 vulnerable_code
  class n4 component
  class n5 asset
  class n6 missing_control
  1. the handlers maybeCollectNotarization and handleNotarizationMessage mark round r as notarized in memory before saving it. They call storeNotarization, which sets round.notarization, and only afterwards call the persist code. (simplex/epoch.go:1907-1912)
  2. the function persistNotarization returns the WAL.Append error before increaseRound runs. A transient disk fault makes the append fail, so the epoch never leaves round r. (simplex/epoch.go:1742-1768)
  3. the function persistAndBroadcastNotarization passes the error up without halting or rolling back. e.haltedError is not set and round.notarization is not cleared, so memory says round r is notarized while the log has no record of it. (simplex/epoch.go:1771-1775)
  4. the epoch keeps processing messages and time. HandleMessage stops only once haltedError is set, and AdvanceTime is never gated. (simplex/epoch.go:136-165)
  5. a peer relays a valid finalization for an earlier, uncommitted round. The finalization has Seq above nextSeqToCommit. It goes through handleFinalizationMessage and persistFinalization into rebroadcastPastFinalizeVotes, which signs a finalize vote for round r because it trusts the in-memory notarization. (simplex/epoch.go:1418-1461)
  6. the function triggerEmptyBlockNotarization signs and broadcasts an empty vote for round r when the round timer fires. The timer is cancelled only by increaseRound. The function checks only e.round > round and an earlier timeout, never an earlier finalize vote, so it signs. (simplex/epoch.go:2779-2821)
  7. up to f dishonest validators combine the two signatures into a finalization and an empty notarization for round r. Once an empty notarization for r exists, a later proposal that skips r is accepted by blockDependencies.

Evidence

  1. simplex/epoch.go:1771–1775
  2. simplex/epoch.go:1742–1768
  3. simplex/epoch.go:1907–1912
  4. simplex/epoch.go:1418–1461
  5. simplex/epoch.go:2779–2821
  6. simplex/epoch.go:136–165
  7. simplex/epoch.go:2179–2187

Impact

The affected node acts as one extra Byzantine validator for that round. A finalized round can also be skipped, which defeats the safety guarantee of the protocol. The report finds no data disclosure and no outage from this flaw.

Reproduction steps

  1. The trigger is a local fault the attacker does not control: one WAL.Append of a notarization record fails transiently on the victim validator, such as a disk or I/O error. The later appends at :1372 and :2805 must succeed, so the fault must be transient. After that, any peer can relay a valid finalization for an earlier, uncommitted round, and the report says this needs only low privileges. The finalize vote must be signed before the timer fires, because maybeConstructFinalizeVoteMessage refuses to sign after a timeout. Up to f Byzantine validators then collect both signatures.

Recommended fix

(1) After a failed notarization append, persistAndBroadcastNotarization and persistNotarization leave e.rounds[r].notarization set and the epoch running at round r. In-memory state is ahead of the durable log.

Fix criteria: A failed notarization append either halts the epoch, by setting haltedError as the replication paths do and stopping timers from signing, or fully rolls back the in-memory notarization. A test with an injected Append failure shows that no finalize vote is ever signed for that round.

(2) The signing paths do not exclude each other. rebroadcastPastFinalizeVotes trusts the in-memory notarization for the current round, and triggerEmptyBlockNotarization does not check whether a finalize vote was already cast for the round.

Fix criteria: Signing a finalize vote and signing an empty vote for the same round are mutually exclusive, whatever the persistence outcome. A test with any single injected Append failure shows that this node's broadcasts never include both message types for one round.

Status on main

Re-checked against main at 90995e0 on 2026-10-26; still present:

persistNotarization (simplex/epoch.go:1742-1745) returns the writeNotarizationToWal error before increaseRound, and persistAndBroadcastNotarization (epoch.go:1771-1775) propagates it without setting e.haltedError or clearing round.notarization, which storeNotarization already set (epoch.go:1907-1912, 446). HandleMessage (epoch.go:163) only stops once haltedError is set, and AdvanceTime (epoch.go:136-143) is not gated. rebroadcastPastFinalizeVotes (epoch.go:1418-1461) trusts in-memory round.notarization for rounds up to e.round, and triggerEmptyBlockNotarization (epoch.go:2779-2795) checks only e.round>round and timedOut, not a prior finalize vote. Whether this is exploitable depends on the host continuing after HandleMessage returns an error. No PR or issue found.


Severity: MEDIUM
Status: Open
Category: Improper error handling
CWE: CWE-755
Repository: ava-labs/Simplex
Branch: main
Date created: 2026-10-26
Claude Security finding: https://claude.ai/security/findings/find_01JC24yL5oTXowYhuYwx6nDi

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

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.