Merge pull request #388 from nspcc-dev/substr-left-indexes

VM: harden substr/left/right against bad indexes and improve
PUSHDATA along the way. Fixes #387.
This commit is contained in:
Roman Khimov 2019-09-11 14:52:48 +03:00 committed by GitHub
commit 794d375bfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 152 additions and 7 deletions

View file

@ -89,7 +89,7 @@ func (c *Context) String() string {
func (c *Context) readUint32() uint32 {
start, end := c.IP(), c.IP()+4
if end > len(c.prog) {
return 0
panic("failed to read uint32 parameter")
}
val := binary.LittleEndian.Uint32(c.prog[start:end])
c.ip += 4
@ -99,7 +99,7 @@ func (c *Context) readUint32() uint32 {
func (c *Context) readUint16() uint16 {
start, end := c.IP(), c.IP()+2
if end > len(c.prog) {
return 0
panic("failed to read uint16 parameter")
}
val := binary.LittleEndian.Uint16(c.prog[start:end])
c.ip += 2

View file

@ -262,16 +262,25 @@ func (v *VM) execute(ctx *Context, op Instruction) {
case PUSHDATA1:
n := ctx.readByte()
b := ctx.readBytes(int(n))
if b == nil {
panic("failed to read instruction parameter")
}
v.estack.PushVal(b)
case PUSHDATA2:
n := ctx.readUint16()
b := ctx.readBytes(int(n))
if b == nil {
panic("failed to read instruction parameter")
}
v.estack.PushVal(b)
case PUSHDATA4:
n := ctx.readUint32()
b := ctx.readBytes(int(n))
if b == nil {
panic("failed to read instruction parameter")
}
v.estack.PushVal(b)
// Stack operations.
@ -311,9 +320,21 @@ func (v *VM) execute(ctx *Context, op Instruction) {
l := int(v.estack.Pop().BigInt().Int64())
o := int(v.estack.Pop().BigInt().Int64())
s := v.estack.Pop().Bytes()
if l < 0 {
panic("negative length")
}
if o < 0 {
panic("negative index")
}
if l+o > len(s) {
panic("out of bounds access")
}
v.estack.PushVal(s[o : o+l])
case LEFT:
l := int(v.estack.Pop().BigInt().Int64())
if l < 0 {
panic("negative length")
}
s := v.estack.Pop().Bytes()
if t := len(s); l > t {
l = t
@ -321,6 +342,9 @@ func (v *VM) execute(ctx *Context, op Instruction) {
v.estack.PushVal(s[:l])
case RIGHT:
l := int(v.estack.Pop().BigInt().Int64())
if l < 0 {
panic("negative length")
}
s := v.estack.Pop().Bytes()
v.estack.PushVal(s[len(s)-l:])
case XDROP:

View file

@ -99,16 +99,87 @@ func TestPushm1to16(t *testing.T) {
}
}
func TestPushData1(t *testing.T) {
func TestPushData1BadNoN(t *testing.T) {
prog := []byte{byte(PUSHDATA1)}
vm := load(prog)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestPushData2(t *testing.T) {
func TestPushData1BadN(t *testing.T) {
prog := []byte{byte(PUSHDATA1), 1}
vm := load(prog)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestPushData4(t *testing.T) {
func TestPushData1Good(t *testing.T) {
prog := makeProgram(PUSHDATA1, 3, 1, 2, 3)
vm := load(prog)
vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, []byte{1, 2, 3}, vm.estack.Pop().Bytes())
}
func TestPushData2BadNoN(t *testing.T) {
prog := []byte{byte(PUSHDATA2)}
vm := load(prog)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestPushData2ShortN(t *testing.T) {
prog := []byte{byte(PUSHDATA2), 0}
vm := load(prog)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestPushData2BadN(t *testing.T) {
prog := []byte{byte(PUSHDATA2), 1, 0}
vm := load(prog)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestPushData2Good(t *testing.T) {
prog := makeProgram(PUSHDATA2, 3, 0, 1, 2, 3)
vm := load(prog)
vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, []byte{1, 2, 3}, vm.estack.Pop().Bytes())
}
func TestPushData4BadNoN(t *testing.T) {
prog := []byte{byte(PUSHDATA4)}
vm := load(prog)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestPushData4BadN(t *testing.T) {
prog := []byte{byte(PUSHDATA4), 1, 0, 0, 0}
vm := load(prog)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestPushData4ShortN(t *testing.T) {
prog := []byte{byte(PUSHDATA4), 0, 0, 0}
vm := load(prog)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestPushData4Good(t *testing.T) {
prog := makeProgram(PUSHDATA4, 3, 0, 0, 0, 1, 2, 3)
vm := load(prog)
vm.Run()
assert.Equal(t, false, vm.state.HasFlag(faultState))
assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, []byte{1, 2, 3}, vm.estack.Pop().Bytes())
}
func TestNOTNoArgument(t *testing.T) {
@ -807,6 +878,38 @@ func TestSUBSTRBadLen(t *testing.T) {
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestSUBSTRBad387(t *testing.T) {
prog := makeProgram(SUBSTR)
vm := load(prog)
b := make([]byte, 6, 20)
copy(b, "abcdef")
vm.estack.PushVal(b)
vm.estack.PushVal(1)
vm.estack.PushVal(6)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestSUBSTRBadNegativeOffset(t *testing.T) {
prog := makeProgram(SUBSTR)
vm := load(prog)
vm.estack.PushVal([]byte("abcdef"))
vm.estack.PushVal(-1)
vm.estack.PushVal(3)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestSUBSTRBadNegativeLen(t *testing.T) {
prog := makeProgram(SUBSTR)
vm := load(prog)
vm.estack.PushVal([]byte("abcdef"))
vm.estack.PushVal(3)
vm.estack.PushVal(-1)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestLEFTBadNoArgs(t *testing.T) {
prog := makeProgram(LEFT)
vm := load(prog)
@ -822,6 +925,15 @@ func TestLEFTBadNoString(t *testing.T) {
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestLEFTBadNegativeLen(t *testing.T) {
prog := makeProgram(LEFT)
vm := load(prog)
vm.estack.PushVal([]byte("abcdef"))
vm.estack.PushVal(-1)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestLEFTGood(t *testing.T) {
prog := makeProgram(LEFT)
vm := load(prog)
@ -859,6 +971,15 @@ func TestRIGHTBadNoString(t *testing.T) {
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestRIGHTBadNegativeLen(t *testing.T) {
prog := makeProgram(RIGHT)
vm := load(prog)
vm.estack.PushVal([]byte("abcdef"))
vm.estack.PushVal(-1)
vm.Run()
assert.Equal(t, true, vm.state.HasFlag(faultState))
}
func TestRIGHTGood(t *testing.T) {
prog := makeProgram(RIGHT)
vm := load(prog)