Merge pull request #2054 from nspcc-dev/fix-oom-on-struct-cloning

Fix OOM on struct cloning
This commit is contained in:
Roman Khimov 2021-07-12 13:04:12 +03:00 committed by GitHub
commit 9de5c850e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 8 deletions

View file

@ -298,17 +298,30 @@ func (i *Struct) Convert(typ Type) (Item, error) {
// Clone returns a Struct with all Struct fields copied by value. // Clone returns a Struct with all Struct fields copied by value.
// Array fields are still copied by reference. // 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))} ret := &Struct{make([]Item, len(i.value))}
for j := range i.value { for j := range i.value {
switch t := i.value[j].(type) { switch t := i.value[j].(type) {
case *Struct: 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: default:
ret.value[j] = t ret.value[j] = t
} }
if *limit < 0 {
return nil, ErrTooBig
}
} }
return ret return ret, nil
} }
// Null represents null on the stack. // Null represents null on the stack.

View file

@ -465,6 +465,15 @@ func TestNewVeryBigInteger(t *testing.T) {
check(false, new(big.Int).Mul(maxBitSet, big.NewInt(2))) 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) { func TestDeepCopy(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string

View file

@ -1056,7 +1056,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
itemElem := v.estack.Pop() itemElem := v.estack.Pop()
arrElem := v.estack.Pop() arrElem := v.estack.Pop()
val := cloneIfStruct(itemElem.value) val := cloneIfStruct(itemElem.value, MaxStackSize-v.refs.size)
switch t := arrElem.value.(type) { switch t := arrElem.value.(type) {
case *stackitem.Array: case *stackitem.Array:
@ -1370,12 +1370,12 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
src := t.Value().([]stackitem.Item) src := t.Value().([]stackitem.Item)
arr = make([]stackitem.Item, len(src)) arr = make([]stackitem.Item, len(src))
for i := range src { for i := range src {
arr[i] = cloneIfStruct(src[i]) arr[i] = cloneIfStruct(src[i], MaxStackSize-v.refs.size)
} }
case *stackitem.Map: case *stackitem.Map:
arr = make([]stackitem.Item, 0, t.Len()) arr = make([]stackitem.Item, 0, t.Len())
for k := range t.Value().([]stackitem.MapElement) { for k := range t.Value().([]stackitem.MapElement) {
arr = append(arr, cloneIfStruct(t.Value().([]stackitem.MapElement)[k].Value)) arr = append(arr, cloneIfStruct(t.Value().([]stackitem.MapElement)[k].Value, MaxStackSize-v.refs.size))
} }
default: default:
panic("not a Map, Array or Struct") panic("not a Map, Array or Struct")
@ -1741,10 +1741,14 @@ func checkMultisig1(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sig [
return false return false
} }
func cloneIfStruct(item stackitem.Item) stackitem.Item { func cloneIfStruct(item stackitem.Item, limit int) stackitem.Item {
switch it := item.(type) { switch it := item.(type) {
case *stackitem.Struct: case *stackitem.Struct:
return it.Clone() ret, err := it.Clone(limit)
if err != nil {
panic(err)
}
return ret
default: default:
return it return it
} }

View file

@ -3,6 +3,7 @@ package vm
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math" "math"
@ -2431,6 +2432,25 @@ 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",
// VALUES for a lot of not-so-deep nested structs.
"5601c5000a60589d604a12c0db415824f701fe03504a519d4a102afa01ff03c0cd45",
}
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 { 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++ {