Replicated empty notarization lets a previous-epoch Telock become the new epoch's chain tip
I maintainer di solito rispondono entro 1 giorno
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
- Ambito
- distributed-systems, security
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
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
- attacker validator builds a mixed
ReplicationResponsequorum round. It combines the previous epoch's Telock block and its notarization with a genuine new-epoch empty notarization for the same round. - 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) processQuorumRoundaccepts the quorum round after one epoch check.QuorumRound.GetEpochreturns the empty notarization's epoch first, so the current epoch hides the old epoch of the block. (simplex/epoch.go:3628-3633)verifyQuorumRoundverifies 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, andVerifyQCConsistentWithBlockonly compares rounds, not epochs. (simplex/epoch.go:3576-3601)createNotarizedBlockVerificationTaskstores the old-epoch Telock as the newest notarized block and restarts the round. It writes the block and notarization toe.roundsand the WAL, andVerify(OnlyVMVerifyOpt)returns at once for a Telock. (simplex/epoch.go:2376-2457)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)indexFinalizationswaits forever for seq k+1 after the new block is finalized.CallbackStorage.Indexskips Telocks, so seq k+1 is never committed. Blocks keep being finalized but none is committed. (simplex/epoch.go:1503-1544)
Evidence
- simplex/epoch.go:3628–3633
- common/msg.go:351–361
- common/msg.go:383–421
- simplex/epoch.go:3576–3601
- simplex/epoch.go:186–190
- simplex/epoch.go:2376–2457
- external.go:62–79
- simplex/epoch.go:2749–2777
- simplex/epoch.go:682–706
- msm/msm.go:1469–1489
- simplex/epoch.go:1503–1544
- adapters.go:59–75
- 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
- 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.lastBlockto 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
- 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
-
Unverified sealing-block finalization lets a validator redirect a syncing node's replication requestsForse già presa @yacovm l’ha presa 1 giorno fa. Apertalow security
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
ava-labs/Simplex#666 · 1 assegnatario ·
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 2 giorni fa. Apertamedium security
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
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 4 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 4 giorni fa. Apertamedium security
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
I maintainer di solito rispondono entro 1 giorno
-
Honest builders never raise timestamp to parent's, so a leader's future timestamp voids honest proposalsForse già presa @yacovm l’ha presa 4 giorni fa. Apertahigh security
Difficoltà 4/5 3-5 giorni Idoneità per principianti 43/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di ava-labs/Simplex
Issue simili
-
Headerless responses block while skipping duplicate status linesForse già presa @mynhe l’ha presa 1 giorno fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 73/100
projectdiscovery/rawhttp#554 ·
-
stage-fail
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
siyuan-note/bazaar#2447 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 80/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
superplanehq/superplane#9120 ·
I maintainer di solito rispondono entro 1 giorno