diff --git a/pkg/vm/stackitem/item.go b/pkg/vm/stackitem/item.go index db2c1cac4..c5d7007fe 100644 --- a/pkg/vm/stackitem/item.go +++ b/pkg/vm/stackitem/item.go @@ -298,17 +298,30 @@ func (i *Struct) Convert(typ Type) (Item, error) { // Clone returns a Struct with all Struct fields copied by value. // Array fields are still copied by reference. -func (i *Struct) Clone() *Struct { +func (i *Struct) Clone(limit int) (*Struct, error) { + return i.clone(&limit) +} + +func (i *Struct) clone(limit *int) (*Struct, error) { ret := &Struct{make([]Item, len(i.value))} for j := range i.value { switch t := i.value[j].(type) { case *Struct: - ret.value[j] = t.Clone() + var err error + + ret.value[j], err = t.clone(limit) + if err != nil { + return nil, err + } + *limit-- default: ret.value[j] = t } + if *limit < 0 { + return nil, ErrTooBig + } } - return ret + return ret, nil } // Null represents null on the stack. diff --git a/pkg/vm/stackitem/item_test.go b/pkg/vm/stackitem/item_test.go index ef6fc5762..1244074a0 100644 --- a/pkg/vm/stackitem/item_test.go +++ b/pkg/vm/stackitem/item_test.go @@ -465,6 +465,15 @@ func TestNewVeryBigInteger(t *testing.T) { check(false, new(big.Int).Mul(maxBitSet, big.NewInt(2))) } +func TestStructClone(t *testing.T) { + st0 := Struct{} + st := Struct{value: []Item{&st0}} + _, err := st.Clone(1) + require.NoError(t, err) + _, err = st.Clone(0) + require.Error(t, err) +} + func TestDeepCopy(t *testing.T) { testCases := []struct { name string diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 12014aeaa..ad2fe2d1f 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1056,7 +1056,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro itemElem := v.estack.Pop() arrElem := v.estack.Pop() - val := cloneIfStruct(itemElem.value) + val, err := cloneIfStruct(itemElem.value) + if err != nil { + panic(err) + } switch t := arrElem.value.(type) { case *stackitem.Array: @@ -1370,12 +1373,19 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro src := t.Value().([]stackitem.Item) arr = make([]stackitem.Item, len(src)) for i := range src { - arr[i] = cloneIfStruct(src[i]) + arr[i], err = cloneIfStruct(src[i]) + if err != nil { + panic(err) + } } case *stackitem.Map: arr = make([]stackitem.Item, 0, t.Len()) for k := range t.Value().([]stackitem.MapElement) { - arr = append(arr, cloneIfStruct(t.Value().([]stackitem.MapElement)[k].Value)) + elem, err := cloneIfStruct(t.Value().([]stackitem.MapElement)[k].Value) + if err != nil { + panic(err) + } + arr = append(arr, elem) } default: panic("not a Map, Array or Struct") @@ -1741,12 +1751,12 @@ func checkMultisig1(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sig [ return false } -func cloneIfStruct(item stackitem.Item) stackitem.Item { +func cloneIfStruct(item stackitem.Item) (stackitem.Item, error) { switch it := item.(type) { case *stackitem.Struct: - return it.Clone() + return it.Clone(MaxStackSize) default: - return it + return it, nil } } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 91870e236..8a176a88f 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -3,6 +3,7 @@ package vm import ( "bytes" "encoding/binary" + "encoding/hex" "errors" "fmt" "math" @@ -2431,6 +2432,23 @@ func TestSLOTOpcodes(t *testing.T) { }) } +func TestNestedStructClone(t *testing.T) { + progs := []string{ + // VALUES for deeply nested structs, see neo-project/neo#2534. + "5601c501fe0360589d604a12c0db415824f7cd45", + // APPEND of deeply nested struct to empty array. + "5601c2c501fe0360589d604a12c0db415824f7cf45", + // VALUES for map with deeply nested struct. + "5601c84a11c501fe0060589d604a12c0db415824f7d0cd45", + } + for _, h := range progs { + 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++ {