Failed notarization WAL write lets a node sign both a finalize vote and an empty vote
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 38/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- go
調査の方向性
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.
索引モデルが issue の本文から書いたものです。
説明
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
- the handlers
maybeCollectNotarizationandhandleNotarizationMessagemark roundras notarized in memory before saving it. They callstoreNotarization, which setsround.notarization, and only afterwards call the persist code. (simplex/epoch.go:1907-1912) - the function
persistNotarizationreturns theWAL.Appenderror beforeincreaseRoundruns. A transient disk fault makes the append fail, so the epoch never leaves roundr. (simplex/epoch.go:1742-1768) - the function
persistAndBroadcastNotarizationpasses the error up without halting or rolling back.e.haltedErroris not set andround.notarizationis not cleared, so memory says roundris notarized while the log has no record of it. (simplex/epoch.go:1771-1775) - the epoch keeps processing messages and time.
HandleMessagestops only oncehaltedErroris set, andAdvanceTimeis never gated. (simplex/epoch.go:136-165) - a peer relays a valid finalization for an earlier, uncommitted round. The finalization has
SeqabovenextSeqToCommit. It goes throughhandleFinalizationMessageandpersistFinalizationintorebroadcastPastFinalizeVotes, which signs a finalize vote for roundrbecause it trusts the in-memorynotarization. (simplex/epoch.go:1418-1461) - the function
triggerEmptyBlockNotarizationsigns and broadcasts an empty vote for roundrwhen the round timer fires. The timer is cancelled only byincreaseRound. The function checks onlye.round > roundand an earlier timeout, never an earlier finalize vote, so it signs. (simplex/epoch.go:2779-2821) - up to
fdishonest validators combine the two signatures into a finalization and an empty notarization for roundr. Once an empty notarization forrexists, a later proposal that skipsris accepted byblockDependencies.
Evidence
- simplex/epoch.go:1771–1775
- simplex/epoch.go:1742–1768
- simplex/epoch.go:1907–1912
- simplex/epoch.go:1418–1461
- simplex/epoch.go:2779–2821
- simplex/epoch.go:136–165
- 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
- The trigger is a local fault the attacker does not control: one
WAL.Appendof a notarization record fails transiently on the victim validator, such as a disk or I/O error. The later appends at:1372and:2805must 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, becausemaybeConstructFinalizeVoteMessagerefuses to sign after a timeout. Up tofByzantine 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
- 主要言語
- 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 が 1 日前に担当しました。 オープン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 日以内に返信
-
Replicated empty notarization lets a previous-epoch Telock become the new epoch's chain tip対応中かも @yacovm が 1 日前に担当しました。 オープンmedium security
難易度 4/5 3〜5日 初心者へのやさしさ 38/100
ava-labs/Simplex#663 · 担当者 1 名 ·
メンテナーはふだん 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
-
bug from-studio
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
esengine/DeepSeek-Reasonix#14868 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 80/100
project-zot/zot#4636 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
keyxmakerx/Chronicle#1911 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
eugenioenko/ttt#1293 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
needs/area needs/kind needs/priority
難易度 2/5 1〜3時間 初心者へのやさしさ 80/100
メンテナーはふだん 1 日以内に返信