Skip to content

Commit 3a023da

Browse files
authored
Merge commit from fork
1 parent deef97f commit 3a023da

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
@@ -2192,6 +2192,14 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error
21922192
// Here, we verify the signature of the vote extension included in the vote
21932193
// message.
21942194
_, val := cs.state.Validators.GetByIndex(vote.ValidatorIndex)
2195+
if val == nil { // TODO: we should disconnect from this malicious peer
2196+
valsCount := cs.state.Validators.Size()
2197+
cs.Logger.Info("Peer sent us vote with invalid ValidatorIndex",
2198+
"peer", peerID,
2199+
"validator_index", vote.ValidatorIndex,
2200+
"len_validators", valsCount)
2201+
return added, ErrInvalidVote{Reason: fmt.Sprintf("ValidatorIndex %d is out of bounds [0, %d)", vote.ValidatorIndex, valsCount)}
2202+
}
21952203
if err := vote.VerifyExtension(cs.state.ChainID, val.PubKey); err != nil {
21962204
return false, err
21972205
}

consensus/state_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,6 +1938,33 @@ func TestVoteExtensionEnableHeight(t *testing.T) {
19381938
}
19391939
}
19401940

1941+
// TestStateDoesntCrashOnInvalidVote tests that the state does not crash when
1942+
// receiving an invalid vote. In particular, one with the incorrect
1943+
// ValidatorIndex.
1944+
func TestStateDoesntCrashOnInvalidVote(t *testing.T) {
1945+
cs, vss := randState(2)
1946+
height, round := cs.Height, cs.Round
1947+
// create dummy peer
1948+
peer := p2pmock.NewPeer(nil)
1949+
1950+
startTestRound(cs, height, round)
1951+
1952+
vote := signVote(vss[1], cmtproto.PrecommitType, nil, types.PartSetHeader{}, true)
1953+
// Non-existent validator index
1954+
vote.ValidatorIndex = int32(len(vss))
1955+
1956+
voteMessage := &VoteMessage{vote}
1957+
assert.NotPanics(t, func() {
1958+
cs.handleMsg(msgInfo{voteMessage, peer.ID()})
1959+
})
1960+
1961+
added, err := cs.AddVote(vote, peer.ID())
1962+
assert.False(t, added)
1963+
assert.NoError(t, err)
1964+
// TODO: uncomment once we punish peer and return an error
1965+
// assert.Equal(t, ErrInvalidVote{Reason: "ValidatorIndex 2 is out of bounds [0, 2)"}, err)
1966+
}
1967+
19411968
// 4 vals, 3 Nil Precommits at P0
19421969
// What we want:
19431970
// P0 waits for timeoutPrecommit before starting next round

0 commit comments

Comments
 (0)