Failed notarization WAL write lets a node sign both a finalize vote and an empty vote
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 38/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- go
- Lĩnh vực
- distributed-systems, security
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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
- Ngôn ngữ chính
- Go
- Star
- 22
- Fork
- 4
- Merge trung bình
- 1 ngày 17 giờ
- Pull request đã merge (30 ngày)
- 33
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của ava-labs/Simplex
-
low security
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Replicated notarized block dropped when the leader refills the round, stalling the validatorCó thể đã có người làm @yacovm đã nhận 1 ngày trước. Đang mởmedium security
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Epoch discovery accepts a sealing block on a head count, letting low-stake validators forge epochsCó thể đã có người làm @yacovm đã nhận 1 ngày trước. Đang mởmedium security
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Replicated empty notarization lets a previous-epoch Telock become the new epoch's chain tipCó thể đã có người làm @yacovm đã nhận 1 ngày trước. Đang mởmedium security
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 38/100
ava-labs/Simplex#663 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Builder ignores parent's P-chain height, so a leader's maximal height stalls honest proposalsCó thể đã có người làm @yacovm đã nhận 3 ngày trước. Đang mởmedium security
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 55/100
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của ava-labs/Simplex
Issue tương tự
-
ux
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
evcc-io/evcc#35605 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug report
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
Maintainer thường phản hồi trong vòng 1 ngày
-
type:bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
nightgauge/nightgauge#2732 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
fix below CVEsĐang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 5 ngày
-
good first issue help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100