@@ -2,6 +2,7 @@ package prepare_test
22
33import (
44 "bytes"
5+ "encoding/binary"
56 "fmt"
67 "math/rand"
78 "sort"
@@ -696,6 +697,40 @@ func TestPrepareProposalAtVoteExtensionHeight(t *testing.T) {
696697 },
697698 expectError : false ,
698699 },
700+ {
701+ name : "Malicious validator with oversized vote extension should be rejected" ,
702+ scenarioSetup : func (ec * EpochAndCtx , ek * mocks.MockCheckpointingKeeper ) * Scenario {
703+ bh := randomBlockHash ()
704+ validatorAndExtensions , totalPower := generateNValidatorAndVoteExtensions (t , 4 , & bh , ec .Epoch .EpochNumber )
705+
706+ var signedVoteExtensions []cbftt.ExtendedVoteInfo
707+ for i , val := range validatorAndExtensions .Vals {
708+ validator := val
709+ ek .EXPECT ().GetPubKeyByConsAddr (gomock .Any (), sdk .ConsAddress (validator .ValidatorAddress (t ).Bytes ())).Return (validator .ProtoPubkey (), nil ).AnyTimes ()
710+ ek .EXPECT ().VerifyBLSSig (gomock .Any (), validatorAndExtensions .Extensions [i ].ToBLSSig ()).Return (nil ).AnyTimes ()
711+ ek .EXPECT ().GetBlsPubKey (gomock .Any (), validator .ValidatorAddress (t )).Return (validator .BlsPubKey (), nil ).AnyTimes ()
712+ marshaledExtension , err := validatorAndExtensions .Extensions [i ].Marshal ()
713+ require .NoError (t , err )
714+
715+ // First validator adds malicious padding to make the vote extension oversized
716+ if i == 0 {
717+ marshaledExtension = appendProtobufPadding (marshaledExtension , 1 * 1024 * 1024 - 100 )
718+ }
719+
720+ signedExtension := validator .SignVoteExtension (t , marshaledExtension , ec .Ctx .HeaderInfo ().Height - 1 , ec .Ctx .ChainID ())
721+ signedVoteExtensions = append (signedVoteExtensions , signedExtension )
722+ }
723+
724+ return & Scenario {
725+ TotalPower : totalPower ,
726+ ValidatorSet : validatorAndExtensions .Vals ,
727+ Extensions : signedVoteExtensions ,
728+ TxVerifier : newTxVerifier (encCfg .TxConfig ),
729+ ExpectedAbsentVotes : 1 , // malicious validator's vote extension should be considered absent
730+ }
731+ },
732+ expectError : false ,
733+ },
699734 // TODO: Add scenarios testing compatibility of prepareProposal, processProposal and preBlocker
700735 }
701736
@@ -782,3 +817,30 @@ func TestPrepareProposalAtVoteExtensionHeight(t *testing.T) {
782817 })
783818 }
784819}
820+
821+ // appendProtobufPadding adds padding bytes to a protobuf message using an unknown field
822+ // This simulates a malicious validator trying to inflate the size of their vote extension
823+ func appendProtobufPadding (data []byte , paddingSize int ) []byte {
824+ // Protobuf tag for field 7, wire type 2 (length-delimited)
825+ tag := byte (58 ) // (7 << 3) | 2
826+
827+ var lengthBuf [binary .MaxVarintLen64 ]byte
828+ n := binary .PutUvarint (lengthBuf [:], uint64 (paddingSize ))
829+
830+ result := make ([]byte , len (data )+ 1 + n + paddingSize )
831+
832+ copy (result , data )
833+ offset := len (data )
834+
835+ result [offset ] = tag
836+ offset ++
837+
838+ copy (result [offset :], lengthBuf [:n ])
839+ offset += n
840+
841+ for i := 0 ; i < paddingSize ; i ++ {
842+ result [offset + i ] = byte (i % 256 )
843+ }
844+
845+ return result
846+ }
0 commit comments