Replicated empty notarization lets a previous-epoch Telock become the new epoch's chain tip
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 38/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- go
調査の方向性
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.
索引モデルが issue の本文から書いたものです。
説明
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
- 主要言語
- Go
- スター
- 22
- フォーク
- 4
- 平均マージ
- 1日 17時間
- マージ済み PR(30日)
- 33
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
ava-labs/Simplex のほかの issue
-
low security
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
メンテナーはふだん 1 日以内に返信
-
Replicated notarized block dropped when the leader refills the round, stalling the validator対応中かも @yacovm が今日担当しました。 オープンmedium security
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
メンテナーはふだん 1 日以内に返信
-
Epoch discovery accepts a sealing block on a head count, letting low-stake validators forge epochs対応中かも @yacovm が 1 日前に担当しました。 オープンmedium security
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
メンテナーはふだん 1 日以内に返信
-
Failed notarization WAL write lets a node sign both a finalize vote and an empty vote対応中かも @yacovm が 3 日前に担当しました。 オープンmedium security
難易度 4/5 3〜5日 初心者へのやさしさ 38/100
メンテナーはふだん 1 日以内に返信
-
Builder ignores parent's P-chain height, so a leader's maximal height stalls honest proposals対応中かも @yacovm が 3 日前に担当しました。 オープンmedium security
難易度 4/5 3〜5日 初心者へのやさしさ 55/100
メンテナーはふだん 1 日以内に返信
ava-labs/Simplex の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信
-
schema-drift
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
stefanhoth/paperless-ngx-cli#61 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
slavakurilyak/awesome-ai-agents#888 ·
メンテナーはふだん 1 日以内に返信
-
enhancement nav-pilot
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
kind/enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
メンテナーはふだん 1 日以内に返信