Skip to content

Commit 3ab07f4

Browse files
roy-dydxmergify[bot]casonsergio-menaandynog
authored
Cherry-pick fix for ASA-2025-002 (#40)
* feat(consensus): additional sanity checks for the size of proposed blocks (backport cometbft#1408) (cometbft#2139) This is an automatic backport of pull request cometbft#1408 done by [Mergify](https://mergify.com). Cherry-pick of 28ad4d2 has failed: ``` On branch mergify/bp/v0.38.x/pr-1408 Your branch is up to date with 'origin/v0.38.x'. You are currently cherry-picking commit 28ad4d2. (fix conflicts and run "git cherry-pick --continue") (use "git cherry-pick --skip" to skip this patch) (use "git cherry-pick --abort" to cancel the cherry-pick operation) Changes to be committed: modified: consensus/state.go modified: consensus/state_test.go modified: crypto/merkle/proof.go modified: evidence/pool_test.go modified: state/execution_test.go modified: types/event_bus_test.go modified: types/part_set.go modified: types/part_set_test.go Unmerged paths: (use "git add/rm <file>..." as appropriate to mark resolution) deleted by us: internal/consensus/errors.go both modified: state/store_test.go both modified: store/store_test.go ``` To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally --- <details> <summary>Mergify commands and options</summary> <br /> More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport <destination>` will backport this PR on `<destination>` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com </details> --------- Co-authored-by: Daniel <[email protected]> Co-authored-by: Sergio Mena <[email protected]> Co-authored-by: Andy Nogueira <[email protected]> * Merge commit from fork --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Daniel <[email protected]> Co-authored-by: Sergio Mena <[email protected]> Co-authored-by: Andy Nogueira <[email protected]> Co-authored-by: Anton Kaliaev <[email protected]>
1 parent 2067b31 commit 3ab07f4

File tree

10 files changed

+117
-25
lines changed

10 files changed

+117
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- `[types]` Check that `Part.Index` equals `Part.Proof.Index`
2+
([ASA-2025-001](https://github.com/cometbft/cometbft/security/advisories/GHSA-r3r4-g7hq-pq4f))

Diff for: consensus/state.go

+10
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var (
3737
ErrInvalidProposalPOLRound = errors.New("error invalid proposal POL round")
3838
ErrAddingVote = errors.New("error adding vote")
3939
ErrSignatureFoundInPastBlocks = errors.New("found signature from the same key")
40+
ErrProposalTooManyParts = errors.New("proposal block has too many parts")
4041

4142
errPubKeyIsNotSet = errors.New("pubkey is not set. Look for \"Can't get private validator pubkey\" errors")
4243
)
@@ -1996,6 +1997,15 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal) error {
19961997
return ErrInvalidProposalSignature
19971998
}
19981999

2000+
// Validate the proposed block size, derived from its PartSetHeader
2001+
maxBytes := cs.state.ConsensusParams.Block.MaxBytes
2002+
if maxBytes == -1 {
2003+
maxBytes = int64(types.MaxBlockSizeBytes)
2004+
}
2005+
if int64(proposal.BlockID.PartSetHeader.Total) > (maxBytes-1)/int64(types.BlockPartSizeBytes)+1 {
2006+
return ErrProposalTooManyParts
2007+
}
2008+
19992009
proposal.Signature = p.Signature
20002010
cs.Proposal = proposal
20012011
// We don't update cs.ProposalBlockParts if it is already set.

Diff for: consensus/state_test.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ func TestStateBadProposal(t *testing.T) {
265265
}
266266

267267
func TestStateOversizedBlock(t *testing.T) {
268-
const maxBytes = 2000
268+
const maxBytes = int64(types.BlockPartSizeBytes)
269269

270270
for _, testCase := range []struct {
271271
name string
@@ -311,14 +311,21 @@ func TestStateOversizedBlock(t *testing.T) {
311311
totalBytes += len(part.Bytes)
312312
}
313313

314+
maxBlockParts := maxBytes / int64(types.BlockPartSizeBytes)
315+
if maxBytes > maxBlockParts*int64(types.BlockPartSizeBytes) {
316+
maxBlockParts++
317+
}
318+
numBlockParts := int64(propBlockParts.Total())
319+
314320
if err := cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil {
315321
t.Fatal(err)
316322
}
317323

318324
// start the machine
319325
startTestRound(cs1, height, round)
320326

321-
t.Log("Block Sizes;", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes)
327+
t.Log("Block Sizes;", "Limit", maxBytes, "Current", totalBytes)
328+
t.Log("Proposal Parts;", "Maximum", maxBlockParts, "Current", numBlockParts)
322329

323330
validateHash := propBlock.Hash()
324331
lockedRound := int32(1)
@@ -334,6 +341,11 @@ func TestStateOversizedBlock(t *testing.T) {
334341
ensurePrevote(voteCh, height, round)
335342
validatePrevote(t, cs1, round, vss[0], validateHash)
336343

344+
// Should not accept a Proposal with too many block parts
345+
if numBlockParts > maxBlockParts {
346+
require.Nil(t, cs1.Proposal)
347+
}
348+
337349
bps, err := propBlock.MakePartSet(partSize)
338350
require.NoError(t, err)
339351

Diff for: crypto/merkle/proof.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ const (
2424
// everything. This also affects the generalized proof system as
2525
// well.
2626
type Proof struct {
27-
Total int64 `json:"total"` // Total number of items.
28-
Index int64 `json:"index"` // Index of item to prove.
29-
LeafHash []byte `json:"leaf_hash"` // Hash of item value.
30-
Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child.
27+
Total int64 `json:"total"` // Total number of items.
28+
Index int64 `json:"index"` // Index of item to prove.
29+
LeafHash []byte `json:"leaf_hash"` // Hash of item value.
30+
Aunts [][]byte `json:"aunts,omitempty"` // Hashes from leaf's sibling to a root's child.
3131
}
3232

3333
// ProofsFromByteSlices computes inclusion proof for given items.

Diff for: evidence/pool_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,7 @@ func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) (*store.Blo
416416
block := state.MakeBlock(i, test.MakeNTxs(i, 1), lastCommit.ToCommit(), nil, state.Validators.Proposer.Address)
417417
block.Header.Time = defaultEvidenceTime.Add(time.Duration(i) * time.Minute)
418418
block.Header.Version = cmtversion.Consensus{Block: version.BlockProtocol, App: 1}
419-
const parts = 1
420-
partSet, err := block.MakePartSet(parts)
419+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
421420
if err != nil {
422421
return nil, err
423422
}

Diff for: state/execution_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import (
3636

3737
var (
3838
chainID = "execution_chain"
39-
testPartSize uint32 = 65536
39+
testPartSize uint32 = types.BlockPartSizeBytes
4040
)
4141

4242
func TestApplyBlock(t *testing.T) {

Diff for: store/store_test.go

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package store
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"os"
67
"runtime/debug"
@@ -154,10 +155,12 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
154155
}
155156
}
156157

157-
// save a block
158-
block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address)
159-
validPartSet, err := block.MakePartSet(2)
158+
// save a block big enough to have two block parts
159+
txs := []types.Tx{make([]byte, types.BlockPartSizeBytes)} // TX taking one block part alone
160+
block := state.MakeBlock(bs.Height()+1, txs, new(types.Commit), nil, state.Validators.GetProposer().Address)
161+
validPartSet, err := block.MakePartSet(types.BlockPartSizeBytes)
160162
require.NoError(t, err)
163+
require.GreaterOrEqual(t, validPartSet.Total(), uint32(2))
161164
part2 := validPartSet.GetPart(1)
162165

163166
seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now())
@@ -399,7 +402,7 @@ func TestSaveBlockWithExtendedCommitPanicOnAbsentExtension(t *testing.T) {
399402
block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
400403

401404
seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now())
402-
ps, err := block.MakePartSet(2)
405+
ps, err := block.MakePartSet(types.BlockPartSizeBytes)
403406
require.NoError(t, err)
404407
testCase.malleateCommit(seenCommit)
405408
if testCase.shouldPanic {
@@ -439,7 +442,7 @@ func TestLoadBlockExtendedCommit(t *testing.T) {
439442
h := bs.Height() + 1
440443
block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
441444
seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now())
442-
ps, err := block.MakePartSet(2)
445+
ps, err := block.MakePartSet(types.BlockPartSizeBytes)
443446
require.NoError(t, err)
444447
if testCase.saveExtended {
445448
bs.SaveBlockWithExtendedCommit(block, ps, seenCommit)
@@ -468,7 +471,7 @@ func TestLoadBaseMeta(t *testing.T) {
468471

469472
for h := int64(1); h <= 10; h++ {
470473
block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
471-
partSet, err := block.MakePartSet(2)
474+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
472475
require.NoError(t, err)
473476
seenCommit := makeTestExtCommit(h, cmttime.Now())
474477
bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit)
@@ -513,7 +516,7 @@ func TestLoadBlockPart(t *testing.T) {
513516

514517
// 3. A good block serialized and saved to the DB should be retrievable
515518
block := state.MakeBlock(height, nil, new(types.Commit), nil, state.Validators.GetProposer().Address)
516-
partSet, err := block.MakePartSet(2)
519+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
517520
require.NoError(t, err)
518521
part1 := partSet.GetPart(0)
519522

@@ -524,7 +527,13 @@ func TestLoadBlockPart(t *testing.T) {
524527
gotPart, _, panicErr := doFn(loadPart)
525528
require.Nil(t, panicErr, "an existent and proper block should not panic")
526529
require.Nil(t, res, "a properly saved block should return a proper block")
527-
require.Equal(t, gotPart.(*types.Part), part1,
530+
531+
// Having to do this because of https://github.com/stretchr/testify/issues/1141
532+
gotPartJSON, err := json.Marshal(gotPart.(*types.Part))
533+
require.NoError(t, err)
534+
part1JSON, err := json.Marshal(part1)
535+
require.NoError(t, err)
536+
require.JSONEq(t, string(gotPartJSON), string(part1JSON),
528537
"expecting successful retrieval of previously saved block")
529538
}
530539

@@ -552,7 +561,7 @@ func TestPruneBlocks(t *testing.T) {
552561
// make more than 1000 blocks, to test batch deletions
553562
for h := int64(1); h <= 1500; h++ {
554563
block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
555-
partSet, err := block.MakePartSet(2)
564+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
556565
require.NoError(t, err)
557566
seenCommit := makeTestExtCommit(h, cmttime.Now())
558567
bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit)
@@ -681,7 +690,7 @@ func TestLoadBlockMetaByHash(t *testing.T) {
681690
bs := NewBlockStore(dbm.NewMemDB())
682691

683692
b1 := state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address)
684-
partSet, err := b1.MakePartSet(2)
693+
partSet, err := b1.MakePartSet(types.BlockPartSizeBytes)
685694
require.NoError(t, err)
686695
seenCommit := makeTestExtCommit(1, cmttime.Now())
687696
bs.SaveBlock(b1, partSet, seenCommit.ToCommit())
@@ -698,7 +707,7 @@ func TestBlockFetchAtHeight(t *testing.T) {
698707
require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
699708
block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address)
700709

701-
partSet, err := block.MakePartSet(2)
710+
partSet, err := block.MakePartSet(types.BlockPartSizeBytes)
702711
require.NoError(t, err)
703712
seenCommit := makeTestExtCommit(block.Header.Height, cmttime.Now())
704713
bs.SaveBlockWithExtendedCommit(block, partSet, seenCommit)

Diff for: types/event_bus_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func TestEventBusPublishEventNewBlock(t *testing.T) {
9696
}()
9797

9898
var ps *PartSet
99-
ps, err = block.MakePartSet(MaxBlockSizeBytes)
99+
ps, err = block.MakePartSet(BlockPartSizeBytes)
100100
require.NoError(t, err)
101101

102102
err = eventBus.PublishEventNewBlock(EventDataNewBlock{

Diff for: types/part_set.go

+30-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,23 @@ import (
1818
var (
1919
ErrPartSetUnexpectedIndex = errors.New("error part set unexpected index")
2020
ErrPartSetInvalidProof = errors.New("error part set invalid proof")
21+
ErrPartTooBig = errors.New("error part size too big")
22+
ErrPartInvalidSize = errors.New("error inner part with invalid size")
2123
)
2224

25+
// ErrInvalidPart is an error type for invalid parts.
26+
type ErrInvalidPart struct {
27+
Reason error
28+
}
29+
30+
func (e ErrInvalidPart) Error() string {
31+
return fmt.Sprintf("invalid part: %v", e.Reason)
32+
}
33+
34+
func (e ErrInvalidPart) Unwrap() error {
35+
return e.Reason
36+
}
37+
2338
type Part struct {
2439
Index uint32 `json:"index"`
2540
Bytes cmtbytes.HexBytes `json:"bytes"`
@@ -29,10 +44,17 @@ type Part struct {
2944
// ValidateBasic performs basic validation.
3045
func (part *Part) ValidateBasic() error {
3146
if len(part.Bytes) > int(BlockPartSizeBytes) {
32-
return fmt.Errorf("too big: %d bytes, max: %d", len(part.Bytes), BlockPartSizeBytes)
47+
return ErrPartTooBig
48+
}
49+
// All parts except the last one should have the same constant size.
50+
if int64(part.Index) < part.Proof.Total-1 && len(part.Bytes) != int(BlockPartSizeBytes) {
51+
return ErrPartInvalidSize
52+
}
53+
if int64(part.Index) != part.Proof.Index {
54+
return ErrInvalidPart{Reason: fmt.Errorf("part index %d != proof index %d", part.Index, part.Proof.Index)}
3355
}
3456
if err := part.Proof.ValidateBasic(); err != nil {
35-
return fmt.Errorf("wrong Proof: %w", err)
57+
return ErrInvalidPart{Reason: fmt.Errorf("wrong Proof: %w", err)}
3658
}
3759
return nil
3860
}
@@ -269,6 +291,7 @@ func (ps *PartSet) Total() uint32 {
269291
return ps.total
270292
}
271293

294+
// CONTRACT: part is validated using ValidateBasic.
272295
func (ps *PartSet) AddPart(part *Part) (bool, error) {
273296
// TODO: remove this? would be preferable if this only returned (false, nil)
274297
// when its a duplicate block part
@@ -289,6 +312,11 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) {
289312
return false, nil
290313
}
291314

315+
// The proof should be compatible with the number of parts.
316+
if part.Proof.Total != int64(ps.total) {
317+
return false, ErrPartSetInvalidProof
318+
}
319+
292320
// Check hash proof
293321
if part.Proof.Verify(ps.Hash(), part.Bytes) != nil {
294322
return false, ErrPartSetInvalidProof

Diff for: types/part_set_test.go

+34-2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ func TestWrongProof(t *testing.T) {
8686
if added || err == nil {
8787
t.Errorf("expected to fail adding a part with bad bytes.")
8888
}
89+
90+
// Test adding a part with wrong proof index.
91+
part = partSet.GetPart(2)
92+
part.Proof.Index = 1
93+
added, err = partSet2.AddPart(part)
94+
if added || err == nil {
95+
t.Errorf("expected to fail adding a part with bad proof index.")
96+
}
97+
98+
// Test adding a part with wrong proof total.
99+
part = partSet.GetPart(3)
100+
part.Proof.Total = int64(partSet.Total() - 1)
101+
added, err = partSet2.AddPart(part)
102+
if added || err == nil {
103+
t.Errorf("expected to fail adding a part with bad proof total.")
104+
}
89105
}
90106

91107
func TestPartSetHeaderValidateBasic(t *testing.T) {
@@ -109,20 +125,36 @@ func TestPartSetHeaderValidateBasic(t *testing.T) {
109125
}
110126
}
111127

112-
func TestPartValidateBasic(t *testing.T) {
128+
func TestPart_ValidateBasic(t *testing.T) {
113129
testCases := []struct {
114130
testName string
115131
malleatePart func(*Part)
116132
expectErr bool
117133
}{
118134
{"Good Part", func(pt *Part) {}, false},
119135
{"Too big part", func(pt *Part) { pt.Bytes = make([]byte, BlockPartSizeBytes+1) }, true},
136+
{"Good small last part", func(pt *Part) {
137+
pt.Index = 1
138+
pt.Bytes = make([]byte, BlockPartSizeBytes-1)
139+
pt.Proof.Total = 2
140+
pt.Proof.Index = 1
141+
}, false},
142+
{"Too small inner part", func(pt *Part) {
143+
pt.Index = 0
144+
pt.Bytes = make([]byte, BlockPartSizeBytes-1)
145+
pt.Proof.Total = 2
146+
}, true},
120147
{"Too big proof", func(pt *Part) {
121148
pt.Proof = merkle.Proof{
122-
Total: 1,
149+
Total: 2,
123150
Index: 1,
124151
LeafHash: make([]byte, 1024*1024),
125152
}
153+
pt.Index = 1
154+
}, true},
155+
{"Index mismatch", func(pt *Part) {
156+
pt.Index = 1
157+
pt.Proof.Index = 0
126158
}, true},
127159
}
128160

0 commit comments

Comments
 (0)