@@ -3,6 +3,7 @@ package evm
33import (
44 "encoding/hex"
55 "encoding/json"
6+ "math"
67 "math/big"
78 "testing"
89
@@ -656,3 +657,102 @@ func TestFinalizeEvent(t *testing.T) {
656657 assert .Equal (t , "1000" , decoded .Amount )
657658 })
658659}
660+
661+ // abiWord returns a 32-byte big-endian word holding v, for building hostile log data.
662+ func abiWord (v * big.Int ) []byte {
663+ w := make ([]byte , 32 )
664+ v .FillBytes (w )
665+ return w
666+ }
667+
668+ // Both the offset and the length word come from the RPC, so both can be chosen
669+ // to overflow uint64. Addition-based bounds wrap and pass, then the slice panics
670+ // — and the listener has no caller between here and the goroutine root, so that
671+ // panic would end the process.
672+ func TestReadDynamicBytes_OverflowIsRejectedNotPanicked (t * testing.T ) {
673+ maxU64 := new (big.Int ).SetUint64 (math .MaxUint64 )
674+
675+ t .Run ("offset near 2^64 does not wrap past the bounds check" , func (t * testing.T ) {
676+ data := make ([]byte , 128 )
677+ for _ , off := range []uint64 {
678+ math .MaxUint64 , // absOff + 32 wraps to 31
679+ math .MaxUint64 - 16 , // wraps to 15
680+ math .MaxUint64 - 31 , // wraps to 0
681+ math .MaxUint64 - 32 , // wraps to exactly 0 after the +32
682+ } {
683+ _ , ok := readDynamicBytes (data , off )
684+ assert .False (t , ok , "offset %d must be rejected" , off )
685+ }
686+ })
687+
688+ t .Run ("length near 2^64 does not wrap the end below the start" , func (t * testing.T ) {
689+ // Word at offset 0 is the length; make it enormous so dataStart+byteLen wraps.
690+ data := make ([]byte , 128 )
691+ copy (data [0 :32 ], abiWord (maxU64 ))
692+
693+ _ , ok := readDynamicBytes (data , 0 )
694+ assert .False (t , ok , "a length that wraps the end must be rejected" )
695+ })
696+
697+ t .Run ("length just past the buffer is rejected without wrapping" , func (t * testing.T ) {
698+ data := make ([]byte , 128 )
699+ copy (data [0 :32 ], abiWord (big .NewInt (97 ))) // 32 header + 97 > 128
700+ _ , ok := readDynamicBytes (data , 0 )
701+ assert .False (t , ok )
702+ })
703+
704+ t .Run ("well formed input still decodes" , func (t * testing.T ) {
705+ data := make ([]byte , 128 )
706+ copy (data [0 :32 ], abiWord (big .NewInt (4 )))
707+ copy (data [32 :36 ], []byte {0xDE , 0xAD , 0xBE , 0xEF })
708+
709+ got , ok := readDynamicBytes (data , 0 )
710+ require .True (t , ok )
711+ assert .Equal (t , "0xdeadbeef" , got )
712+ })
713+
714+ t .Run ("zero length decodes to empty" , func (t * testing.T ) {
715+ data := make ([]byte , 64 )
716+ got , ok := readDynamicBytes (data , 0 )
717+ require .True (t , ok )
718+ assert .Equal (t , "0x" , got )
719+ })
720+
721+ t .Run ("exactly filling the buffer decodes" , func (t * testing.T ) {
722+ data := make ([]byte , 64 )
723+ copy (data [0 :32 ], abiWord (big .NewInt (32 )))
724+ copy (data [32 :64 ], abiWord (big .NewInt (1 )))
725+
726+ _ , ok := readDynamicBytes (data , 32 + 32 - 32 ) // offset 32 is past the end for a 64-byte buffer
727+ assert .False (t , ok )
728+
729+ got , ok := readDynamicBytes (data , 0 )
730+ require .True (t , ok )
731+ assert .Len (t , got , 2 + 64 )
732+ })
733+ }
734+
735+ // End to end: a log carrying an overflowing payload offset must be skipped, not
736+ // crash the listener goroutine.
737+ func TestParseEvent_HostileLogDoesNotPanic (t * testing.T ) {
738+ // 5 words of data so the length guard passes, with word 2 (the payload
739+ // offset) set to a value that overflows when 32 is added to it.
740+ data := make ([]byte , 32 * 5 )
741+ copy (data [2 * 32 :3 * 32 ], abiWord (new (big.Int ).SetUint64 (math .MaxUint64 )))
742+
743+ log := & types.Log {
744+ Topics : []ethcommon.Hash {
745+ ethcommon .HexToHash ("0x01" ),
746+ ethcommon .HexToHash ("0x02" ),
747+ ethcommon .HexToHash ("0x03" ),
748+ },
749+ Data : data ,
750+ TxHash : ethcommon .HexToHash ("0xabc" ),
751+ Index : 7 ,
752+ Address : ethcommon .HexToAddress ("0xdead" ),
753+ }
754+
755+ require .NotPanics (t , func () {
756+ ParseEvent (log , EventTypeSendFunds , "eip155:1" , zerolog .Nop ())
757+ })
758+ }
0 commit comments