fix(consensus): harden FullProposal.Verify and NewProposal against malicious proposals#2908
fix(consensus): harden FullProposal.Verify and NewProposal against malicious proposals#2908wen-coding merged 11 commits intomainfrom
Conversation
…osal.Verify FullProposal.Verify only validated lane ranges for committee lanes, never checking for extra non-committee lanes. A malicious proposer could inject fake lanes to inflate GlobalRange, causing a panic in NewFullCommitQC when header count didn't match. Add a lane count check and comprehensive Verify test suite (20 tests covering all verification branches). Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## main #2908 +/- ##
===========================================
- Coverage 57.19% 48.19% -9.01%
===========================================
Files 2093 666 -1427
Lines 171732 50167 -121565
===========================================
- Hits 98228 24179 -74049
+ Misses 64738 23871 -40867
+ Partials 8766 2117 -6649
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2908 +/- ##
===========================================
- Coverage 68.42% 57.74% -10.68%
===========================================
Files 5 2111 +2106
Lines 456 174226 +173770
===========================================
+ Hits 312 100603 +100291
- Misses 114 64652 +64538
- Partials 30 8971 +8941
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
FullProposal.Verify checked that the LaneQC's block number matched the proposal's LaneRange but not the header hash. A malicious proposer could present a valid LaneQC for header H1 while setting LastHash to H2, causing a liveness attack: the committed proposal would reference a header hash no honest replica has votes for, permanently stalling FullCommitQC construction. Add a header hash check alongside the existing block number check. Also remove unused laneKey parameter from test helper makeLaneQC. Co-authored-by: Cursor <cursoragent@cursor.com>
FullProposal.Verify checked that the proposal's claimed signing key matched the expected leader but never cryptographically verified the signature itself. A man-in-the-middle could intercept a valid proposal, modify its content, and relay it with the original leader's key claim and stale signature, passing the key identity check. Add m.proposal.VerifySig(c) to verify the ed25519 signature against the proposal hash, and add a test for forged proposal signatures. Co-authored-by: Cursor <cursoragent@cursor.com>
…sal_test.go Test names are self-explanatory; section comments add noise. Also remove stale _ = rng assignment. Co-authored-by: Cursor <cursoragent@cursor.com>
The ExtraLane test already covers this case; the GlobalRange inflation variant adds no additional code path coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
….Verify - Add Proposal.Verify(c) to check lane count and membership against the committee, fixing a bug where a non-committee lane substituted for a committee lane (same count) could pass verification at genesis due to Proposal.LaneRange() returning a synthetic [0,0) fallback. - Extract proposal local variable to avoid repeated .Msg() calls. - Replace TestVerifyRejectsExtraLane with two targeted tests: TestVerifyRejectsNonCommitteeLane (substitution) and TestVerifyRejectsMissingLaneRange (count mismatch). Co-authored-by: Cursor <cursoragent@cursor.com>
- Relax Proposal.Verify to allow implicit empty lane ranges for omitted lanes - Rename all test functions with TestProposal prefix for clarity - Use NewProposal as source of truth in tests, tamper fields for negative cases - Remove stringly-typed error assertions (require.Contains) in favor of require.Error - Add tests for implicit lane ranges (contiguous and non-contiguous) - Discard NewProposal error with _ since it cannot fail with valid test inputs Co-authored-by: Cursor <cursoragent@cursor.com>
037a3b3 to
6c4b13a
Compare
| break | ||
| } | ||
| } | ||
| tamperedFP := &FullProposal{ |
There was a problem hiding this comment.
nit: I think that NewProposal does not verify whether leader is correct, so it can be called directly
There was a problem hiding this comment.
Hmm, but maybe we should check?
Since NewProposal has everything to verify the leader, we can check at construction time, instead of sending the wrong proposal across the wires and getting it dropped.
Adding the check now and updating tests.
| tamperedRanges = append(tamperedRanges, r) | ||
| } | ||
| } | ||
| tamperedProposal := newProposal(origP.view, origP.createdAt, tamperedRanges, origP.app) |
There was a problem hiding this comment.
Same here, NewProposal would set first to a sane value, we need to tamper and sign the wrong value.
| i++ | ||
| } | ||
|
|
||
| shortProposal := newProposal(origP.view, origP.createdAt, keptRanges, origP.app) |
There was a problem hiding this comment.
NewProposal? Especially the happy path test (no tampering) shouldn't need to access private constructors.
There was a problem hiding this comment.
Can't use NewProposal here, because NewProposal's construction logic always produces all lanes and signs over the content. If we use NewProposal and replaces signature, it would fail at "bad signature" in Verify.
| for _, r := range origP.laneRanges { | ||
| ranges = append(ranges, r) | ||
| } | ||
| tamperedProposal := newProposal(origP.view, origP.createdAt, ranges, utils.None[*AppProposal]()) |
There was a problem hiding this comment.
We force app to be None here, NewProposal will reset app to be old appQC I believe.
pompon0
left a comment
There was a problem hiding this comment.
lgtm, left some comments for tests.
…1 in tests - NewProposal now validates that the key belongs to the view leader - Replace `fp, _ := NewProposal(...)` with `utils.OrPanic1(...)` in tests - Simplify TestProposalVerifyRejectsForgedSignature - Fix all callers to use correct leader key (data/testonly, avail tests) Co-authored-by: Cursor <cursoragent@cursor.com>
78e415d to
3eccafc
Compare
Summary
FullProposal.Verify(was only checking key identity, not the actual sig)LaneRange.LastHashProposal.VerifymethodNewProposalconstructor (fail-fast on wrong key)utils.OrPanic1for compact error handling in testsTest plan
TestProposalVerify*tests passautobahn/...test suite passes (avail, consensus, data, types)