Replayed Telock leaves unsatisfiable verification dependency, stalling non-validator forever
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 48/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- go
調査の方向性
Start with scheduleNewFinalizedBlockTask in nonvalidator/non_validator.go and the pending task logic in common/block_scheduler.go; trace how indexed sequences and epochs affect queued dependencies. Then inspect the non-validator pruning path and existing tests. Done means stale replay tasks are removed and unmarked, superseded old-epoch blocks are not scheduled, and the described regression test passes.
索引モデルが issue の本文から書いたものです。
説明
Details
A Byzantine validator from the old epoch can permanently stall a non-validator node (one that follows the chain without voting) at one block height after an epoch change. This needs the old epoch to have ended with two or more Telocks (empty filler blocks). Only a restart recovers the node. The cause is that scheduleNewFinalizedBlockTask queues a replayed Telock whose parent is never indexed, and nothing removes that task.
flowchart TD
n1["Old-epoch validator that led T2's round"]
n2["handleFinalization and handleBlock"]
n3["scheduleNewFinalizedBlockTask"]
n4["BlockDependencyManager pending task for seq k+2"]
n5["Non-validator indexing at seq k+2 and later"]
n6["RemoveOldTasks (never called by non-validator)"]
n1 -->|"re-sends T2 finalization and block"| n2
n2 -->|"accepts and schedules T2"| n3
n3 -->|"queues task waiting on digest of T1"| n4
n4 -->|"keeps seq k+2 marked scheduled, real block refused"| n5
n6 -->|"not applied, task never removed"| 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 vulnerable_code
class n4 component
class n5 asset
class n6 missing_control
- Network finishes epoch
ewith a sealing block and two Telocks. Sealing blockSis at seqk, TelockT1atk+1and TelockT2atk+2. The Telocks are finalized but never indexed, and the new epochkreuses their sequence numbers. - Attacker (old-epoch validator that led
T2's round) sends the finalization ofT2to the non-validator.handleFinalizationstill finds epocheinn.epochsuntil the first epoch-kblockR1is indexed. It therefore verifies and stores the old-epoch finalization, and any peer could send it. (nonvalidator/non_validator.go:470-557) - Attacker sends the
T2block message as the round leader.handleBlockaccepts the block only from the leader of its round, and the attacker is that leader. A matching finalization is already stored, so the block is scheduled. (nonvalidator/non_validator.go:306-357) - the function
scheduleNewFinalizedBlockTaskregisters a task for seqk+2that waits ondigest(T1). Seqk+1is not indexed, so the task depends on theprevdigest from the finalization header. The task marks seqk+2as scheduled. (nonvalidator/non_validator.go:559-574) - the scheduler in
common/block_scheduler.gonever releases the waiting task.ExecuteBlockDependentsclears a dependency only when a finished task returns exactly that digest.R1returns its own digest, andT1's task has already finished or never runs. (common/block_scheduler.go:67-98) - the non-validator never prunes the stale task. After indexing it prunes sequences and epochs but never calls
RemoveOldTasks, so the task stays pending. (nonvalidator/non_validator.go:405-414) - the non-validator refuses the real block at seq
k+2.IsSequenceScheduledstays true for that sequence, so every later attempt to schedule it returns early. Later sequences wait behind it, and the node stops until restarted. (common/block_scheduler.go:131-137)
Evidence
- nonvalidator/non_validator.go:559–574
- common/block_scheduler.go:131–137
- common/block_scheduler.go:67–98
- nonvalidator/non_validator.go:306–357
- nonvalidator/non_validator.go:470–557
- nonvalidator/non_validator.go:405–414
Impact
The non-validator stops indexing at seq k+2 for good, until it is restarted. It cannot follow the chain, serve newer blocks by replication, or become a validator in a later epoch. Nothing is disclosed or altered.
Reproduction steps
- The attacker is the epoch-
evalidator that led the round of finalized TelockT2at seqk+2. It may be Byzantine, and it may already be removed from the validator set. The victim is a non-validator running across the epoch transition. That node has indexed the sealing block but not the first epoch-kblockR1. It must also not be in epochk's validator set, because members switch to validator mode. A node started afterSknows only epochkand is affected too. The attacker sends theT2finalization (any peer may) and theT2block message (leader only). Honest ordering can also trigger it:Sis indexed,T1's task finishes, thenT2arrives.incompleteSequences[k+2]keepsT2's finalization, so a re-sentT2block reschedules it even if the node already processed both Telocks correctly. The report did not run the reproduction. It comes from a static audit.
Recommended fix
(1) A verification task whose prev dependency can no longer be met stays pending forever and keeps its sequence marked as scheduled. This happens when the preceding sequence was indexed with a different digest, or when the task's epoch was dropped.
Fix criteria: When a sequence is indexed, pending tasks for later sequences are removed and their sequence is unmarked. This applies to tasks whose prev differs from the indexed digest and to tasks whose block epoch is below the minimum kept epoch. The genuine block for that sequence can then be scheduled. The test TestNonValidatorStallsOnStaleTelockDependency from the description passes by indexing seq 5 of epoch 3 after the Telock replay.
(2) The non-validator accepts and schedules old-epoch blocks at sequences above the new epoch's first block. It does not recognize them as superseded Telocks.
Fix criteria: A finalized block is not scheduled for verification in either of two cases. Its epoch is older than that of an already-indexed block at a lower or equal sequence. Or it is a Telock at a sequence above the new epoch's first block. Replaying such a block before seq k+1 is indexed no longer prevents seq k+2 from being indexed.
Status on main
Re-checked against main at 90995e0 on 2026-10-26; still present:
scheduleNewFinalizedBlockTask (nonvalidator/non_validator.go:559-574) schedules with prev=&bh.Prev whenever seq-1 is not indexed, and returns early when IsSequenceScheduled(seq) is true. BlockDependencyManager.IsSequenceScheduled (common/block_scheduler.go:131-137) stays true for a pending task until it runs or RemoveOldTasks drops it. The nonvalidator package never calls RemoveOldTasks (grep finds no call). After R1 is indexed, removeOldSequencesAndEpochs (non_validator.go:405-414) only prunes incompleteSequences and epochs. A T2 task waiting on digest(T1) is never released, so later genuine scheduling of k+2 via handleBlock, handleFinalization or processReplicationState (non_validator.go:356/551/691) is a no-op. PR #648 ('Ignore telocks') only changed tests.
Severity: MEDIUM
Status: Open
Category: Improper resource shutdown or release
CWE: CWE-404
Repository: ava-labs/Simplex
Branch: main
Date created: 2026-10-26
Claude Security finding: https://claude.ai/security/findings/find_0177fGoBt3cquBsrHSqEkpXS
- 主要言語
- Go
- スター
- 22
- フォーク
- 4
- 平均マージ
- 1日 16時間
- マージ済み PR(30日)
- 35
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
ava-labs/Simplex のほかの issue
-
Unverified sealing-block finalization lets a validator redirect a syncing node's replication requests対応中かも @yacovm が 3 日前に担当しました。 オープンlow security
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
ava-labs/Simplex#666 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
Epoch discovery accepts a sealing block on a head count, letting low-stake validators forge epochs対応中かも @yacovm が 3 日前に担当しました。 オープン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 が 5 日前に担当しました。 オープン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 が 5 日前に担当しました。 オープンmedium security
難易度 4/5 3〜5日 初心者へのやさしさ 55/100
メンテナーはふだん 1 日以内に返信
-
Honest builders never raise timestamp to parent's, so a leader's future timestamp voids honest proposals対応中かも @yacovm が 6 日前に担当しました。 オープンhigh security
難易度 4/5 3〜5日 初心者へのやさしさ 43/100
メンテナーはふだん 1 日以内に返信
ava-labs/Simplex の issue をすべて見る
似ている issue
-
date_time, url_encode, html_escape, to_unix_time, contains_all and contains_any panic when called without arguments対応中かも @antcybersec が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
projectdiscovery/dsl#372 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
JuliusBrussee/caveman#1449 ·
メンテナーはふだん 1 日以内に返信
-
bot-found bug priority: P3
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
madenvel/KalinkaPlayer#498 ·
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
clawsweeper:fix-shape-clear clawsweeper:needs-maintainer-review clawsweeper:needs-product-decision clawsweeper:no-new-fix-pr clawsweeper:source-repro impact:ux-friction issue-rating: 🦞 diamond lobster P2
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
openclaw/gogcli#1291 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信