Skip to content

Commit ef0833b

Browse files
mattac21rootulp
authored andcommitted
Merge commit from fork
* add VaidateBasic to BitArray to ensure Bits and len(Elems) are valid * call ValidateBasic on BitArrays when receiving as a msg from exteranl nodes * enfore SetIndex is not setting out of bounds * add guard to getNumTrueIndices getNumTrueIndices will index out of bounds if Bits and Elems have a mismatch where len(elems) != (bits+63)/64, this guard makes it simply return 0 if this mismatch is present * changelog * fix missing import for v0.38.x * update changelog for release of v0.38.19 * remove duplicate bug fixes from unreleased * fix changelog date * fix lint * fix expected error string in test
1 parent 2309005 commit ef0833b

File tree

5 files changed

+156
-6
lines changed

5 files changed

+156
-6
lines changed

CHANGELOG.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,51 @@
11
# CHANGELOG
22

3+
## UNRELEASED
4+
5+
### DEPENDENCIES
6+
7+
### BUG FIXES
8+
9+
### IMPROVEMENTS
10+
11+
### FEATURES
12+
13+
### BUG-FIXES
14+
15+
### STATE-BREAKING
16+
17+
### API-BREAKING
18+
19+
## v0.38.19
20+
21+
*October 14, 2025*
22+
23+
This release fixes two security issues, including ([ASA-2025-003](https://github.com/cometbft/cometbft/security/advisories/GHSA-hrhf-2vcr-ghch)).
24+
Users are encouraged to upgrade as soon as possible.
25+
26+
Additionally included is a bug fix to properly prune extended commits (with
27+
vote extensions).
28+
29+
### BUG-FIXES
30+
31+
- `[consensus]` Reject oversized proposals
32+
([\#5324](https://github.com/cometbft/cometbft/pull/5324))
33+
- `[store]` Prune extended commits properly
34+
([5275](https://github.com/cometbft/cometbft/issues/5275))
35+
- `[bits]` Validate BitArray mismatched Bits and Elems length
36+
([ASA-2025-003](https://github.com/cometbft/cometbft/security/advisories/GHSA-hrhf-2vcr-ghch))
37+
38+
## v0.38.18
39+
40+
*July 3, 2025*
41+
42+
Adds precommit metrics and reindex CLI command.
43+
44+
### IMPROVEMENTS
45+
46+
- Adds metrics that emit precommit data; precommit quorum delay from proposal, and precommit vote count and stake weight within timeout commit period.
47+
([\#5251](https://github.com/cometbft/cometbft/issues/5251))
48+
349
## v0.38.17
450

551
*February 3, 2025*
@@ -881,4 +927,3 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/cosmos).
881927
## Previous changes
882928

883929
For changes released before the creation of CometBFT, please refer to the Tendermint Core [CHANGELOG.md](https://github.com/tendermint/tendermint/blob/a9feb1c023e172b542c972605311af83b777855b/CHANGELOG.md).
884-

consensus/reactor.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,9 @@ func (m *NewValidBlockMessage) ValidateBasic() error {
17851785
if err := m.BlockPartSetHeader.ValidateBasic(); err != nil {
17861786
return fmt.Errorf("wrong BlockPartSetHeader: %v", err)
17871787
}
1788+
if err := m.BlockParts.ValidateBasic(); err != nil {
1789+
return fmt.Errorf("validating BlockParts: %w", err)
1790+
}
17881791
if m.BlockParts.Size() == 0 {
17891792
return errors.New("empty blockParts")
17901793
}
@@ -1839,6 +1842,9 @@ func (m *ProposalPOLMessage) ValidateBasic() error {
18391842
if m.ProposalPOLRound < 0 {
18401843
return errors.New("negative ProposalPOLRound")
18411844
}
1845+
if err := m.ProposalPOL.ValidateBasic(); err != nil {
1846+
return fmt.Errorf("validating ProposalPOL: %w", err)
1847+
}
18421848
if m.ProposalPOL.Size() == 0 {
18431849
return errors.New("empty ProposalPOL bit array")
18441850
}
@@ -1984,6 +1990,9 @@ func (m *VoteSetBitsMessage) ValidateBasic() error {
19841990
if err := m.BlockID.ValidateBasic(); err != nil {
19851991
return fmt.Errorf("wrong BlockID: %v", err)
19861992
}
1993+
if err := m.Votes.ValidateBasic(); err != nil {
1994+
return fmt.Errorf("validating Votes: %w", err)
1995+
}
19871996
// NOTE: Votes.Size() can be zero if the node does not have any
19881997
if m.Votes.Size() > types.MaxVotesCount {
19891998
return fmt.Errorf("votes bit array is too big: %d, max: %d", m.Votes.Size(), types.MaxVotesCount)

consensus/reactor_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,14 @@ func TestNewValidBlockMessageValidateBasic(t *testing.T) {
885885
func(msg *NewValidBlockMessage) { msg.BlockParts = bits.NewBitArray(int(types.MaxBlockPartsCount) + 1) },
886886
"blockParts bit array size 1602 not equal to BlockPartSetHeader.Total 1",
887887
},
888+
{
889+
func(msg *NewValidBlockMessage) { msg.BlockParts.Elems = nil },
890+
"mismatch between specified number of bits 1, and number of elements 0, expected 1 elements",
891+
},
892+
{
893+
func(msg *NewValidBlockMessage) { msg.BlockParts.Bits = 500 },
894+
"mismatch between specified number of bits 500, and number of elements 1, expected 8 elements",
895+
},
888896
}
889897

890898
for i, tc := range testCases {
@@ -921,6 +929,14 @@ func TestProposalPOLMessageValidateBasic(t *testing.T) {
921929
func(msg *ProposalPOLMessage) { msg.ProposalPOL = bits.NewBitArray(types.MaxVotesCount + 1) },
922930
"proposalPOL bit array is too big: 10001, max: 10000",
923931
},
932+
{
933+
func(msg *ProposalPOLMessage) { msg.ProposalPOL.Elems = nil },
934+
"mismatch between specified number of bits 1, and number of elements 0, expected 1 elements",
935+
},
936+
{
937+
func(msg *ProposalPOLMessage) { msg.ProposalPOL.Bits = 500 },
938+
"mismatch between specified number of bits 500, and number of elements 1, expected 8 elements",
939+
},
924940
}
925941

926942
for i, tc := range testCases {
@@ -1077,6 +1093,14 @@ func TestVoteSetBitsMessageValidateBasic(t *testing.T) {
10771093
func(msg *VoteSetBitsMessage) { msg.Votes = bits.NewBitArray(types.MaxVotesCount + 1) },
10781094
"votes bit array is too big: 10001, max: 10000",
10791095
},
1096+
{
1097+
func(msg *VoteSetBitsMessage) { msg.Votes.Elems = nil },
1098+
"mismatch between specified number of bits 1, and number of elements 0, expected 1 elements",
1099+
},
1100+
{
1101+
func(msg *VoteSetBitsMessage) { msg.Votes.Bits = 500 },
1102+
"mismatch between specified number of bits 500, and number of elements 1, expected 8 elements",
1103+
},
10801104
}
10811105

10821106
for i, tc := range testCases {

libs/bits/bit_array.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func NewBitArray(bits int) *BitArray {
2828
}
2929
return &BitArray{
3030
Bits: bits,
31-
Elems: make([]uint64, (bits+63)/64),
31+
Elems: make([]uint64, numElements(bits)),
3232
}
3333
}
3434

@@ -41,7 +41,7 @@ func NewBitArrayFromFn(bits int, fn func(int) bool) *BitArray {
4141
}
4242
bA := &BitArray{
4343
Bits: bits,
44-
Elems: make([]uint64, (bits+63)/64),
44+
Elems: make([]uint64, numElements(bits)),
4545
}
4646
for i := 0; i < bits; i++ {
4747
v := fn(i)
@@ -90,7 +90,7 @@ func (bA *BitArray) SetIndex(i int, v bool) bool {
9090
}
9191

9292
func (bA *BitArray) setIndex(i int, v bool) bool {
93-
if i >= bA.Bits {
93+
if i >= bA.Bits || i/64 >= len(bA.Elems) {
9494
return false
9595
}
9696
if v {
@@ -121,7 +121,7 @@ func (bA *BitArray) copy() *BitArray {
121121
}
122122

123123
func (bA *BitArray) copyBits(bits int) *BitArray {
124-
c := make([]uint64, (bits+63)/64)
124+
c := make([]uint64, numElements(bits))
125125
copy(c, bA.Elems)
126126
return &BitArray{
127127
Bits: bits,
@@ -282,6 +282,11 @@ func (bA *BitArray) PickRandom() (int, bool) {
282282
}
283283

284284
func (bA *BitArray) getNumTrueIndices() int {
285+
if bA.Size() == 0 || len(bA.Elems) == 0 || len(bA.Elems) != numElements(bA.Size()) {
286+
// size and elements must be valid to do this calc
287+
return 0
288+
}
289+
285290
count := 0
286291
numElems := len(bA.Elems)
287292
// handle all elements except the last one
@@ -495,3 +500,22 @@ func (bA *BitArray) FromProto(protoBitArray *cmtprotobits.BitArray) {
495500
bA.Elems = protoBitArray.Elems
496501
}
497502
}
503+
504+
// ValidateBasic validates a BitArray. Note that a nil BitArray and BitArray of
505+
// size 0 bits is valid. However the number of Bits and Elems be valid based on
506+
// each other.
507+
func (bA *BitArray) ValidateBasic() error {
508+
if bA == nil {
509+
return nil
510+
}
511+
512+
expectedElems := numElements(bA.Size())
513+
if expectedElems != len(bA.Elems) {
514+
return fmt.Errorf("mismatch between specified number of bits %d, and number of elements %d, expected %d elements", bA.Size(), len(bA.Elems), expectedElems)
515+
}
516+
return nil
517+
}
518+
519+
func numElements(bits int) int {
520+
return (bits + 63) / 64
521+
}

libs/bits/bit_array_test.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,28 @@ func TestGetNumTrueIndices(t *testing.T) {
173173
}
174174
}
175175

176+
func TestGetNumTrueIndicesInvalidStates(t *testing.T) {
177+
testCases := []struct {
178+
name string
179+
bA1 *BitArray
180+
exp int
181+
}{
182+
{"empty", &BitArray{}, 0},
183+
{"explicit 0 bits nil elements", &BitArray{Bits: 0, Elems: nil}, 0},
184+
{"explicit 0 bits 0 len elements", &BitArray{Bits: 0, Elems: make([]uint64, 0)}, 0},
185+
{"nil", nil, 0},
186+
{"with elements", NewBitArray(10), 0},
187+
{"more elements than bits specifies", &BitArray{Bits: 0, Elems: make([]uint64, 5)}, 0},
188+
{"less elements than bits specifies", &BitArray{Bits: 200, Elems: make([]uint64, 1)}, 0},
189+
}
190+
for _, tc := range testCases {
191+
t.Run(tc.name, func(t *testing.T) {
192+
n := tc.bA1.getNumTrueIndices()
193+
require.Equal(t, n, tc.exp)
194+
})
195+
}
196+
}
197+
176198
func TestGetNthTrueIndex(t *testing.T) {
177199
type testcase struct {
178200
Input string
@@ -226,7 +248,7 @@ func TestGetNthTrueIndex(t *testing.T) {
226248
}
227249
}
228250

229-
func TestBytes(_ *testing.T) {
251+
func TestBytes(t *testing.T) {
230252
bA := NewBitArray(4)
231253
bA.SetIndex(0, true)
232254
check := func(bA *BitArray, bz []byte) {
@@ -253,6 +275,10 @@ func TestBytes(_ *testing.T) {
253275
check(bA, []byte{0x80, 0x01})
254276
bA.SetIndex(9, true)
255277
check(bA, []byte{0x80, 0x03})
278+
279+
bA = NewBitArray(4)
280+
bA.Elems = nil
281+
require.False(t, bA.SetIndex(1, true))
256282
}
257283

258284
func TestEmptyFull(t *testing.T) {
@@ -371,6 +397,28 @@ func TestBitArrayProtoBuf(t *testing.T) {
371397
}
372398
}
373399

400+
func TestBitArrayValidateBasic(t *testing.T) {
401+
testCases := []struct {
402+
name string
403+
bA1 *BitArray
404+
expPass bool
405+
}{
406+
{"valid empty", &BitArray{}, true},
407+
{"valid explicit 0 bits nil elements", &BitArray{Bits: 0, Elems: nil}, true},
408+
{"valid explicit 0 bits 0 len elements", &BitArray{Bits: 0, Elems: make([]uint64, 0)}, true},
409+
{"valid nil", nil, true},
410+
{"valid with elements", NewBitArray(10), true},
411+
{"more elements than bits specifies", &BitArray{Bits: 0, Elems: make([]uint64, 5)}, false},
412+
{"less elements than bits specifies", &BitArray{Bits: 200, Elems: make([]uint64, 1)}, false},
413+
}
414+
for _, tc := range testCases {
415+
t.Run(tc.name, func(t *testing.T) {
416+
err := tc.bA1.ValidateBasic()
417+
require.Equal(t, err == nil, tc.expPass)
418+
})
419+
}
420+
}
421+
374422
// Tests that UnmarshalJSON doesn't crash when no bits are passed into the JSON.
375423
// See issue https://github.com/cometbft/cometbft/issues/2658
376424
func TestUnmarshalJSONDoesntCrashOnZeroBits(t *testing.T) {

0 commit comments

Comments
 (0)