From 2c79be046dc11ca53741e08906a594b5d7d0c735 Mon Sep 17 00:00:00 2001 From: beer-1 Date: Wed, 20 May 2026 18:53:16 +0900 Subject: [PATCH] disallow unknown fields --- abci/strategies/codec/codec.go | 28 ++++++++++++--- abci/strategies/codec/codec_test.go | 54 +++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/abci/strategies/codec/codec.go b/abci/strategies/codec/codec.go index 0bab2a46..13b430af 100644 --- a/abci/strategies/codec/codec.go +++ b/abci/strategies/codec/codec.go @@ -3,6 +3,7 @@ package codec import ( "bytes" "compress/zlib" + "fmt" "io" cometabci "github.com/cometbft/cometbft/abci/types" @@ -54,7 +55,13 @@ func (codec *DefaultVoteExtensionCodec) Encode(ve vetypes.OracleVoteExtension) ( func (codec *DefaultVoteExtensionCodec) Decode(bz []byte) (vetypes.OracleVoteExtension, error) { var ve vetypes.OracleVoteExtension - return ve, ve.Unmarshal(bz) + if err := ve.Unmarshal(bz); err != nil { + return ve, err + } + if len(bz) != ve.Size() { + return ve, fmt.Errorf("non-canonical OracleVoteExtension encoding: got %d bytes, expected %d", len(bz), ve.Size()) + } + return ve, nil } type Compressor interface { @@ -95,14 +102,25 @@ func (c *ZLibCompressor) Decompress(bz []byte) ([]byte, error) { if len(bz) == 0 { return nil, nil } - r, err := zlib.NewReader(bytes.NewReader(bz)) + br := bytes.NewReader(bz) + r, err := zlib.NewReader(br) if err != nil { return nil, err } - r.Close() - // read bytes and return - return io.ReadAll(r) + decompressed, err := io.ReadAll(r) + if err != nil { + r.Close() + return nil, err + } + if err := r.Close(); err != nil { + return nil, err + } + if br.Len() != 0 { + return nil, fmt.Errorf("zlib stream contains %d trailing bytes", br.Len()) + } + + return decompressed, nil } // ZStdCompressor is a Compressor that uses zstd to compress / decompress byte arrays, this object is thread-safe. diff --git a/abci/strategies/codec/codec_test.go b/abci/strategies/codec/codec_test.go index ef2909e1..49c33beb 100644 --- a/abci/strategies/codec/codec_test.go +++ b/abci/strategies/codec/codec_test.go @@ -1,6 +1,7 @@ package codec_test import ( + "encoding/binary" "testing" "github.com/stretchr/testify/require" @@ -38,6 +39,24 @@ func TestDefaultVoteExtensionCodec(t *testing.T) { _, err := codec.Decode([]byte{}) require.Nil(t, err) }) + + t.Run("test decoding vote extension rejects unknown protobuf field padding", func(t *testing.T) { + ve := vetypes.OracleVoteExtension{ + Prices: map[uint64][]byte{ + 1: []byte("123"), + }, + } + + codec := compression.NewDefaultVoteExtensionCodec() + bz, err := codec.Encode(ve) + require.NoError(t, err) + + paddedBz := appendUnknownFieldPadding(bz, 64*1024) + require.Greater(t, len(paddedBz), len(bz)) + + _, err = codec.Decode(paddedBz) + require.Error(t, err) + }) } func TestCompressionVoteExtensionCodec(t *testing.T) { @@ -80,6 +99,41 @@ func TestCompressionVoteExtensionCodec(t *testing.T) { _, err := codec.Decode([]byte{}) require.Nil(t, err) }) + + t.Run("test decoding compressed vote extension rejects trailing padding", func(t *testing.T) { + ve := vetypes.OracleVoteExtension{ + Prices: map[uint64][]byte{ + 1: []byte("123"), + }, + } + + codec := compression.NewCompressionVoteExtensionCodec( + compression.NewDefaultVoteExtensionCodec(), + compression.NewZLibCompressor(), + ) + bz, err := codec.Encode(ve) + require.NoError(t, err) + + paddedBz := append(bz, make([]byte, 64*1024)...) + require.Greater(t, len(paddedBz), len(bz)) + + _, err = codec.Decode(paddedBz) + require.Error(t, err) + }) +} + +func appendUnknownFieldPadding(bz []byte, paddingSize int) []byte { + const field7LengthDelimited = byte(7<<3 | 2) + + var lengthBuf [binary.MaxVarintLen64]byte + n := binary.PutUvarint(lengthBuf[:], uint64(paddingSize)) + + padded := make([]byte, 0, len(bz)+1+n+paddingSize) + padded = append(padded, bz...) + padded = append(padded, field7LengthDelimited) + padded = append(padded, lengthBuf[:n]...) + padded = append(padded, make([]byte, paddingSize)...) + return padded } func TestDefaultExtendedCommitCodec(t *testing.T) {