parent
a6e25cffde
commit
158f0d9d9c
8 changed files with 321 additions and 14 deletions
|
@ -22,6 +22,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util/bitfield"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -273,6 +275,10 @@ func (m *Management) Deploy(d dao.DAO, sender util.Uint160, neff *nef.File, mani
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid manifest: %w", err)
|
return nil, fmt.Errorf("invalid manifest: %w", err)
|
||||||
}
|
}
|
||||||
|
err = checkScriptAndMethods(neff.Script, manif.ABI.Methods)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
newcontract := &state.Contract{
|
newcontract := &state.Contract{
|
||||||
ID: id,
|
ID: id,
|
||||||
Hash: h,
|
Hash: h,
|
||||||
|
@ -334,6 +340,10 @@ func (m *Management) Update(d dao.DAO, hash util.Uint160, neff *nef.File, manif
|
||||||
m.markUpdated(hash)
|
m.markUpdated(hash)
|
||||||
contract.Manifest = *manif
|
contract.Manifest = *manif
|
||||||
}
|
}
|
||||||
|
err = checkScriptAndMethods(contract.NEF.Script, contract.Manifest.ABI.Methods)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
contract.UpdateCounter++
|
contract.UpdateCounter++
|
||||||
err = m.PutContractState(d, contract)
|
err = m.PutContractState(d, contract)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -551,3 +561,15 @@ func (m *Management) emitNotification(ic *interop.Context, name string, hash uti
|
||||||
}
|
}
|
||||||
ic.Notifications = append(ic.Notifications, ne)
|
ic.Notifications = append(ic.Notifications, ne)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkScriptAndMethods(script []byte, methods []manifest.Method) error {
|
||||||
|
l := len(script)
|
||||||
|
offsets := bitfield.New(l)
|
||||||
|
for i := range methods {
|
||||||
|
if methods[i].Offset >= l {
|
||||||
|
return errors.New("out of bounds method offset")
|
||||||
|
}
|
||||||
|
offsets.Set(methods[i].Offset)
|
||||||
|
}
|
||||||
|
return vm.IsScriptCorrect(script, offsets)
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ func TestDeployGetUpdateDestroyContract(t *testing.T) {
|
||||||
mgmt := newManagement()
|
mgmt := newManagement()
|
||||||
d := dao.NewCached(dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false))
|
d := dao.NewCached(dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false))
|
||||||
mgmt.Initialize(&interop.Context{DAO: d})
|
mgmt.Initialize(&interop.Context{DAO: d})
|
||||||
script := []byte{1}
|
script := []byte{byte(opcode.RET)}
|
||||||
sender := util.Uint160{1, 2, 3}
|
sender := util.Uint160{1, 2, 3}
|
||||||
ne, err := nef.NewFile(script)
|
ne, err := nef.NewFile(script)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -163,6 +163,17 @@ func TestContractDeploy(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkFAULTState(t, res)
|
checkFAULTState(t, res)
|
||||||
})
|
})
|
||||||
|
t.Run("bad script in NEF", func(t *testing.T) {
|
||||||
|
nf, err := nef.FileFromBytes(nef1b) // make a full copy
|
||||||
|
require.NoError(t, err)
|
||||||
|
nf.Script[0] = 0xff
|
||||||
|
nf.CalculateChecksum()
|
||||||
|
nefbad, err := nf.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nefbad, manif1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkFAULTState(t, res)
|
||||||
|
})
|
||||||
t.Run("int for manifest", func(t *testing.T) {
|
t.Run("int for manifest", func(t *testing.T) {
|
||||||
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, int64(1))
|
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, int64(1))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -198,6 +209,32 @@ func TestContractDeploy(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkFAULTState(t, res)
|
checkFAULTState(t, res)
|
||||||
})
|
})
|
||||||
|
t.Run("bad methods in manifest 1", func(t *testing.T) {
|
||||||
|
var badManifest = cs1.Manifest
|
||||||
|
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
||||||
|
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
||||||
|
badManifest.ABI.Methods[0].Offset = 100500 // out of bounds
|
||||||
|
|
||||||
|
manifB, err := json.Marshal(badManifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manifB)
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkFAULTState(t, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad methods in manifest 2", func(t *testing.T) {
|
||||||
|
var badManifest = cs1.Manifest
|
||||||
|
badManifest.ABI.Methods = make([]manifest.Method, len(cs1.Manifest.ABI.Methods))
|
||||||
|
copy(badManifest.ABI.Methods, cs1.Manifest.ABI.Methods)
|
||||||
|
badManifest.ABI.Methods[0].Offset = len(cs1.NEF.Script) - 2 // Ends with `CALLT(X,X);RET`.
|
||||||
|
|
||||||
|
manifB, err := json.Marshal(badManifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manifB)
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkFAULTState(t, res)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("not enough GAS", func(t *testing.T) {
|
t.Run("not enough GAS", func(t *testing.T) {
|
||||||
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "deploy", nef1b, manif1)
|
res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "deploy", nef1b, manif1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -382,6 +419,19 @@ func TestContractUpdate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkFAULTState(t, res)
|
checkFAULTState(t, res)
|
||||||
})
|
})
|
||||||
|
t.Run("manifest and script mismatch", func(t *testing.T) {
|
||||||
|
nf, err := nef.FileFromBytes(nef1b) // Make a full copy.
|
||||||
|
require.NoError(t, err)
|
||||||
|
nf.Script = append(nf.Script, byte(opcode.RET))
|
||||||
|
copy(nf.Script[1:], nf.Script) // Now all method offsets are wrong.
|
||||||
|
nf.Script[0] = byte(opcode.RET) // Even though the script is correct.
|
||||||
|
nf.CalculateChecksum()
|
||||||
|
nefnew, err := nf.Bytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nefnew, manif1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkFAULTState(t, res)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("change name", func(t *testing.T) {
|
t.Run("change name", func(t *testing.T) {
|
||||||
var badManifest = cs1.Manifest
|
var badManifest = cs1.Manifest
|
||||||
|
|
|
@ -62,3 +62,18 @@ func (f Field) Equals(o Field) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSubset returns true when f is a subset of o (only has bits set that are
|
||||||
|
// set in o).
|
||||||
|
func (f Field) IsSubset(o Field) bool {
|
||||||
|
if len(f) > len(o) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range f {
|
||||||
|
r := f[i] & o[i]
|
||||||
|
if r != f[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ func TestFields(t *testing.T) {
|
||||||
b.Set(100)
|
b.Set(100)
|
||||||
require.True(t, a.IsSet(42))
|
require.True(t, a.IsSet(42))
|
||||||
require.False(t, b.IsSet(43))
|
require.False(t, b.IsSet(43))
|
||||||
|
require.True(t, a.IsSubset(b))
|
||||||
|
|
||||||
v := uint64(1<<10 | 1<<42)
|
v := uint64(1<<10 | 1<<42)
|
||||||
require.Equal(t, v, a[0])
|
require.Equal(t, v, a[0])
|
||||||
|
@ -28,14 +29,17 @@ func TestFields(t *testing.T) {
|
||||||
require.True(t, c.Equals(b))
|
require.True(t, c.Equals(b))
|
||||||
|
|
||||||
z := New(128)
|
z := New(128)
|
||||||
|
require.True(t, z.IsSubset(c))
|
||||||
c.And(a)
|
c.And(a)
|
||||||
require.True(t, c.Equals(b))
|
require.True(t, c.Equals(b))
|
||||||
c.And(z)
|
c.And(z)
|
||||||
require.True(t, c.Equals(z))
|
require.True(t, c.Equals(z))
|
||||||
|
|
||||||
c = New(64)
|
c = New(64)
|
||||||
|
require.False(t, z.IsSubset(c))
|
||||||
c[0] = a[0]
|
c[0] = a[0]
|
||||||
require.False(t, c.Equals(a))
|
require.False(t, c.Equals(a))
|
||||||
|
require.True(t, c.IsSubset(a))
|
||||||
|
|
||||||
b.And(c)
|
b.And(c)
|
||||||
require.False(t, b.Equals(a))
|
require.False(t, b.Equals(a))
|
||||||
|
|
|
@ -2,9 +2,12 @@ package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util/bitfield"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
@ -127,3 +130,62 @@ func IsSignatureContract(script []byte) bool {
|
||||||
func IsStandardContract(script []byte) bool {
|
func IsStandardContract(script []byte) bool {
|
||||||
return IsSignatureContract(script) || IsMultiSigContract(script)
|
return IsSignatureContract(script) || IsMultiSigContract(script)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsScriptCorrect checks script for errors and mask provided for correctness wrt
|
||||||
|
// instruction boundaries. Normally it returns nil, but can return some specific
|
||||||
|
// error if there is any.
|
||||||
|
func IsScriptCorrect(script []byte, methods bitfield.Field) error {
|
||||||
|
var (
|
||||||
|
l = len(script)
|
||||||
|
instrs = bitfield.New(l)
|
||||||
|
jumps = bitfield.New(l)
|
||||||
|
)
|
||||||
|
ctx := NewContext(script)
|
||||||
|
for ctx.nextip < l {
|
||||||
|
op, param, err := ctx.Next()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
instrs.Set(ctx.ip)
|
||||||
|
switch op {
|
||||||
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
||||||
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
||||||
|
opcode.CALL, opcode.ENDTRY, opcode.JMPL, opcode.JMPIFL,
|
||||||
|
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.
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
off, _, err = calcJumpOffset(ctx, finallyP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jumps.Set(off)
|
||||||
|
case opcode.NEWARRAYT, opcode.ISTYPE, opcode.CONVERT:
|
||||||
|
typ := stackitem.Type(param[0])
|
||||||
|
if !typ.IsValid() {
|
||||||
|
return fmt.Errorf("invalid type specification at offset %d", ctx.ip)
|
||||||
|
}
|
||||||
|
if typ == stackitem.AnyT && op != opcode.NEWARRAYT {
|
||||||
|
return fmt.Errorf("using type ANY is incorrect at offset %d", ctx.ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !jumps.IsSubset(instrs) {
|
||||||
|
return errors.New("some jumps are done to wrong offsets (not to instruction boundary)")
|
||||||
|
}
|
||||||
|
if methods != nil && !methods.IsSubset(instrs) {
|
||||||
|
return errors.New("some methods point to wrong offsets (not to instruction boundary)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,8 +5,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util/bitfield"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -115,3 +119,152 @@ func TestIsMultiSigContract(t *testing.T) {
|
||||||
assert.False(t, IsMultiSigContract(prog))
|
assert.False(t, IsMultiSigContract(prog))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsScriptCorrect(t *testing.T) {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.String(w.BinWriter, "something")
|
||||||
|
|
||||||
|
jmpOff := w.Len()
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.JMP, opcode.Opcode(-jmpOff))
|
||||||
|
|
||||||
|
retOff := w.Len()
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
||||||
|
|
||||||
|
jmplOff := w.Len()
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.JMPL, opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff))
|
||||||
|
|
||||||
|
tryOff := w.Len()
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.TRY, opcode.Opcode(3), opcode.Opcode(0xfb)) // -5
|
||||||
|
|
||||||
|
trylOff := w.Len()
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.TRYL, opcode.Opcode(0xfd), opcode.Opcode(0xff), opcode.Opcode(0xff), opcode.Opcode(0xff),
|
||||||
|
opcode.Opcode(9), opcode.Opcode(0), opcode.Opcode(0), opcode.Opcode(0))
|
||||||
|
|
||||||
|
istypeOff := w.Len()
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ISTYPE, opcode.Opcode(stackitem.IntegerT))
|
||||||
|
|
||||||
|
pushOff := w.Len()
|
||||||
|
emit.String(w.BinWriter, "else")
|
||||||
|
|
||||||
|
good := w.Bytes()
|
||||||
|
|
||||||
|
getScript := func() []byte {
|
||||||
|
s := make([]byte, len(good))
|
||||||
|
copy(s, good)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
require.NoError(t, IsScriptCorrect(good, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad instruction", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[retOff] = 0xff
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("out of bounds JMP 1", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[jmpOff+1] = 0x80 // -128
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("out of bounds JMP 2", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[jmpOff+1] = 0x7f
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad JMP offset 1", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[jmpOff+1] = 0xff // into "something"
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad JMP offset 2", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[jmpOff+1] = byte(pushOff - jmpOff + 1)
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("out of bounds JMPL 1", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[jmplOff+1] = byte(-jmplOff - 1)
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("out of bounds JMPL 1", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[jmplOff+1] = byte(len(bad) - jmplOff)
|
||||||
|
bad[jmplOff+2] = 0
|
||||||
|
bad[jmplOff+3] = 0
|
||||||
|
bad[jmplOff+4] = 0
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad JMPL offset", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[jmplOff+1] = 0xfe // into JMP
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("out of bounds TRY 1", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[tryOff+1] = byte(-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)
|
||||||
|
require.Error(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"
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad TRYL offset 2", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[trylOff+5] = byte(len(bad) - trylOff - 1)
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad ISTYPE type", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[istypeOff+1] = byte(0xff)
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad ISTYPE type (Any)", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[istypeOff+1] = byte(stackitem.AnyT)
|
||||||
|
require.Error(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good NEWARRAY_T type", func(t *testing.T) {
|
||||||
|
bad := getScript()
|
||||||
|
bad[istypeOff] = byte(opcode.NEWARRAYT)
|
||||||
|
bad[istypeOff+1] = byte(stackitem.AnyT)
|
||||||
|
require.NoError(t, IsScriptCorrect(bad, nil))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good methods", func(t *testing.T) {
|
||||||
|
methods := bitfield.New(len(good))
|
||||||
|
methods.Set(retOff)
|
||||||
|
methods.Set(tryOff)
|
||||||
|
methods.Set(pushOff)
|
||||||
|
require.NoError(t, IsScriptCorrect(good, methods))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad methods", func(t *testing.T) {
|
||||||
|
methods := bitfield.New(len(good))
|
||||||
|
methods.Set(retOff)
|
||||||
|
methods.Set(tryOff)
|
||||||
|
methods.Set(pushOff + 1)
|
||||||
|
require.Error(t, IsScriptCorrect(good, methods))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
26
pkg/vm/vm.go
26
pkg/vm/vm.go
|
@ -185,11 +185,11 @@ func (v *VM) PrintOps(out io.Writer) {
|
||||||
opcode.JMPEQL, opcode.JMPNEL,
|
opcode.JMPEQL, opcode.JMPNEL,
|
||||||
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
|
||||||
opcode.PUSHA, opcode.ENDTRY, opcode.ENDTRYL:
|
opcode.PUSHA, opcode.ENDTRY, opcode.ENDTRYL:
|
||||||
desc = v.getOffsetDesc(ctx, parameter)
|
desc = getOffsetDesc(ctx, parameter)
|
||||||
case opcode.TRY, opcode.TRYL:
|
case opcode.TRY, opcode.TRYL:
|
||||||
catchP, finallyP := getTryParams(instr, parameter)
|
catchP, finallyP := getTryParams(instr, parameter)
|
||||||
desc = fmt.Sprintf("catch %s, finally %s",
|
desc = fmt.Sprintf("catch %s, finally %s",
|
||||||
v.getOffsetDesc(ctx, catchP), v.getOffsetDesc(ctx, finallyP))
|
getOffsetDesc(ctx, catchP), getOffsetDesc(ctx, finallyP))
|
||||||
case opcode.INITSSLOT:
|
case opcode.INITSSLOT:
|
||||||
desc = fmt.Sprint(parameter[0])
|
desc = fmt.Sprint(parameter[0])
|
||||||
case opcode.CONVERT, opcode.ISTYPE:
|
case opcode.CONVERT, opcode.ISTYPE:
|
||||||
|
@ -226,8 +226,8 @@ func (v *VM) PrintOps(out io.Writer) {
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VM) getOffsetDesc(ctx *Context, parameter []byte) string {
|
func getOffsetDesc(ctx *Context, parameter []byte) string {
|
||||||
offset, rOffset, err := v.calcJumpOffset(ctx, parameter)
|
offset, rOffset, err := calcJumpOffset(ctx, parameter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("ERROR: %v", err)
|
return fmt.Sprintf("ERROR: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -552,7 +552,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
v.estack.PushVal(parameter)
|
v.estack.PushVal(parameter)
|
||||||
|
|
||||||
case opcode.PUSHA:
|
case opcode.PUSHA:
|
||||||
n := v.getJumpOffset(ctx, parameter)
|
n := getJumpOffset(ctx, parameter)
|
||||||
ptr := stackitem.NewPointerWithHash(n, ctx.prog, ctx.ScriptHash())
|
ptr := stackitem.NewPointerWithHash(n, ctx.prog, ctx.ScriptHash())
|
||||||
v.estack.PushVal(ptr)
|
v.estack.PushVal(ptr)
|
||||||
|
|
||||||
|
@ -1249,7 +1249,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
opcode.JMPEQ, opcode.JMPEQL, opcode.JMPNE, opcode.JMPNEL,
|
opcode.JMPEQ, opcode.JMPEQL, opcode.JMPNE, opcode.JMPNEL,
|
||||||
opcode.JMPGT, opcode.JMPGTL, opcode.JMPGE, opcode.JMPGEL,
|
opcode.JMPGT, opcode.JMPGTL, opcode.JMPGE, opcode.JMPGEL,
|
||||||
opcode.JMPLT, opcode.JMPLTL, opcode.JMPLE, opcode.JMPLEL:
|
opcode.JMPLT, opcode.JMPLTL, opcode.JMPLE, opcode.JMPLEL:
|
||||||
offset := v.getJumpOffset(ctx, parameter)
|
offset := getJumpOffset(ctx, parameter)
|
||||||
cond := true
|
cond := true
|
||||||
switch op {
|
switch op {
|
||||||
case opcode.JMP, opcode.JMPL:
|
case opcode.JMP, opcode.JMPL:
|
||||||
|
@ -1268,7 +1268,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
case opcode.CALL, opcode.CALLL:
|
case opcode.CALL, opcode.CALLL:
|
||||||
// Note: jump offset must be calculated regarding to new context,
|
// Note: jump offset must be calculated regarding to new context,
|
||||||
// but it is cloned and thus has the same script and instruction pointer.
|
// but it is cloned and thus has the same script and instruction pointer.
|
||||||
v.call(ctx, v.getJumpOffset(ctx, parameter))
|
v.call(ctx, getJumpOffset(ctx, parameter))
|
||||||
|
|
||||||
case opcode.CALLA:
|
case opcode.CALLA:
|
||||||
ptr := v.estack.Pop().Item().(*stackitem.Pointer)
|
ptr := v.estack.Pop().Item().(*stackitem.Pointer)
|
||||||
|
@ -1406,8 +1406,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
if ctx.tryStack.Len() >= MaxTryNestingDepth {
|
if ctx.tryStack.Len() >= MaxTryNestingDepth {
|
||||||
panic("maximum TRY depth exceeded")
|
panic("maximum TRY depth exceeded")
|
||||||
}
|
}
|
||||||
cOffset := v.getJumpOffset(ctx, catchP)
|
cOffset := getJumpOffset(ctx, catchP)
|
||||||
fOffset := v.getJumpOffset(ctx, finallyP)
|
fOffset := getJumpOffset(ctx, finallyP)
|
||||||
if cOffset == ctx.ip && fOffset == ctx.ip {
|
if cOffset == ctx.ip && fOffset == ctx.ip {
|
||||||
panic("invalid offset for TRY*")
|
panic("invalid offset for TRY*")
|
||||||
} else if cOffset == ctx.ip {
|
} else if cOffset == ctx.ip {
|
||||||
|
@ -1423,7 +1423,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
if eCtx.State == eFinally {
|
if eCtx.State == eFinally {
|
||||||
panic("invalid exception handling state during ENDTRY*")
|
panic("invalid exception handling state during ENDTRY*")
|
||||||
}
|
}
|
||||||
eOffset := v.getJumpOffset(ctx, parameter)
|
eOffset := getJumpOffset(ctx, parameter)
|
||||||
if eCtx.HasFinally() {
|
if eCtx.HasFinally() {
|
||||||
eCtx.State = eFinally
|
eCtx.State = eFinally
|
||||||
eCtx.EndOffset = eOffset
|
eCtx.EndOffset = eOffset
|
||||||
|
@ -1527,8 +1527,8 @@ func (v *VM) call(ctx *Context, offset int) {
|
||||||
// to a which JMP should be performed.
|
// to a which JMP should be performed.
|
||||||
// parameter should have length either 1 or 4 and
|
// parameter should have length either 1 or 4 and
|
||||||
// is interpreted as little-endian.
|
// is interpreted as little-endian.
|
||||||
func (v *VM) getJumpOffset(ctx *Context, parameter []byte) int {
|
func getJumpOffset(ctx *Context, parameter []byte) int {
|
||||||
offset, _, err := v.calcJumpOffset(ctx, parameter)
|
offset, _, err := calcJumpOffset(ctx, parameter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -1537,7 +1537,7 @@ func (v *VM) getJumpOffset(ctx *Context, parameter []byte) int {
|
||||||
|
|
||||||
// calcJumpOffset returns absolute and relative offset of JMP/CALL/TRY instructions
|
// calcJumpOffset returns absolute and relative offset of JMP/CALL/TRY instructions
|
||||||
// either in short (1-byte) or long (4-byte) form.
|
// either in short (1-byte) or long (4-byte) form.
|
||||||
func (v *VM) calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
|
func calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
|
||||||
var rOffset int32
|
var rOffset int32
|
||||||
switch l := len(parameter); l {
|
switch l := len(parameter); l {
|
||||||
case 1:
|
case 1:
|
||||||
|
|
Loading…
Reference in a new issue