Skip to content

Commit 3160928

Browse files
teddydingmelekes
andauthored
Merge commit from fork (#37)
Co-authored-by: Anton Kaliaev <[email protected]>
1 parent cfee87f commit 3160928

File tree

5 files changed

+84
-0
lines changed

5 files changed

+84
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- `[consensus]` Do not panic if the validator index of a `Vote` message is out
2+
of bounds, when vote extensions are enabled
3+
([\#ABC-0021](https://github.com/cometbft/cometbft/security/advisories/GHSA-p7mv-53f2-4cwj))

consensus/errors.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package consensus
2+
3+
type ErrInvalidVote struct {
4+
Reason string
5+
}
6+
7+
func (e ErrInvalidVote) Error() string {
8+
return "invalid vote: " + e.Reason
9+
}

consensus/reactor_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/cometbft/cometbft/libs/bytes"
2727
"github.com/cometbft/cometbft/libs/json"
2828
"github.com/cometbft/cometbft/libs/log"
29+
cmtrand "github.com/cometbft/cometbft/libs/rand"
2930
cmtsync "github.com/cometbft/cometbft/libs/sync"
3031
mempl "github.com/cometbft/cometbft/mempool"
3132
"github.com/cometbft/cometbft/p2p"
@@ -1126,3 +1127,39 @@ func TestMarshalJSONPeerState(t *testing.T) {
11261127
"block_parts":"0"}
11271128
}`, string(data))
11281129
}
1130+
1131+
func TestVoteMessageValidateBasic(t *testing.T) {
1132+
_, vss := randState(2)
1133+
1134+
randBytes := cmtrand.Bytes(tmhash.Size)
1135+
blockID := types.BlockID{
1136+
Hash: randBytes,
1137+
PartSetHeader: types.PartSetHeader{
1138+
Total: 1,
1139+
Hash: randBytes,
1140+
},
1141+
}
1142+
vote := signVote(vss[1], cmtproto.PrecommitType, randBytes, blockID.PartSetHeader, true)
1143+
1144+
testCases := []struct {
1145+
malleateFn func(*VoteMessage)
1146+
expErr string
1147+
}{
1148+
{func(_ *VoteMessage) {}, ""},
1149+
{func(msg *VoteMessage) { msg.Vote.ValidatorIndex = -1 }, "negative ValidatorIndex"},
1150+
// INVALID, but passes ValidateBasic, since the method does not know the number of active validators
1151+
{func(msg *VoteMessage) { msg.Vote.ValidatorIndex = 1000 }, ""},
1152+
}
1153+
1154+
for i, tc := range testCases {
1155+
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
1156+
msg := &VoteMessage{vote}
1157+
1158+
tc.malleateFn(msg)
1159+
err := msg.ValidateBasic()
1160+
if tc.expErr != "" && assert.Error(t, err) { //nolint:testifylint // require.Error doesn't work with the conditional here
1161+
assert.Contains(t, err.Error(), tc.expErr)
1162+
}
1163+
})
1164+
}
1165+
}

consensus/state.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,6 +2254,14 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error
22542254
// Here, we verify the signature of the vote extension included in the vote
22552255
// message.
22562256
_, val := cs.state.Validators.GetByIndex(vote.ValidatorIndex)
2257+
if val == nil { // TODO: we should disconnect from this malicious peer
2258+
valsCount := cs.state.Validators.Size()
2259+
cs.Logger.Info("Peer sent us vote with invalid ValidatorIndex",
2260+
"peer", peerID,
2261+
"validator_index", vote.ValidatorIndex,
2262+
"len_validators", valsCount)
2263+
return added, ErrInvalidVote{Reason: fmt.Sprintf("ValidatorIndex %d is out of bounds [0, %d)", vote.ValidatorIndex, valsCount)}
2264+
}
22572265
if err := vote.VerifyExtension(cs.state.ChainID, val.PubKey); err != nil {
22582266
return false, err
22592267
}

consensus/state_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,6 +2541,33 @@ func TestVoteExtensionEnableHeight(t *testing.T) {
25412541
}
25422542
}
25432543

2544+
// TestStateDoesntCrashOnInvalidVote tests that the state does not crash when
2545+
// receiving an invalid vote. In particular, one with the incorrect
2546+
// ValidatorIndex.
2547+
func TestStateDoesntCrashOnInvalidVote(t *testing.T) {
2548+
cs, vss := randState(2)
2549+
height, round := cs.Height, cs.Round
2550+
// create dummy peer
2551+
peer := p2pmock.NewPeer(nil)
2552+
2553+
startTestRound(cs, height, round)
2554+
2555+
vote := signVote(vss[1], cmtproto.PrecommitType, nil, types.PartSetHeader{}, true)
2556+
// Non-existent validator index
2557+
vote.ValidatorIndex = int32(len(vss))
2558+
2559+
voteMessage := &VoteMessage{vote}
2560+
assert.NotPanics(t, func() {
2561+
cs.handleMsg(msgInfo{voteMessage, peer.ID()})
2562+
})
2563+
2564+
added, err := cs.AddVote(vote, peer.ID())
2565+
assert.False(t, added)
2566+
assert.NoError(t, err)
2567+
// TODO: uncomment once we punish peer and return an error
2568+
// assert.Equal(t, ErrInvalidVote{Reason: "ValidatorIndex 2 is out of bounds [0, 2)"}, err)
2569+
}
2570+
25442571
// 4 vals, 3 Nil Precommits at P0
25452572
// What we want:
25462573
// P0 waits for timeoutPrecommit before starting next round

0 commit comments

Comments
 (0)