stackitem: completely drop MaxArraySize

Turns out C# VM doesn't have it since preview2, so our limiting of
MaxArraySize in incompatible with it. Removing this limit shouldn't be a
problem with the reference counter we have, both APPEND and SETITEM add things
to reference counter and we can't exceed MaxStackSize. PACK on the other hand
can't get more than MaxStackSize-1 of input elements.

Unify NEWSTRUCT with NEWARRAY* and use better integer checks at the same time.

Multisig limit is still 1024.
This commit is contained in:
Roman Khimov 2021-07-19 13:35:48 +03:00
parent ee4a647934
commit 233307aca5
8 changed files with 50 additions and 67 deletions

View file

@ -3,8 +3,6 @@ package compiler_test
import ( import (
"math/big" "math/big"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
var assignTestCases = []testCase{ var assignTestCases = []testCase{
@ -154,9 +152,9 @@ func TestManyAssignments(t *testing.T) {
src2 := `return a src2 := `return a
}` }`
for i := 0; i < stackitem.MaxArraySize; i++ { for i := 0; i < 1024; i++ {
src1 += "a += 1\n" src1 += "a += 1\n"
} }
eval(t, src1+src2, big.NewInt(stackitem.MaxArraySize)) eval(t, src1+src2, big.NewInt(1024))
} }

View file

@ -72,7 +72,7 @@ func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
aer.VMState = vm.State(r.ReadB()) aer.VMState = vm.State(r.ReadB())
aer.GasConsumed = int64(r.ReadU64LE()) aer.GasConsumed = int64(r.ReadU64LE())
sz := r.ReadVarUint() sz := r.ReadVarUint()
if stackitem.MaxArraySize < sz && r.Err == nil { if stackitem.MaxDeserialized < sz && r.Err == nil {
r.Err = errors.New("invalid format") r.Err = errors.New("invalid format")
} }
if r.Err != nil { if r.Err != nil {

View file

@ -12,6 +12,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
// MaxMultisigKeys is the maximum number of used keys for correct multisig contract.
const MaxMultisigKeys = 1024
var ( var (
verifyInteropID = interopnames.ToID([]byte(interopnames.SystemCryptoCheckSig)) verifyInteropID = interopnames.ToID([]byte(interopnames.SystemCryptoCheckSig))
multisigInteropID = interopnames.ToID([]byte(interopnames.SystemCryptoCheckMultisig)) multisigInteropID = interopnames.ToID([]byte(interopnames.SystemCryptoCheckMultisig))
@ -25,14 +28,14 @@ func getNumOfThingsFromInstr(instr opcode.Opcode, param []byte) (int, bool) {
nthings = int(instr-opcode.PUSH1) + 1 nthings = int(instr-opcode.PUSH1) + 1
case instr <= opcode.PUSHINT256: case instr <= opcode.PUSHINT256:
n := bigint.FromBytes(param) n := bigint.FromBytes(param)
if !n.IsInt64() || n.Int64() > stackitem.MaxArraySize { if !n.IsInt64() || n.Sign() < 0 || n.Int64() > MaxMultisigKeys {
return 0, false return 0, false
} }
nthings = int(n.Int64()) nthings = int(n.Int64())
default: default:
return 0, false return 0, false
} }
if nthings < 1 || nthings > stackitem.MaxArraySize { if nthings < 1 || nthings > MaxMultisigKeys {
return 0, false return 0, false
} }
return nthings, true return nthings, true
@ -76,7 +79,7 @@ func ParseMultiSigContract(script []byte) (int, [][]byte, bool) {
} }
pubs = append(pubs, param) pubs = append(pubs, param)
nkeys++ nkeys++
if nkeys > stackitem.MaxArraySize { if nkeys > MaxMultisigKeys {
return nsigs, nil, false return nsigs, nil, false
} }
} }

View file

@ -20,8 +20,6 @@ import (
const ( const (
// MaxBigIntegerSizeBits is the maximum size of BigInt item in bits. // MaxBigIntegerSizeBits is the maximum size of BigInt item in bits.
MaxBigIntegerSizeBits = 32 * 8 MaxBigIntegerSizeBits = 32 * 8
// MaxArraySize is the maximum array size allowed in the VM.
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 is the maximum number of items that can be compared for structs.
@ -71,7 +69,6 @@ var (
// value exceeds MaxSize. // value exceeds MaxSize.
ErrTooBig = errors.New("too big") ErrTooBig = errors.New("too big")
errTooBigArray = fmt.Errorf("%w: array", ErrTooBig)
errTooBigComparable = fmt.Errorf("%w: uncomparable", ErrTooBig) errTooBigComparable = fmt.Errorf("%w: uncomparable", ErrTooBig)
errTooBigInteger = fmt.Errorf("%w: integer", ErrTooBig) errTooBigInteger = fmt.Errorf("%w: integer", ErrTooBig)
errTooBigKey = fmt.Errorf("%w: map key", ErrTooBig) errTooBigKey = fmt.Errorf("%w: map key", ErrTooBig)

View file

@ -236,8 +236,8 @@ func (r *deserContext) decodeBinary() Item {
return NewBigInteger(num) return NewBigInteger(num)
case ArrayT, StructT: case ArrayT, StructT:
size := int(r.ReadVarUint()) size := int(r.ReadVarUint())
if size > MaxArraySize { if size > MaxDeserialized {
r.Err = errTooBigArray r.Err = errTooBigElements
return nil return nil
} }
arr := make([]Item, size) arr := make([]Item, size)
@ -251,8 +251,8 @@ func (r *deserContext) decodeBinary() Item {
return NewStruct(arr) return NewStruct(arr)
case MapT: case MapT:
size := int(r.ReadVarUint()) size := int(r.ReadVarUint())
if size > MaxArraySize { if size > MaxDeserialized {
r.Err = errTooBigArray r.Err = errTooBigElements
return nil return nil
} }
m := NewMap() m := NewMap()

View file

@ -52,8 +52,8 @@ func TestSerialize(t *testing.T) {
arr.Value().([]Item)[0] = arr arr.Value().([]Item)[0] = arr
testSerialize(t, ErrRecursive, arr) testSerialize(t, ErrRecursive, arr)
items := make([]Item, 0, MaxArraySize) items := make([]Item, 0, MaxDeserialized-1)
for i := 0; i < MaxArraySize; i++ { for i := 0; i < MaxDeserialized-1; i++ {
items = append(items, zeroByteArray) items = append(items, zeroByteArray)
} }
testSerialize(t, nil, newItem(items)) testSerialize(t, nil, newItem(items))
@ -141,12 +141,14 @@ func TestSerialize(t *testing.T) {
testSerialize(t, ErrTooBig, m) testSerialize(t, ErrTooBig, m)
m = NewMap() m = NewMap()
for i := 0; i < MaxArraySize; i++ { for i := 0; i < MaxDeserialized/2-1; i++ {
m.Add(Make(i), zeroByteArray) m.Add(Make(i), zeroByteArray)
} }
// testSerialize(t, nil, m) // It contains too many elements already, so ErrTooBig. testSerialize(t, nil, m)
m.Add(Make(100500), zeroByteArray) for i := 0; i <= MaxDeserialized; i++ {
m.Add(Make(i), zeroByteArray)
}
data, err := Serialize(m) data, err := Serialize(m)
require.NoError(t, err) require.NoError(t, err)
_, err = Deserialize(data) _, err = Deserialize(data)

View file

@ -1027,31 +1027,27 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case opcode.NEWARRAY0: case opcode.NEWARRAY0:
v.estack.PushVal(stackitem.NewArray([]stackitem.Item{})) v.estack.PushVal(stackitem.NewArray([]stackitem.Item{}))
case opcode.NEWARRAY, opcode.NEWARRAYT: case opcode.NEWARRAY, opcode.NEWARRAYT, opcode.NEWSTRUCT:
item := v.estack.Pop() n := toInt(v.estack.Pop().BigInt())
n := item.BigInt().Int64() if n < 0 || n > MaxStackSize {
if n > stackitem.MaxArraySize { panic("wrong number of elements")
panic("too long array")
} }
typ := stackitem.AnyT typ := stackitem.AnyT
if op == opcode.NEWARRAYT { if op == opcode.NEWARRAYT {
typ = stackitem.Type(parameter[0]) typ = stackitem.Type(parameter[0])
} }
items := makeArrayOfType(int(n), typ) items := makeArrayOfType(int(n), typ)
v.estack.PushVal(stackitem.NewArray(items)) var res stackitem.Item
if op == opcode.NEWSTRUCT {
res = stackitem.NewStruct(items)
} else {
res = stackitem.NewArray(items)
}
v.estack.PushVal(res)
case opcode.NEWSTRUCT0: case opcode.NEWSTRUCT0:
v.estack.PushVal(stackitem.NewStruct([]stackitem.Item{})) v.estack.PushVal(stackitem.NewStruct([]stackitem.Item{}))
case opcode.NEWSTRUCT:
item := v.estack.Pop()
n := item.BigInt().Int64()
if n > stackitem.MaxArraySize {
panic("too long struct")
}
items := makeArrayOfType(int(n), stackitem.AnyT)
v.estack.PushVal(stackitem.NewStruct(items))
case opcode.APPEND: case opcode.APPEND:
itemElem := v.estack.Pop() itemElem := v.estack.Pop()
arrElem := v.estack.Pop() arrElem := v.estack.Pop()
@ -1060,14 +1056,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
switch t := arrElem.value.(type) { switch t := arrElem.value.(type) {
case *stackitem.Array: case *stackitem.Array:
if t.Len() >= stackitem.MaxArraySize {
panic("too long array")
}
t.Append(val) t.Append(val)
case *stackitem.Struct: case *stackitem.Struct:
if t.Len() >= stackitem.MaxArraySize {
panic("too long struct")
}
t.Append(val) t.Append(val)
default: default:
panic("APPEND: not of underlying type Array") panic("APPEND: not of underlying type Array")
@ -1076,8 +1066,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.refs.Add(val) v.refs.Add(val)
case opcode.PACK: case opcode.PACK:
n := int(v.estack.Pop().BigInt().Int64()) n := toInt(v.estack.Pop().BigInt())
if n < 0 || n > v.estack.Len() || n > stackitem.MaxArraySize { if n < 0 || n > v.estack.Len() {
panic("OPACK: invalid length") panic("OPACK: invalid length")
} }
@ -1148,8 +1138,6 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case *stackitem.Map: case *stackitem.Map:
if i := t.Index(key.value); i >= 0 { if i := t.Index(key.value); i >= 0 {
v.refs.Remove(t.Value().([]stackitem.MapElement)[i].Value) v.refs.Remove(t.Value().([]stackitem.MapElement)[i].Value)
} else if t.Len() >= stackitem.MaxArraySize {
panic("too big map")
} }
t.Add(key.value, item) t.Add(key.value, item)
v.refs.Add(item) v.refs.Add(item)

View file

@ -1057,7 +1057,7 @@ func TestNEWSTRUCT0(t *testing.T) {
func TestNEWARRAYArray(t *testing.T) { func TestNEWARRAYArray(t *testing.T) {
prog := makeProgram(opcode.NEWARRAY) prog := makeProgram(opcode.NEWARRAY)
t.Run("ByteArray", getTestFuncForVM(prog, stackitem.NewArray([]stackitem.Item{}), []byte{})) t.Run("ByteArray", getTestFuncForVM(prog, stackitem.NewArray([]stackitem.Item{}), []byte{}))
t.Run("BadSize", getTestFuncForVM(prog, nil, stackitem.MaxArraySize+1)) t.Run("BadSize", getTestFuncForVM(prog, nil, MaxStackSize+1))
t.Run("Integer", getTestFuncForVM(prog, []stackitem.Item{stackitem.Null{}}, 1)) t.Run("Integer", getTestFuncForVM(prog, []stackitem.Item{stackitem.Null{}}, 1))
} }
@ -1108,7 +1108,7 @@ func TestNEWARRAYT(t *testing.T) {
func TestNEWSTRUCT(t *testing.T) { func TestNEWSTRUCT(t *testing.T) {
prog := makeProgram(opcode.NEWSTRUCT) prog := makeProgram(opcode.NEWSTRUCT)
t.Run("ByteArray", getTestFuncForVM(prog, stackitem.NewStruct([]stackitem.Item{}), []byte{})) t.Run("ByteArray", getTestFuncForVM(prog, stackitem.NewStruct([]stackitem.Item{}), []byte{}))
t.Run("BadSize", getTestFuncForVM(prog, nil, stackitem.MaxArraySize+1)) t.Run("BadSize", getTestFuncForVM(prog, nil, MaxStackSize+1))
t.Run("Integer", getTestFuncForVM(prog, stackitem.NewStruct([]stackitem.Item{stackitem.Null{}}), 1)) t.Run("Integer", getTestFuncForVM(prog, stackitem.NewStruct([]stackitem.Item{stackitem.Null{}}), 1))
} }
@ -1136,15 +1136,20 @@ func TestAPPENDBad(t *testing.T) {
func TestAPPENDGoodSizeLimit(t *testing.T) { func TestAPPENDGoodSizeLimit(t *testing.T) {
prog := makeProgram(opcode.NEWARRAY, opcode.DUP, opcode.PUSH0, opcode.APPEND) prog := makeProgram(opcode.NEWARRAY, opcode.DUP, opcode.PUSH0, opcode.APPEND)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(stackitem.MaxArraySize - 1) vm.estack.PushVal(MaxStackSize - 3) // 1 for array, 1 for copy, 1 for pushed 0.
runVM(t, vm) runVM(t, vm)
assert.Equal(t, 1, vm.estack.Len()) assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, stackitem.MaxArraySize, len(vm.estack.Pop().Array())) assert.Equal(t, MaxStackSize-2, len(vm.estack.Pop().Array()))
} }
func TestAPPENDBadSizeLimit(t *testing.T) { func TestAPPENDBadSizeLimit(t *testing.T) {
prog := makeProgram(opcode.NEWARRAY, opcode.DUP, opcode.PUSH0, opcode.APPEND) prog := makeProgram(opcode.NEWARRAY, opcode.DUP, opcode.PUSH0, opcode.APPEND)
runWithArgs(t, prog, nil, stackitem.MaxArraySize) runWithArgs(t, prog, nil, MaxStackSize)
}
func TestAPPENDRefSizeLimit(t *testing.T) {
prog := makeProgram(opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND, opcode.JMP, 0xfd)
runWithArgs(t, prog, nil)
} }
func TestPICKITEM(t *testing.T) { func TestPICKITEM(t *testing.T) {
@ -1205,19 +1210,19 @@ func TestSETITEMMap(t *testing.T) {
func TestSETITEMBigMapBad(t *testing.T) { func TestSETITEMBigMapBad(t *testing.T) {
prog := makeProgram(opcode.SETITEM) prog := makeProgram(opcode.SETITEM)
m := stackitem.NewMap() m := stackitem.NewMap()
for i := 0; i < stackitem.MaxArraySize; i++ { for i := 0; i < MaxStackSize; i++ {
m.Add(stackitem.Make(i), stackitem.Make(i)) m.Add(stackitem.Make(i), stackitem.Make(i))
} }
runWithArgs(t, prog, nil, m, stackitem.MaxArraySize, 0) runWithArgs(t, prog, nil, m, m, MaxStackSize, 0)
} }
// This test checks is SETITEM properly updates reference counter. // This test checks is SETITEM properly updates reference counter.
// 1. Create 2 arrays of size MaxArraySize - 3. (MaxStackSize = 2 * MaxArraySize) // 1. Create 2 arrays of size MaxStackSize/2 - 3.
// 2. SETITEM each of them to a map. // 2. SETITEM each of them to a map.
// 3. Replace each of them with a scalar value. // 3. Replace each of them with a scalar value.
func TestSETITEMMapStackLimit(t *testing.T) { func TestSETITEMMapStackLimit(t *testing.T) {
size := stackitem.MaxArraySize - 3 size := MaxStackSize/2 - 3
m := stackitem.NewMap() m := stackitem.NewMap()
m.Add(stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewArray(makeArrayOfType(size, stackitem.BooleanT))) m.Add(stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewArray(makeArrayOfType(size, stackitem.BooleanT)))
m.Add(stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewArray(makeArrayOfType(size, stackitem.BooleanT))) m.Add(stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewArray(makeArrayOfType(size, stackitem.BooleanT)))
@ -1237,7 +1242,7 @@ func TestSETITEMBigMapGood(t *testing.T) {
vm := load(prog) vm := load(prog)
m := stackitem.NewMap() m := stackitem.NewMap()
for i := 0; i < stackitem.MaxArraySize; i++ { for i := 0; i < MaxStackSize-3; i++ {
m.Add(stackitem.Make(i), stackitem.Make(i)) m.Add(stackitem.Make(i), stackitem.Make(i))
} }
vm.estack.Push(&Element{value: m}) vm.estack.Push(&Element{value: m})
@ -1723,16 +1728,6 @@ func TestPACK(t *testing.T) {
t.Run("Good0Len", getTestFuncForVM(prog, []stackitem.Item{}, 0)) t.Run("Good0Len", getTestFuncForVM(prog, []stackitem.Item{}, 0))
} }
func TestPACKBigLen(t *testing.T) {
prog := makeProgram(opcode.PACK)
vm := load(prog)
for i := 0; i <= stackitem.MaxArraySize; i++ {
vm.estack.PushVal(0)
}
vm.estack.PushVal(stackitem.MaxArraySize + 1)
checkVMFailed(t, vm)
}
func TestPACKGood(t *testing.T) { func TestPACKGood(t *testing.T) {
prog := makeProgram(opcode.PACK) prog := makeProgram(opcode.PACK)
elements := []int{55, 34, 42} elements := []int{55, 34, 42}
@ -1756,7 +1751,7 @@ func TestPACKGood(t *testing.T) {
func TestPACK_UNPACK_MaxSize(t *testing.T) { func TestPACK_UNPACK_MaxSize(t *testing.T) {
prog := makeProgram(opcode.PACK, opcode.UNPACK) prog := makeProgram(opcode.PACK, opcode.UNPACK)
elements := make([]int, stackitem.MaxArraySize) elements := make([]int, MaxStackSize-2)
vm := load(prog) vm := load(prog)
// canary // canary
vm.estack.PushVal(1) vm.estack.PushVal(1)
@ -1779,7 +1774,7 @@ func TestPACK_UNPACK_MaxSize(t *testing.T) {
func TestPACK_UNPACK_PACK_MaxSize(t *testing.T) { func TestPACK_UNPACK_PACK_MaxSize(t *testing.T) {
prog := makeProgram(opcode.PACK, opcode.UNPACK, opcode.PACK) prog := makeProgram(opcode.PACK, opcode.UNPACK, opcode.PACK)
elements := make([]int, stackitem.MaxArraySize) elements := make([]int, MaxStackSize-2)
vm := load(prog) vm := load(prog)
// canary // canary
vm.estack.PushVal(1) vm.estack.PushVal(1)