stackitem: rework struct cloning protection

Count everything, fail early, make it more compatible with
neo-project/neo-vm#423.
This commit is contained in:
Roman Khimov 2021-07-19 15:39:54 +03:00
parent 79b1bf72aa
commit 1568ebc513
3 changed files with 23 additions and 18 deletions

View file

@ -23,7 +23,9 @@ const (
// 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 is the maximum number of items that can be compared for structs.
MaxComparableNumOfItems = 2048 MaxComparableNumOfItems = MaxDeserialized
// MaxClonableNumOfItems is the maximum number of items that can be cloned in structs.
MaxClonableNumOfItems = MaxDeserialized
// 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
@ -323,13 +325,18 @@ 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(limit int) (*Struct, error) { func (i *Struct) Clone() (*Struct, error) {
var limit = MaxClonableNumOfItems - 1 // For this struct itself.
return i.clone(&limit) return i.clone(&limit)
} }
func (i *Struct) clone(limit *int) (*Struct, error) { 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 {
*limit--
if *limit < 0 {
return nil, ErrTooBig
}
switch t := i.value[j].(type) { switch t := i.value[j].(type) {
case *Struct: case *Struct:
var err error var err error
@ -338,13 +345,9 @@ func (i *Struct) clone(limit *int) (*Struct, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
*limit--
default: default:
ret.value[j] = t ret.value[j] = t
} }
if *limit < 0 {
return nil, ErrTooBig
}
} }
return ret, nil return ret, nil
} }

View file

@ -397,7 +397,7 @@ func TestEqualsDeepStructure(t *testing.T) {
var layerUp = func(sa *Struct, num int) (*Struct, int) { var layerUp = func(sa *Struct, num int) (*Struct, int) {
items := []Item{} items := []Item{}
for i := 0; i < perStruct; i++ { for i := 0; i < perStruct; i++ {
clon, err := sa.Clone(100500) clon, err := sa.Clone()
require.NoError(t, err) require.NoError(t, err)
items = append(items, clon) items = append(items, clon)
} }
@ -410,14 +410,13 @@ func TestEqualsDeepStructure(t *testing.T) {
sa, num = layerUp(sa, num) sa, num = layerUp(sa, num)
} }
require.Less(t, num, MaxComparableNumOfItems) require.Less(t, num, MaxComparableNumOfItems)
sb, err := sa.Clone(num) sb, err := sa.Clone()
require.NoError(t, err) require.NoError(t, err)
require.True(t, sa.Equals(sb)) require.True(t, sa.Equals(sb))
sa, num = layerUp(sa, num) sa, num = layerUp(sa, num)
sb, num = layerUp(sb, num)
require.Less(t, MaxComparableNumOfItems, num) require.Less(t, MaxComparableNumOfItems, num)
sb, err = sa.Clone(num)
require.NoError(t, err)
require.Panics(t, func() { sa.Equals(sb) }) require.Panics(t, func() { sa.Equals(sb) })
} }
@ -508,9 +507,12 @@ func TestNewVeryBigInteger(t *testing.T) {
func TestStructClone(t *testing.T) { func TestStructClone(t *testing.T) {
st0 := Struct{} st0 := Struct{}
st := Struct{value: []Item{&st0}} st := Struct{value: []Item{&st0}}
_, err := st.Clone(1) for i := 0; i < MaxClonableNumOfItems-1; i++ {
require.NoError(t, err) nst, err := st.Clone()
_, err = st.Clone(0) require.NoError(t, err)
st = Struct{value: []Item{nst}}
}
_, err := st.Clone()
require.Error(t, err) require.Error(t, err)
} }

View file

@ -1052,7 +1052,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, MaxStackSize-v.refs.size) val := cloneIfStruct(itemElem.value)
switch t := arrElem.value.(type) { switch t := arrElem.value.(type) {
case *stackitem.Array: case *stackitem.Array:
@ -1358,12 +1358,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], MaxStackSize-v.refs.size) arr[i] = cloneIfStruct(src[i])
} }
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, MaxStackSize-v.refs.size)) arr = append(arr, cloneIfStruct(t.Value().([]stackitem.MapElement)[k].Value))
} }
default: default:
panic("not a Map, Array or Struct") panic("not a Map, Array or Struct")
@ -1729,10 +1729,10 @@ func checkMultisig1(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sig [
return false return false
} }
func cloneIfStruct(item stackitem.Item, limit int) stackitem.Item { func cloneIfStruct(item stackitem.Item) stackitem.Item {
switch it := item.(type) { switch it := item.(type) {
case *stackitem.Struct: case *stackitem.Struct:
ret, err := it.Clone(limit) ret, err := it.Clone()
if err != nil { if err != nil {
panic(err) panic(err)
} }