diff --git a/pkg/vm/contract_checks.go b/pkg/vm/contract_checks.go index 40b7d6289..de5ebf51b 100644 --- a/pkg/vm/contract_checks.go +++ b/pkg/vm/contract_checks.go @@ -160,23 +160,31 @@ func IsScriptCorrect(script []byte, methods bitfield.Field) error { opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL, opcode.ENDTRYL, opcode.CALLL, opcode.PUSHA: - off, _, err := calcJumpOffset(ctx, param) // It does bounds checking. + off, _, err := calcJumpOffset(ctx, param) if err != nil { return err } - jumps.Set(off) + // `calcJumpOffset` does bounds checking but can return `len(script)`. + // This check avoids panic in bitset when script length is a multiple of 64. + if off != len(script) { + jumps.Set(off) + } case opcode.TRY, opcode.TRYL: catchP, finallyP := getTryParams(op, param) off, _, err := calcJumpOffset(ctx, catchP) if err != nil { return err } - jumps.Set(off) + if off != len(script) { + jumps.Set(off) + } off, _, err = calcJumpOffset(ctx, finallyP) if err != nil { return err } - jumps.Set(off) + if off != len(script) { + jumps.Set(off) + } case opcode.NEWARRAYT, opcode.ISTYPE, opcode.CONVERT: typ := stackitem.Type(param[0]) if !typ.IsValid() { diff --git a/pkg/vm/contract_checks_test.go b/pkg/vm/contract_checks_test.go index 083e5db53..7bb9ef08a 100644 --- a/pkg/vm/contract_checks_test.go +++ b/pkg/vm/contract_checks_test.go @@ -191,13 +191,20 @@ func TestIsScriptCorrect(t *testing.T) { t.Run("out of bounds JMPL 1", func(t *testing.T) { bad := getScript() - bad[jmplOff+1] = byte(len(bad) - jmplOff) + bad[jmplOff+1] = byte(len(bad)-jmplOff) + 1 bad[jmplOff+2] = 0 bad[jmplOff+3] = 0 bad[jmplOff+4] = 0 require.Error(t, IsScriptCorrect(bad, nil)) }) + t.Run("JMP to a len(script)", func(t *testing.T) { + bad := make([]byte, 64) // 64 is the word-size of a bitset. + bad[0] = byte(opcode.JMP) + bad[1] = 64 + require.NoError(t, IsScriptCorrect(bad, nil)) + }) + t.Run("bad JMPL offset", func(t *testing.T) { bad := getScript() bad[jmplOff+1] = 0xfe // into JMP @@ -212,10 +219,25 @@ func TestIsScriptCorrect(t *testing.T) { t.Run("out of bounds TRY 2", func(t *testing.T) { bad := getScript() - bad[tryOff+2] = byte(len(bad) - tryOff) + bad[tryOff+1] = byte(len(bad)-tryOff) + 1 require.Error(t, IsScriptCorrect(bad, nil)) }) + t.Run("out of bounds TRY 2", func(t *testing.T) { + bad := getScript() + bad[tryOff+2] = byte(len(bad)-tryOff) + 1 + require.Error(t, IsScriptCorrect(bad, nil)) + }) + + t.Run("TRY with jumps to a len(script)", func(t *testing.T) { + bad := make([]byte, 64) // 64 is the word-size of a bitset. + bad[0] = byte(opcode.TRY) + bad[1] = 64 + bad[2] = 64 + bad[3] = byte(opcode.RET) // pad so that remaining script (PUSHINT8 0) is even in length. + require.NoError(t, IsScriptCorrect(bad, nil)) + }) + t.Run("bad TRYL offset 1", func(t *testing.T) { bad := getScript() bad[trylOff+1] = byte(-(trylOff - jmpOff) - 1) // into "something"