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
// MaxSize is the maximum item size allowed in the VM.
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.
// It is set to be the maximum uint16 value.
MaxByteArrayComparableSize = math.MaxUint16
@ -268,17 +270,35 @@ func (i *Struct) TryInteger() (*big.Int, error) {
// Equals implements Item interface.
func (i *Struct) Equals(s Item) bool {
if i == s {
return true
} else if s == nil {
if s == nil {
return false
}
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
}
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
}
}

View file

@ -172,6 +172,11 @@ var equalsTestCases = map[string][]struct {
item2: NewStruct([]Item{NewBigInteger(big.NewInt(1))}),
result: true,
},
{
item1: NewStruct([]Item{NewBigInteger(big.NewInt(1)), NewStruct([]Item{})}),
item2: NewStruct([]Item{NewBigInteger(big.NewInt(1)), NewStruct([]Item{})}),
result: true,
},
},
"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 {
input Item
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 {
prog := make([]byte, len(opcodes)+1) // RET
for i := 0; i < len(opcodes); i++ {