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

Replicated empty notarization lets a previous-epoch Telock become the new epoch's chain tip

Chiusa
#663 0 commenti 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@yacovm ci sta già lavorando.

Dal 29/10/2026.

  • #678 di @yacovm — integrata

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
38/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
go

Direzione di ricerca

Start with processQuorumRound and QuorumRound.GetEpoch in simplex/epoch.go and common/msg.go, then trace the existing quorum verification and replicated response handling described in the issue. Check how the block, notarization, finalization, and empty notarization epochs are validated before any state is stored or persisted. Done means a mixed epoch quorum round is rejected; the issue’s test sketch expects the WAL to remain unchanged and Metadata().Prev to stay unchanged.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

medium security

Details

A validator of the new epoch can halt the whole chain for every validator, and a restart does not fix it. It needs the old epoch's notarization for its first Telock, and enough validator overlap for that old certificate to verify. The cause is processQuorumRound, which takes the epoch of a replicated round from its empty notarization and never checks the block's own epoch.

flowchart TD
  n1["attacker validator of the new epoch"]
  n2["processQuorumRound"]
  n3["honest validators' chain state"]
  n4["epoch comparison of block, notarization and empty notarization"]
  n5["ReplicationResponse handling in HandleMessage"]
  n1 -->|"sends mixed quorum round"| n5
  n5 -->|"skips the epoch filter"| n2
  n2 -->|"stores old Telock as chain tip"| n3
  n4 -->|"not applied"| 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 asset
  class n4 missing_control
  class n5 component
  1. attacker validator builds a mixed ReplicationResponse quorum round. It combines the previous epoch's Telock block and its notarization with a genuine new-epoch empty notarization for the same round.
  2. attacker validator sends the response to the validators that are already in the new epoch. Replication responses skip the per-message epoch filter in HandleMessage, so no epoch check runs at this point. (simplex/epoch.go:186-190)
  3. processQuorumRound accepts the quorum round after one epoch check. QuorumRound.GetEpoch returns the empty notarization's epoch first, so the current epoch hides the old epoch of the block. (simplex/epoch.go:3628-3633)
  4. verifyQuorumRound verifies the old notarization against the current validator set. It passes when the signers are still validators with the same keys and form a quorum of the new set, and VerifyQCConsistentWithBlock only compares rounds, not epochs. (simplex/epoch.go:3576-3601)
  5. createNotarizedBlockVerificationTask stores the old-epoch Telock as the newest notarized block and restarts the round. It writes the block and notarization to e.rounds and the WAL, and Verify(OnlyVMVerifyOpt) returns at once for a Telock. (simplex/epoch.go:2376-2457)
  6. metadata() builds the next proposal on the injected Telock at seq k+2. The new epoch's chain is now built on the Telock instead of on the sealing block. (simplex/epoch.go:2749-2777)
  7. indexFinalizations waits forever for seq k+1 after the new block is finalized. CallbackStorage.Index skips Telocks, so seq k+1 is never committed. Blocks keep being finalized but none is committed. (simplex/epoch.go:1503-1544)

Evidence

  1. simplex/epoch.go:3628–3633
  2. common/msg.go:351–361
  3. common/msg.go:383–421
  4. simplex/epoch.go:3576–3601
  5. simplex/epoch.go:186–190
  6. simplex/epoch.go:2376–2457
  7. external.go:62–79
  8. simplex/epoch.go:2749–2777
  9. simplex/epoch.go:682–706
  10. msm/msm.go:1469–1489
  11. simplex/epoch.go:1503–1544
  12. adapters.go:59–75
  13. simplex/epoch.go:3492–3522

Impact

The chain halts for every validator, because no block can be committed once a block built on the Telock is finalized. A restart does not recover it, since the WAL filter keeps the new-epoch blocks built on the Telock. No conflicting block is committed and nothing is disclosed, so the harm to integrity is limited to corrupted consensus state.

Reproduction steps

  1. The attacker must be a validator of the new epoch. It must hold the old epoch's notarization, or the individual votes, for the old epoch's first Telock. Conditions outside its control: that Telock was notarized, and the old certificate verifies against the new validator set. The second condition needs validator overlap, which an incremental validator-set change gives. The attacker can re-aggregate the old votes to pick signers that fit. The new epoch's first round must also get an empty notarization, and the attacker can force this when it leads that round. As the empty notarization forms, it sends the mixed response to every validator. The next leader builds on the Telock, and the poisoned validators notarize and finalize that block. No user action is needed. A variant with a finalized Telock sets e.lastBlock to the Telock directly. Poisoned honest nodes also serve the mixed round to peers. Replies for seq k+1 carry no empty notarization and are dropped.

Recommended fix

processQuorumRound relies on QuorumRound.GetEpoch, which lets a current-epoch empty notarization vouch for a block, notarization or finalization of another epoch. VerifyQCConsistentWithBlock never compares the empty notarization's epoch with the block's. The old certificate then verifies against the current validators.

Fix criteria: Every part of a replicated quorum round (block header, notarization, finalization and empty notarization) carries the node's current epoch. Otherwise the whole quorum round is dropped before anything is stored, persisted, voted on or used by metadata(). The test sketch confirms it: an epoch-4 block plus an epoch-5 empty notarization delivered to an epoch-5 node leaves the WAL without its records and Metadata().Prev unchanged.

Status on main

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

processQuorumRound (simplex/epoch.go:3618-3653) checks only QuorumRound.GetEpoch(), which returns the empty notarization's epoch first (common/msg.go:351-361). VerifyQCConsistentWithBlock (common/msg.go:383-421) compares round and digest/header but never compares the empty notarization's epoch with the block's epoch. verifyQuorumRound (epoch.go:3576-3601) verifies the old-epoch notarization QC against the current validator set (VerifyQC in util.go:53-76 does no epoch check). createNotarizedBlockVerificationTask (epoch.go:2376-2457) also has no block-epoch check. Rounds continue across epochs (setMetadataFromStorage sets e.round = lastBlock.Round+1, epoch.go:688-689), so Telock rounds can collide with new-epoch rounds. Exploitability needs validator overlap.


Severity: MEDIUM
Status: Open
Category: Capture-replay
CWE: CWE-294
Repository: ava-labs/Simplex
Branch: main
Date created: 2026-10-26
Claude Security finding: https://claude.ai/security/findings/find_01Txb4brf7Qje2X7Lr7cVe1E

Lingua principale
Go
Stelle
22
Fork
4
Merge medio
1g 16h
PR unite (30g)
35

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.