Merge pull request #406 from nspcc-dev/fix/fail-on-first-error

VM: do not pop too many items from stack

JSON tests in neo-vm check stack state even in case of failure.
There are 2 corrections need to be done:

    Pop items in EQUAL one-by-one.
    Do not pop anything from stack in OVER.

I have also implemented operand restriction for SHL/SHR.
This commit is contained in:
Roman Khimov 2019-09-23 17:54:08 +03:00 committed by GitHub
commit 317dc6c5ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 26 deletions

View file

@ -22,6 +22,11 @@ var (
ModeMute Mode = 1 << 0 ModeMute Mode = 1 << 0
) )
const (
maxSHLArg = 256
minSHLArg = -256
)
// VM represents the virtual machine. // VM represents the virtual machine.
type VM struct { type VM struct {
state State state State
@ -318,18 +323,22 @@ func (v *VM) execute(ctx *Context, op Instruction) {
v.estack.PushVal(ab) v.estack.PushVal(ab)
case SUBSTR: case SUBSTR:
l := int(v.estack.Pop().BigInt().Int64()) l := int(v.estack.Pop().BigInt().Int64())
o := int(v.estack.Pop().BigInt().Int64())
s := v.estack.Pop().Bytes()
if l < 0 { if l < 0 {
panic("negative length") panic("negative length")
} }
o := int(v.estack.Pop().BigInt().Int64())
if o < 0 { if o < 0 {
panic("negative index") panic("negative index")
} }
if l+o > len(s) { s := v.estack.Pop().Bytes()
panic("out of bounds access") if o > len(s) {
panic("invalid offset")
} }
v.estack.PushVal(s[o : o+l]) last := l + o
if last > len(s) {
last = len(s)
}
v.estack.PushVal(s[o:last])
case LEFT: case LEFT:
l := int(v.estack.Pop().BigInt().Int64()) l := int(v.estack.Pop().BigInt().Int64())
if l < 0 { if l < 0 {
@ -404,15 +413,10 @@ func (v *VM) execute(ctx *Context, op Instruction) {
} }
case OVER: case OVER:
b := v.estack.Pop() a := v.estack.Peek(1)
if b == nil {
panic("no top-level element found")
}
a := v.estack.Peek(0)
if a == nil { if a == nil {
panic("no second element found") panic("no second element found")
} }
v.estack.Push(b)
v.estack.Push(a) v.estack.Push(a)
case PICK: case PICK:
@ -509,21 +513,19 @@ func (v *VM) execute(ctx *Context, op Instruction) {
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Mod(a, b)) v.estack.PushVal(new(big.Int).Mod(a, b))
case SHL: case SHL, SHR:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt().Int64()
if b.Int64() == 0 { if b == 0 {
return return
} else if b < minSHLArg || b > maxSHLArg {
panic(fmt.Sprintf("operand must be between %d and %d", minSHLArg, maxSHLArg))
} }
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Lsh(a, uint(b.Int64()))) if op == SHL {
v.estack.PushVal(new(big.Int).Lsh(a, uint(b)))
case SHR: } else {
b := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Rsh(a, uint(b)))
if b.Int64() == 0 {
return
} }
a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Rsh(a, uint(b.Int64())))
case BOOLAND: case BOOLAND:
b := v.estack.Pop().Bool() b := v.estack.Pop().Bool()

View file

@ -305,6 +305,15 @@ func TestSHRZero(t *testing.T) {
assert.Equal(t, makeStackItem([]byte{0, 1}), vm.estack.Pop().value) assert.Equal(t, makeStackItem([]byte{0, 1}), vm.estack.Pop().value)
} }
func TestSHRSmallValue(t *testing.T) {
prog := makeProgram(SHR)
vm := load(prog)
vm.estack.PushVal(5)
vm.estack.PushVal(-257)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestSHLGood(t *testing.T) { func TestSHLGood(t *testing.T) {
prog := makeProgram(SHL) prog := makeProgram(SHL)
vm := load(prog) vm := load(prog)
@ -327,6 +336,15 @@ func TestSHLZero(t *testing.T) {
assert.Equal(t, makeStackItem([]byte{0, 1}), vm.estack.Pop().value) assert.Equal(t, makeStackItem([]byte{0, 1}), vm.estack.Pop().value)
} }
func TestSHLBigValue(t *testing.T) {
prog := makeProgram(SHL)
vm := load(prog)
vm.estack.PushVal(5)
vm.estack.PushVal(257)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestLT(t *testing.T) { func TestLT(t *testing.T) {
prog := makeProgram(LT) prog := makeProgram(LT)
vm := load(prog) vm := load(prog)
@ -920,6 +938,8 @@ func TestOVERbadNoitem(t *testing.T) {
vm.estack.PushVal(1) vm.estack.PushVal(1)
vm.Run() vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState)) assert.Equal(t, true, vm.state.HasFlag(faultState))
assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, makeStackItem(1), vm.estack.Pop().value)
} }
func TestOVERbadNoitems(t *testing.T) { func TestOVERbadNoitems(t *testing.T) {
@ -1138,20 +1158,22 @@ func TestSUBSTRBadOffset(t *testing.T) {
prog := makeProgram(SUBSTR) prog := makeProgram(SUBSTR)
vm := load(prog) vm := load(prog)
vm.estack.PushVal([]byte("abcdef")) vm.estack.PushVal([]byte("abcdef"))
vm.estack.PushVal(6) vm.estack.PushVal(7)
vm.estack.PushVal(1) vm.estack.PushVal(1)
vm.Run() vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState)) assert.Equal(t, true, vm.state.HasFlag(faultState))
} }
func TestSUBSTRBadLen(t *testing.T) { func TestSUBSTRBigLen(t *testing.T) {
prog := makeProgram(SUBSTR) prog := makeProgram(SUBSTR)
vm := load(prog) vm := load(prog)
vm.estack.PushVal([]byte("abcdef")) vm.estack.PushVal([]byte("abcdef"))
vm.estack.PushVal(1) vm.estack.PushVal(1)
vm.estack.PushVal(6) vm.estack.PushVal(6)
vm.Run() vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState)) assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, []byte("bcdef"), vm.estack.Pop().Bytes())
} }
func TestSUBSTRBad387(t *testing.T) { func TestSUBSTRBad387(t *testing.T) {
@ -1163,7 +1185,9 @@ func TestSUBSTRBad387(t *testing.T) {
vm.estack.PushVal(1) vm.estack.PushVal(1)
vm.estack.PushVal(6) vm.estack.PushVal(6)
vm.Run() vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState)) assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, []byte("bcdef"), vm.estack.Pop().Bytes())
} }
func TestSUBSTRBadNegativeOffset(t *testing.T) { func TestSUBSTRBadNegativeOffset(t *testing.T) {