stackitem: limit EQUAL for deeply nested structs

See neo-project/neo-vm#428.
This commit is contained in:
Roman Khimov 2021-07-19 10:07:05 +03:00
parent f89f0300f6
commit ee4a647934
3 changed files with 73 additions and 5 deletions

View file

@ -24,6 +24,8 @@ const (
MaxArraySize = 1024 MaxArraySize = 1024
// MaxSize is the maximum item size allowed in the VM. // MaxSize is the maximum item size allowed in the VM.
MaxSize = 1024 * 1024 MaxSize = 1024 * 1024
// MaxComparableNumOfItems is the maximum number of items that can be compared for structs.
MaxComparableNumOfItems = 2048
// MaxByteArrayComparableSize is the maximum allowed length of ByteArray for Equals method. // MaxByteArrayComparableSize is the maximum allowed length of ByteArray for Equals method.
// It is set to be the maximum uint16 value. // It is set to be the maximum uint16 value.
MaxByteArrayComparableSize = math.MaxUint16 MaxByteArrayComparableSize = math.MaxUint16
@ -268,17 +270,35 @@ func (i *Struct) TryInteger() (*big.Int, error) {
// Equals implements Item interface. // Equals implements Item interface.
func (i *Struct) Equals(s Item) bool { func (i *Struct) Equals(s Item) bool {
if i == s { if s == nil {
return true
} else if s == nil {
return false return false
} }
val, ok := s.(*Struct) val, ok := s.(*Struct)
if !ok || len(i.value) != len(val.value) { if !ok {
return false
}
var limit = MaxComparableNumOfItems - 1 // 1 for current element.
return i.equalStruct(val, &limit)
}
func (i *Struct) equalStruct(s *Struct, limit *int) bool {
if i == s {
return true
} else if len(i.value) != len(s.value) {
return false return false
} }
for j := range i.value { for j := range i.value {
if !i.value[j].Equals(val.value[j]) { *limit--
if *limit == 0 {
panic(errTooBigElements)
}
sa, oka := i.value[j].(*Struct)
sb, okb := s.value[j].(*Struct)
if oka && okb {
if !sa.equalStruct(sb, limit) {
return false
}
} else if !i.value[j].Equals(s.value[j]) {
return false return false
} }
} }

View file

@ -172,6 +172,11 @@ var equalsTestCases = map[string][]struct {
item2: NewStruct([]Item{NewBigInteger(big.NewInt(1))}), item2: NewStruct([]Item{NewBigInteger(big.NewInt(1))}),
result: true, result: true,
}, },
{
item1: NewStruct([]Item{NewBigInteger(big.NewInt(1)), NewStruct([]Item{})}),
item2: NewStruct([]Item{NewBigInteger(big.NewInt(1)), NewStruct([]Item{})}),
result: true,
},
}, },
"bigint": { "bigint": {
{ {
@ -381,6 +386,41 @@ func TestEquals(t *testing.T) {
} }
} }
func TestEqualsDeepStructure(t *testing.T) {
const perStruct = 4
var items = []Item{}
var num int
for i := 0; i < perStruct; i++ {
items = append(items, Make(0))
num++
}
var layerUp = func(sa *Struct, num int) (*Struct, int) {
items := []Item{}
for i := 0; i < perStruct; i++ {
clon, err := sa.Clone(100500)
require.NoError(t, err)
items = append(items, clon)
}
num *= perStruct
num++
return NewStruct(items), num
}
var sa = NewStruct(items)
for i := 0; i < 4; i++ {
sa, num = layerUp(sa, num)
}
require.Less(t, num, MaxComparableNumOfItems)
sb, err := sa.Clone(num)
require.NoError(t, err)
require.True(t, sa.Equals(sb))
sa, num = layerUp(sa, num)
require.Less(t, MaxComparableNumOfItems, num)
sb, err = sa.Clone(num)
require.NoError(t, err)
require.Panics(t, func() { sa.Equals(sb) })
}
var marshalJSONTestCases = []struct { var marshalJSONTestCases = []struct {
input Item input Item
result []byte result []byte

View file

@ -2450,6 +2450,14 @@ func TestNestedStructClone(t *testing.T) {
} }
} }
func TestNestedStructEquals(t *testing.T) {
h := "560112c501fe0160589d604a12c0db415824f7509d4a102aec4597" // See neo-project/neo-vm#426.
prog, err := hex.DecodeString(h)
require.NoError(t, err)
vm := load(prog)
checkVMFailed(t, vm)
}
func makeProgram(opcodes ...opcode.Opcode) []byte { func makeProgram(opcodes ...opcode.Opcode) []byte {
prog := make([]byte, len(opcodes)+1) // RET prog := make([]byte, len(opcodes)+1) // RET
for i := 0; i < len(opcodes); i++ { for i := 0; i < len(opcodes); i++ {