From 435463e62011b064f9825d6cfb9c77f127cb1993 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 8 Oct 2021 15:05:36 +0300 Subject: [PATCH 1/2] vm: allow big string keys in PICKITEM Because `MaxKeySize` is bigger than integer size, we fail on integer cast while retreiving items from map. SETITEM is not affected. Signed-off-by: Evgeniy Stratonikov --- pkg/vm/vm.go | 3 ++- pkg/vm/vm_test.go | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 0211ce79e..df645ad21 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1087,11 +1087,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro validateMapKey(key) obj := v.estack.Pop() - index := int(key.BigInt().Int64()) switch t := obj.value.(type) { // Struct and Array items have their underlying value as []Item. case *stackitem.Array, *stackitem.Struct: + index := int(key.BigInt().Int64()) arr := t.Value().([]stackitem.Item) if index < 0 || index >= len(arr) { panic("PICKITEM: invalid index") @@ -1105,6 +1105,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro } v.estack.PushItem(t.Value().([]stackitem.MapElement)[index].Value.Dup()) default: + index := int(key.BigInt().Int64()) arr := obj.Bytes() if index < 0 || index >= len(arr) { panic("PICKITEM: invalid index") diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 63ca28fd1..0395f813d 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -1205,6 +1205,16 @@ func TestSETITEMMap(t *testing.T) { m := stackitem.NewMap() m.Add(stackitem.Make(5), stackitem.Make(3)) runWithArgs(t, prog, []byte{0, 1}, m, 5, m, 5, []byte{0, 1}) + + t.Run("big key", func(t *testing.T) { + m := stackitem.NewMap() + key := make([]byte, stackitem.MaxKeySize) + for i := range key { + key[i] = 0x0F + } + m.Add(stackitem.NewByteArray(key), stackitem.Make(3)) + runWithArgs(t, prog, "value", m, key, m, key, "value") + }) } func TestSETITEMBigMapBad(t *testing.T) { From 5c470a6e3d5fe70ef8f2c8c040ace37b68dbb7c9 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 8 Oct 2021 16:02:24 +0300 Subject: [PATCH 2/2] vm: catch out-of-bounds exceptions, fix #2208 Signed-off-by: Evgeniy Stratonikov --- pkg/vm/vm.go | 25 ++++++++++++++++-------- pkg/vm/vm_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index df645ad21..943e781bf 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1091,24 +1091,29 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro switch t := obj.value.(type) { // Struct and Array items have their underlying value as []Item. case *stackitem.Array, *stackitem.Struct: - index := int(key.BigInt().Int64()) + index := toInt(key.BigInt()) arr := t.Value().([]stackitem.Item) if index < 0 || index >= len(arr) { - panic("PICKITEM: invalid index") + msg := fmt.Sprintf("The value %d is out of range.", index) + v.throw(stackitem.NewByteArray([]byte(msg))) + return } item := arr[index].Dup() v.estack.PushItem(item) case *stackitem.Map: index := t.Index(key.Item()) if index < 0 { - panic("invalid key") + v.throw(stackitem.NewByteArray([]byte("Key not found in Map"))) + return } v.estack.PushItem(t.Value().([]stackitem.MapElement)[index].Value.Dup()) default: - index := int(key.BigInt().Int64()) + index := toInt(key.BigInt()) arr := obj.Bytes() if index < 0 || index >= len(arr) { - panic("PICKITEM: invalid index") + msg := fmt.Sprintf("The value %d is out of range.", index) + v.throw(stackitem.NewByteArray([]byte(msg))) + return } item := arr[index] v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(item)))) @@ -1125,9 +1130,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro // Struct and Array items have their underlying value as []Item. case *stackitem.Array, *stackitem.Struct: arr := t.Value().([]stackitem.Item) - index := int(key.BigInt().Int64()) + index := toInt(key.BigInt()) if index < 0 || index >= len(arr) { - panic("SETITEM: invalid index") + msg := fmt.Sprintf("The value %d is out of range.", index) + v.throw(stackitem.NewByteArray([]byte(msg))) + return } v.refs.Remove(arr[index]) arr[index] = item @@ -1142,7 +1149,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case *stackitem.Buffer: index := toInt(key.BigInt()) if index < 0 || index >= t.Len() { - panic("invalid index") + msg := fmt.Sprintf("The value %d is out of range.", index) + v.throw(stackitem.NewByteArray([]byte(msg))) + return } bi, err := item.TryInteger() b := toInt(bi) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 0395f813d..a5ed9b595 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "fmt" + "math" "math/big" "math/rand" "testing" @@ -1158,6 +1159,26 @@ func TestPICKITEM(t *testing.T) { t.Run("Array", getTestFuncForVM(prog, 2, []stackitem.Item{stackitem.Make(1), stackitem.Make(2)}, 1)) t.Run("ByteArray", getTestFuncForVM(prog, 2, []byte{1, 2}, 1)) t.Run("Buffer", getTestFuncForVM(prog, 2, stackitem.NewBuffer([]byte{1, 2}), 1)) + t.Run("Exceptions", func(t *testing.T) { + tryProg := getTRYProgram( + []byte{byte(opcode.PICKITEM), byte(opcode.RET)}, + []byte{byte(opcode.RET)}, nil) + items := []stackitem.Item{ + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewBuffer([]byte{}), + stackitem.NewByteArray([]byte{}), + } + for _, item := range items { + t.Run(item.String()+", negative", getTestFuncForVM(tryProg, + fmt.Sprintf("The value %d is out of range.", math.MinInt32), + item, math.MinInt32)) + t.Run(item.String()+", very big index", + getTestFuncForVM(tryProg, nil, item, int64(math.MaxInt32)+1)) + } + + m := stackitem.NewMap() + t.Run("Map, missing key", getTestFuncForVM(tryProg, "Key not found in Map", m, 1)) + }) } func TestPICKITEMDupArray(t *testing.T) { @@ -1196,8 +1217,33 @@ func TestPICKITEMMap(t *testing.T) { func TestSETITEMBuffer(t *testing.T) { prog := makeProgram(opcode.DUP, opcode.REVERSE4, opcode.SETITEM) t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte{0, 42, 2}), 42, 1, stackitem.NewBuffer([]byte{0, 1, 2}))) - t.Run("BadIndex", getTestFuncForVM(prog, nil, 42, -1, stackitem.NewBuffer([]byte{0, 1, 2}))) t.Run("BadValue", getTestFuncForVM(prog, nil, 256, 1, stackitem.NewBuffer([]byte{0, 1, 2}))) + t.Run("Exceptions", func(t *testing.T) { + tryProg := getTRYProgram( + []byte{byte(opcode.SETITEM), byte(opcode.PUSH12), byte(opcode.RET)}, + []byte{byte(opcode.RET)}, nil) + t.Run("negative index", getTestFuncForVM(tryProg, + fmt.Sprintf("The value %d is out of range.", math.MinInt32), + stackitem.NewBuffer([]byte{0, 1, 2}), math.MinInt32, 0)) + t.Run("very big index", getTestFuncForVM(tryProg, + nil, stackitem.NewBuffer([]byte{0, 1, 2}), int64(math.MaxInt32)+1, 0)) + }) +} + +func TestSETITEMArray(t *testing.T) { + tryProg := getTRYProgram( + []byte{byte(opcode.SETITEM), byte(opcode.RET)}, + []byte{byte(opcode.RET)}, nil) + t.Run("Good", func(t *testing.T) { + arr := stackitem.NewArray([]stackitem.Item{stackitem.Make(12), stackitem.Make(2)}) + expected := stackitem.NewArray([]stackitem.Item{stackitem.Make(12), stackitem.Make(42)}) + runWithArgs(t, tryProg, expected, arr, arr, 1, 42) + }) + t.Run("negative index", getTestFuncForVM(tryProg, + fmt.Sprintf("The value %d is out of range.", math.MinInt32), + []stackitem.Item{}, math.MinInt32, 42)) + t.Run("very big index", getTestFuncForVM(tryProg, + nil, []stackitem.Item{}, int64(math.MaxInt32)+1, 0)) } func TestSETITEMMap(t *testing.T) {