Replicated certificate for a different block deletes the voted round, allowing a second 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
- 35/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
Start with simplex/epoch.go, especially storeNotarization, storeFinalization, processNotarizedBlock, processFinalizedBlock, and the proposal and voting paths cited in the report. Trace how the round's vote record changes after a conflicting certificate, then reproduce the A/B/C sequence described in the issue. Done means the node broadcasts no second vote for a different digest, writes only one block record per round, and still adopts and replicates B.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Details
A Byzantine validator that leads a round can make an honest validator sign votes for two different blocks in that round. The attacker must be a validator and the round's leader. The code at fault is the recovery code in storeNotarization and related functions. It deletes e.rounds[R], the only record of the vote, so storeProposal stops refusing a second block. No second notarization results.
flowchart TD
n1["Byzantine leader validator"]
n2["handleBlockMessage"]
n3["storeProposal guard"]
n4["Second Vote for round R and second WAL block"]
n5["storeNotarization and other recovery code"]
n6["Durable record that the node voted in round R"]
n1 -->|"sends a second block for round R"| n2
n2 -->|"schedules verification"| n3
n3 -->|"accepts block, node signs second vote"| n4
n1 -->|"conflicting certificate reaches the node"| n5
n5 -->|"deletes the entry the guard reads"| n3
n6 -->|"does not exist"| n3
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 asset
class n5 component
class n6 missing_control
- Byzantine leader sends block A to the victim and block B to the other honest validators. The leader of round R is the attacker, so it can choose what each node receives.
- Other honest validators vote for B and broadcast its notarization. B collects 2f+1 real votes, so the certificate is genuine and nothing is forged.
- the victim's
storeNotarizationdeletese.rounds[R]when the certificate names a different block. That entry is the node's only record that it voted in round R. The node stays in round R and requests block B through replication. (simplex/epoch.go:431-446) - Byzantine leader sends a new
BlockMessagefor round R holding a block C whose digest differs from A's.handleBlockMessageaccepts it because the round is current and the leader signed it.deleteFutureProposalalready clearedproposalBeingProcessedwhen block A's task ran, and nothing checks whether the node already voted. (simplex/epoch.go:1981-1992) - the verification task calls
storeProposal, which succeeds.storeProposalrefuses a second block only whilee.rounds[R]exists, and that entry is gone. (simplex/epoch.go:3286-3297) - the verification task calls
doProposed, which signs and broadcasts a secondVotefor round R. The vote decision depends only onstoreProposalsucceeding, and the second block is appended to the WAL (write-ahead log). (simplex/epoch.go:2251-2287)
Evidence
- simplex/epoch.go:2251–2287
- simplex/epoch.go:431–446
- simplex/epoch.go:2168–2177
- simplex/epoch.go:2088–2095
- simplex/epoch.go:3003–3014
- simplex/epoch.go:3286–3297
- simplex/epoch.go:1981–1992
- simplex/epoch.go:1248–1255
Impact
The honest validator signs and broadcasts two votes for round R with different digests, and writes both blocks to its WAL. This breaks the first-block-only voting rule. It does not produce a second notarization or finalization, because a third block C can gather at most f Byzantine votes plus f victim votes, which is below quorum. The report calls this a broken protocol invariant, not a safety break on its own. It would become one if another path deleted e.rounds[R] without a genuine conflicting certificate.
Reproduction steps
- The attacker is a Byzantine validator that leads round R. It sends block A to the victim and block B to the other honest validators. B gets a genuine notarization of 2f+1 votes, which the honest nodes broadcast themselves, and the attacker may also send it. The victim's
storeNotarizationsees a different header and deletese.rounds[R]. The same deletion happens in three other places:storeFinalizationfor a direct Finalization message,processNotarizedBlockfor a replicated notarization, andprocessFinalizedBlockfor a replicated finalization. The leader then sends a newBlockMessagefor round R before block B arrives through replication. Only validators reach these handlers. The only race is beating B's replication round trip, and the attacker controls that timing.
Recommended fix
The node's memory of having voted in round R lives only in e.rounds[R]. The recovery code in processNotarizedBlock, processFinalizedBlock, storeNotarization and storeFinalization deletes that entry, so the first-block-only voting rule is no longer enforced for that round.
Fix criteria: After a node has voted in round R, it never signs a second Vote for a different digest in round R, whatever certificates it learns later. Reproduce the scenario: vote for A, inject a valid notarization or finalization for B, then deliver a new proposal C for the same round. Assert that no second VoteMessage is broadcast and only one block record per round is written. Also assert that the node still replicates and adopts B.
Status on main
Re-checked against main at 90995e0 on 2026-10-26; still present:
storeNotarization deletes e.rounds[R] on a header mismatch (simplex/epoch.go:435-444). processNotarizedBlock (epoch.go:2168-2177) and processFinalizedBlock (epoch.go:2093-2100) do the same. createBlockVerificationTask clears proposalBeingProcessed via deleteFutureProposal (epoch.go:2229, 1248-1255). handleBlockMessage (epoch.go:1915-2022) has no record that the node already voted in the current round R. A new leader proposal therefore passes, storeProposal succeeds because e.rounds[R] is gone (epoch.go:3286-3296), and doProposed/voteOnBlock (epoch.go:3064-3079) signs a second vote with nothing to stop it. No PR or issue found.
Severity: MEDIUM
Status: Open
Category: Improper workflow enforcement
CWE: CWE-841
Repository: ava-labs/Simplex
Branch: main
Date created: 2026-10-26
Claude Security finding: https://claude.ai/security/findings/find_0166rYDpWCzUnzXN4KqY6UUS
- 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)
- 32
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 validatorĐ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 epochsĐ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
-
medium security
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 38/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Replayed Telock leaves unsatisfiable verification dependency, stalling non-validator foreverCó thể đã có người làm @yacovm đã nhận hôm nay. Đang mởmedium security
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
ava-labs/Simplex#659 · 1 người được giao ·
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ự
-
Dokumentasjon på nais defaultsĐang mở
Độ 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 1 ngày
-
agent-butler-finding bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
jordansmall/spindrift#6161 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
open-telemetry/opentelemetry-go-compile-instrumentation#1635 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
C-bug O-community X-blathers-untriaged
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 84/100
cockroachdb/cockroach#176210 · 1 bình luận ·
-
VLAgent `spec.k8sCollector.includePodLabels: false` has no effectCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
VictoriaMetrics/operator#2772 ·
Maintainer thường phản hồi trong vòng 1 ngày