Backport release/v6.3: fix to ProposalPOLMessage poisoning (CON-222)#3138
Backport release/v6.3: fix to ProposalPOLMessage poisoning (CON-222)#3138seidroid[bot] wants to merge 1 commit intorelease/v6.3from
release/v6.3: fix to ProposalPOLMessage poisoning (CON-222)#3138Conversation
|
Please cherry-pick the changes locally and resolve any conflicts. git fetch origin backport-3129-to-release/v6.3
git worktree add --checkout .worktree/backport-3129-to-release/v6.3 backport-3129-to-release/v6.3
cd .worktree/backport-3129-to-release/v6.3
git reset --hard HEAD^
git cherry-pick -x d201e89ad5f867eea4e275b6d4ec5b80e5cf6afc
git push --force-with-lease |
|
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 @@
## release/v6.3 #3138 +/- ##
================================================
- Coverage 46.02% 44.73% -1.30%
================================================
Files 1199 75 -1124
Lines 104568 8146 -96422
================================================
- Hits 48130 3644 -44486
+ Misses 52200 4088 -48112
+ Partials 4238 414 -3824
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
| return vote, true | ||
| } | ||
| ======= | ||
| return votes.GetByIndex(int32(index)) //nolint:gosec // index is bounded by validator set size which fits in int32 |
Check warning
Code scanning / CodeQL
Unreachable statement Warning
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
To fix this, we should resolve the bad merge in PickVoteToSend by ensuring there is exactly one return path inside the if index, ok := ...; ok block that both matches the function’s return signature and is reachable. The simplest way that preserves existing behavior is to keep the HEAD logic (which already returns (*types.Vote, bool) and checks for nil) and delete the unreachable return votes.GetByIndex(int32(index)) line (and its conflict markers). This maintains the previous semantics: pick a random index, get the vote, return (vote, true) if non-nil, otherwise fall through and eventually return (nil, false).
Concretely, in sei-tendermint/internal/consensus/peer_state.go, within func (ps *PeerState) PickVoteToSend, replace the whole conflict-marked region (lines 195–203) with the HEAD implementation only:
- After
if index, ok := votes.BitArray().Sub(psVotes).PickRandom(); ok {, keep:vote := votes.GetByIndex(int32(index))if vote != nil { return vote, true }
- Remove the merge markers
<<<<<<< HEAD,=======,>>>>>>> ...and the unreachablereturn votes.GetByIndex(int32(index))line.
No additional imports or helper methods are necessary.
| @@ -193,14 +193,10 @@ | ||
| } | ||
|
|
||
| if index, ok := votes.BitArray().Sub(psVotes).PickRandom(); ok { | ||
| <<<<<<< HEAD | ||
| vote := votes.GetByIndex(int32(index)) | ||
| if vote != nil { | ||
| return vote, true | ||
| } | ||
| ======= | ||
| return votes.GetByIndex(int32(index)) //nolint:gosec // index is bounded by validator set size which fits in int32 | ||
| >>>>>>> d201e89 (fix to ProposalPOLMessage poisoning (CON-222) (#3129)) | ||
| } | ||
| return nil, false | ||
| } |
| if ok { | ||
| return utils.Some(val) | ||
| } |
Check warning
Code scanning / CodeQL
Unreachable statement Warning
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
In general, unreachable code created by merge conflicts is fixed by resolving the conflict and retaining a single, consistent control-flow path with no dead branches after unconditional returns. Here we must choose and keep one implementation of validatorAtHeight’s final lookup in valsWithH and delete the other, removing the merge markers as well.
The best fix that preserves existing functionality and matches the current Tendermint API is to keep the simpler HEAD version:
_, val := valsWithH.GetByAddress(privValAddress)
return valand delete the alternative branch that expects an ok flag and uses utils.Some / none, along with the conflict markers. This is because: (1) elsewhere in Tendermint, ValidatorSet.GetByAddress commonly returns (index int, val *Validator) without ok; (2) we have no context or imports here for utils.Some or none, so introducing them would change functionality and dependencies; and (3) the rest of the function already returns *types.Validator or nil, matching the HEAD version’s behavior.
Concretely, in sei-tendermint/internal/rpc/core/status.go, in validatorAtHeight, replace the entire conflict block from line 135 through 144 (inclusive) with the two-line lookup and return:
_, val := valsWithH.GetByAddress(privValAddress)
return valNo new imports, methods, or definitions are required.
| @@ -132,14 +132,6 @@ | ||
| } | ||
| } | ||
|
|
||
| <<<<<<< HEAD | ||
| _, val := valsWithH.GetByAddress(privValAddress) | ||
| return val | ||
| ======= | ||
| _, val, ok := valsWithH.GetByAddress(privValAddress) | ||
| if ok { | ||
| return utils.Some(val) | ||
| } | ||
| return none | ||
| >>>>>>> d201e89 (fix to ProposalPOLMessage poisoning (CON-222) (#3129)) | ||
| } |
| if !ok { | ||
| panic(fmt.Errorf("too many signatures")) | ||
| } |
Check warning
Code scanning / CodeQL
Unreachable statement Warning
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
In general, the problem should be fixed by resolving the merge conflict in Commit.ToVoteSet and keeping a single, consistent use of commit.GetVote. The safer, more robust variant is the one that captures both vote and ok and panics if ok is false; this preserves the intended functionality of guarding against too many signatures and ensures that all statements in the function are reachable.
Concretely, in sei-tendermint/types/block.go, in the ToVoteSet method, remove all merge conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...) and the old single-value assignment vote := commit.GetVote(int32(idx)). Keep only the two-value assignment and the if !ok guard:
- Replace the entire conflict region (lines 961–968 in the snippet) with:
vote, ok := commit.GetVote(int32(idx)) //nolint:gosec // idx is bounded by commit.Signatures length which fits in int32 if !ok { panic(fmt.Errorf("too many signatures")) }
No additional imports or new methods are needed; fmt is already imported and GetVote is assumed to be defined elsewhere in this file or package. This change does not alter the external behavior beyond ensuring the intended safety check is active, and it removes the unreachable-statement situation that CodeQL flagged.
| @@ -958,14 +958,10 @@ | ||
| if cs.BlockIDFlag == BlockIDFlagAbsent { | ||
| continue // OK, some precommits can be missing. | ||
| } | ||
| <<<<<<< HEAD | ||
| vote := commit.GetVote(int32(idx)) | ||
| ======= | ||
| vote, ok := commit.GetVote(int32(idx)) //nolint:gosec // idx is bounded by commit.Signatures length which fits in int32 | ||
| if !ok { | ||
| panic(fmt.Errorf("too many signatures")) | ||
| } | ||
| >>>>>>> d201e89 (fix to ProposalPOLMessage poisoning (CON-222) (#3129)) | ||
| if err := vote.ValidateBasic(); err != nil { | ||
| panic(fmt.Errorf("failed to validate vote reconstructed from commit: %w", err)) | ||
| } |
| if !ok { | ||
| panic("VoteSignBytes() failed unexpectedly") | ||
| } |
Check warning
Code scanning / CodeQL
Unreachable statement Warning
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
In general, the problem is caused by an unresolved merge conflict where one branch used voteSignBytes := commit.VoteSignBytes(...) (single return value) and another used voteSignBytes, ok := commit.VoteSignBytes(...); if !ok { panic(...) }. The presence of conflict markers and two competing implementations leads CodeQL to see one of the branches as unreachable or dead. To fix this without changing functionality, we must resolve the conflict by choosing the correct implementation and removing the other, ensuring that VoteSignBytes is called with its correct signature and that the ok check is reachable.
The best fix here is to keep the safer, more recent implementation that handles the two return values: replace the entire conflict region (lines 209–216) with a single, clean block:
voteSignBytes, ok := commit.VoteSignBytes(chainID, int32(idx)) //nolint:gosec // idx is bounded by len(commit.Signatures) which is validated against validator set size
if !ok {
panic("VoteSignBytes() failed unexpectedly")
}This removes the <<<<<<<, =======, and >>>>>>> markers, and removes the older single‑value assignment. It also keeps the semantics implied by the comment: idx is safe, and VoteSignBytes is not expected to fail; if it does, we panic. No additional imports, methods, or definitions are required, since fmt and other imports are already present, and panic is a built‑in. This change restores reachability and keeps the intended behavior of the batch verification logic.
| @@ -206,14 +206,10 @@ | ||
| } | ||
|
|
||
| // Validate signature. | ||
| <<<<<<< HEAD | ||
| voteSignBytes := commit.VoteSignBytes(chainID, int32(idx)) | ||
| ======= | ||
| voteSignBytes, ok := commit.VoteSignBytes(chainID, int32(idx)) //nolint:gosec // idx is bounded by len(commit.Signatures) which is validated against validator set size | ||
| if !ok { | ||
| panic("VoteSignBytes() failed unexpectedly") | ||
| } | ||
| >>>>>>> d201e89 (fix to ProposalPOLMessage poisoning (CON-222) (#3129)) | ||
|
|
||
| // add the key, sig and message to the verifier | ||
| if err := bv.Add(val.PubKey, voteSignBytes, commitSig.Signature); err != nil { |
| if !ok { | ||
| panic("VoteSignBytes() failed unexpectedly") | ||
| } |
Check warning
Code scanning / CodeQL
Unreachable statement Warning
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
In general, the problem arises because the file still contains both sides of a merge conflict: an old implementation using VoteSignBytes as a single‑value function and a new implementation where VoteSignBytes returns (bytes, ok) plus more detailed signature handling. This leaves the control flow ill‑defined and can cause the static analyzer to treat parts of the newer code (including if !ok { ... }) as unreachable or otherwise inconsistent. The correct fix is to resolve the conflict by deleting the obsolete branch and conflict markers, leaving a single coherent implementation.
Concretely, in sei-tendermint/types/validation.go within verifyCommitSingle, you should remove the <<<<<<< HEAD / ======= / >>>>>>> markers and the old block:
voteSignBytes = commit.VoteSignBytes(chainID, int32(idx))
if !val.PubKey.VerifySignature(voteSignBytes, commitSig.Signature) {
return errBadSig{fmt.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature)}
}and keep the newer block that uses (voteSignBytes, ok), panics on failure, extracts the signature via commitSig.Signature.Get(), and verifies with val.PubKey.Verify. No new imports or helper methods are required; everything used in the new block is already referenced in the file. This preserves existing intended functionality (the more defensive verification logic) while eliminating the unreachable/duplicate code path and resolving the analyzer’s complaint.
| @@ -316,12 +316,6 @@ | ||
| seenVals[valIdx] = idx | ||
| } | ||
|
|
||
| <<<<<<< HEAD | ||
| voteSignBytes = commit.VoteSignBytes(chainID, int32(idx)) | ||
|
|
||
| if !val.PubKey.VerifySignature(voteSignBytes, commitSig.Signature) { | ||
| return errBadSig{fmt.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature)} | ||
| ======= | ||
| voteSignBytes, ok := commit.VoteSignBytes(chainID, int32(idx)) //nolint:gosec // idx is bounded by len(commit.Signatures) which is validated against validator set size | ||
| if !ok { | ||
| panic("VoteSignBytes() failed unexpectedly") | ||
| @@ -332,7 +326,6 @@ | ||
| } | ||
| if err := val.PubKey.Verify(voteSignBytes, sig); err != nil { | ||
| return errBadSig{fmt.Errorf("wrong signature (#%d): %v", idx, sig)} | ||
| >>>>>>> d201e89 (fix to ProposalPOLMessage poisoning (CON-222) (#3129)) | ||
| } | ||
|
|
||
| // If this signature counts then add the voting power of the validator |
| <<<<<<< HEAD | ||
| return int32(idx), val.Copy() | ||
| ======= | ||
| return int32(idx), val.Copy(), true //nolint:gosec // validator set size is consensus-bounded, fits in int32 |
Check warning
Code scanning / CodeQL
Unreachable statement Warning
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
In general, to fix an unreachable-statement issue caused by merge conflicts, you must resolve the conflict by removing the conflict markers and retaining only the logically correct, type-consistent branch. Any remaining returns must match the function’s declared return types and be reachable under the desired control flow.
For this specific case in sei-tendermint/types/validator_set.go, the function GetByAddress is declared to return (index int32, val *Validator, ok bool). The body currently contains merge conflict markers around two alternative returns. The HEAD variant returns only two values, which is invalid; the other variant returns three values including a boolean true flag and carries a useful comment (//nolint:gosec ...). The best fix that preserves existing functionality is to delete the conflict markers and the invalid two-value return, keep the three-value return, and leave the rest of the function unchanged. The final return 0, nil, false at the end of the function already matches the signature and should stay as-is. No new imports, methods, or definitions are required.
Concretely:
- In
GetByAddress, replace the entire conflict-marked region (lines 281–285) with a singlereturn int32(idx), val.Copy(), trueline (including the existing comment). - Ensure all conflict markers (
<<<<<<<,=======,>>>>>>>) are removed so the file is valid Go code.
| @@ -278,11 +278,7 @@ | ||
| func (vals *ValidatorSet) GetByAddress(address []byte) (index int32, val *Validator, ok bool) { | ||
| for idx, val := range vals.Validators { | ||
| if bytes.Equal(val.Address, address) { | ||
| <<<<<<< HEAD | ||
| return int32(idx), val.Copy() | ||
| ======= | ||
| return int32(idx), val.Copy(), true //nolint:gosec // validator set size is consensus-bounded, fits in int32 | ||
| >>>>>>> d201e89 (fix to ProposalPOLMessage poisoning (CON-222) (#3129)) | ||
| } | ||
| } | ||
| return 0, nil, false |
Backport of #3129 to
release/v6.3.