Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Replicated certificate for a different block deletes the voted round, allowing a second vote

Đang mở
#662 0 bình luận 0 reaction 1 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@yacovm đang làm issue này rồi.

Từ ngày 28/10/2026.

  • #677 của @yacovm — đang mở

Đá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

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ả

medium security

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
  1. 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.
  2. 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.
  3. the victim's storeNotarization deletes e.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)
  4. Byzantine leader sends a new BlockMessage for round R holding a block C whose digest differs from A's. handleBlockMessage accepts it because the round is current and the leader signed it. deleteFutureProposal already cleared proposalBeingProcessed when block A's task ran, and nothing checks whether the node already voted. (simplex/epoch.go:1981-1992)
  5. the verification task calls storeProposal, which succeeds. storeProposal refuses a second block only while e.rounds[R] exists, and that entry is gone. (simplex/epoch.go:3286-3297)
  6. the verification task calls doProposed, which signs and broadcasts a second Vote for round R. The vote decision depends only on storeProposal succeeding, and the second block is appended to the WAL (write-ahead log). (simplex/epoch.go:2251-2287)

Evidence

  1. simplex/epoch.go:2251–2287
  2. simplex/epoch.go:431–446
  3. simplex/epoch.go:2168–2177
  4. simplex/epoch.go:2088–2095
  5. simplex/epoch.go:3003–3014
  6. simplex/epoch.go:3286–3297
  7. simplex/epoch.go:1981–1992
  8. 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

  1. 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 storeNotarization sees a different header and deletes e.rounds[R]. The same deletion happens in three other places: storeFinalization for a direct Finalization message, processNotarizedBlock for a replicated notarization, and processFinalizedBlock for a replicated finalization. The leader then sends a new BlockMessage for 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

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của ava-labs/Simplex

Tất cả issue của ava-labs/Simplex

Issue tương tự

Thêm issue về Go

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.