Merge pull request #2209 from nspcc-dev/vm-exceptions

Catch out-of-bounds exceptions in VM
This commit is contained in:
Roman Khimov 2021-10-12 09:30:49 +03:00 committed by GitHub
commit e2c4b6d393
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 8 deletions

View file

@ -1087,27 +1087,33 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
validateMapKey(key) validateMapKey(key)
obj := v.estack.Pop() obj := v.estack.Pop()
index := int(key.BigInt().Int64())
switch t := obj.value.(type) { switch t := obj.value.(type) {
// Struct and Array items have their underlying value as []Item. // Struct and Array items have their underlying value as []Item.
case *stackitem.Array, *stackitem.Struct: case *stackitem.Array, *stackitem.Struct:
index := toInt(key.BigInt())
arr := t.Value().([]stackitem.Item) arr := t.Value().([]stackitem.Item)
if index < 0 || index >= len(arr) { 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() item := arr[index].Dup()
v.estack.PushItem(item) v.estack.PushItem(item)
case *stackitem.Map: case *stackitem.Map:
index := t.Index(key.Item()) index := t.Index(key.Item())
if index < 0 { 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()) v.estack.PushItem(t.Value().([]stackitem.MapElement)[index].Value.Dup())
default: default:
index := toInt(key.BigInt())
arr := obj.Bytes() arr := obj.Bytes()
if index < 0 || index >= len(arr) { 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] item := arr[index]
v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(item)))) v.estack.PushItem(stackitem.NewBigInteger(big.NewInt(int64(item))))
@ -1124,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. // Struct and Array items have their underlying value as []Item.
case *stackitem.Array, *stackitem.Struct: case *stackitem.Array, *stackitem.Struct:
arr := t.Value().([]stackitem.Item) arr := t.Value().([]stackitem.Item)
index := int(key.BigInt().Int64()) index := toInt(key.BigInt())
if index < 0 || index >= len(arr) { 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]) v.refs.Remove(arr[index])
arr[index] = item arr[index] = item
@ -1141,7 +1149,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case *stackitem.Buffer: case *stackitem.Buffer:
index := toInt(key.BigInt()) index := toInt(key.BigInt())
if index < 0 || index >= t.Len() { 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() bi, err := item.TryInteger()
b := toInt(bi) b := toInt(bi)

View file

@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math"
"math/big" "math/big"
"math/rand" "math/rand"
"testing" "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("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("ByteArray", getTestFuncForVM(prog, 2, []byte{1, 2}, 1))
t.Run("Buffer", getTestFuncForVM(prog, 2, stackitem.NewBuffer([]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) { func TestPICKITEMDupArray(t *testing.T) {
@ -1196,8 +1217,33 @@ func TestPICKITEMMap(t *testing.T) {
func TestSETITEMBuffer(t *testing.T) { func TestSETITEMBuffer(t *testing.T) {
prog := makeProgram(opcode.DUP, opcode.REVERSE4, opcode.SETITEM) 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("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("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) { func TestSETITEMMap(t *testing.T) {
@ -1205,6 +1251,16 @@ func TestSETITEMMap(t *testing.T) {
m := stackitem.NewMap() m := stackitem.NewMap()
m.Add(stackitem.Make(5), stackitem.Make(3)) m.Add(stackitem.Make(5), stackitem.Make(3))
runWithArgs(t, prog, []byte{0, 1}, m, 5, m, 5, []byte{0, 1}) 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) { func TestSETITEMBigMapBad(t *testing.T) {