native/std: limit input size for some methods

Also fix prices accordingly.
This commit is contained in:
Evgeniy Stratonikov 2021-04-30 11:44:06 +03:00
parent e4b34833da
commit 82a6c3266c
2 changed files with 55 additions and 19 deletions

View file

@ -22,13 +22,20 @@ type Std struct {
interop.ContractMD interop.ContractMD
} }
const stdContractID = -2 const (
stdContractID = -2
// stdMaxInputLength is the maximum input length for string-related methods.
stdMaxInputLength = 1024
)
var ( var (
// ErrInvalidBase is returned when base is invalid. // ErrInvalidBase is returned when base is invalid.
ErrInvalidBase = errors.New("invalid base") ErrInvalidBase = errors.New("invalid base")
// ErrInvalidFormat is returned when string is not a number. // ErrInvalidFormat is returned when string is not a number.
ErrInvalidFormat = errors.New("invalid format") ErrInvalidFormat = errors.New("invalid format")
// ErrTooBigInput is returned when input exceeds size limit.
ErrTooBigInput = errors.New("input is too big")
) )
func newStd() *Std { func newStd() *Std {
@ -69,32 +76,32 @@ func newStd() *Std {
desc = newDescriptor("atoi", smartcontract.IntegerType, desc = newDescriptor("atoi", smartcontract.IntegerType,
manifest.NewParameter("value", smartcontract.StringType), manifest.NewParameter("value", smartcontract.StringType),
manifest.NewParameter("base", smartcontract.IntegerType)) manifest.NewParameter("base", smartcontract.IntegerType))
md = newMethodAndPrice(s.atoi, 1<<12, callflag.NoneFlag) md = newMethodAndPrice(s.atoi, 1<<6, callflag.NoneFlag)
s.AddMethod(md, desc) s.AddMethod(md, desc)
desc = newDescriptor("atoi", smartcontract.IntegerType, desc = newDescriptor("atoi", smartcontract.IntegerType,
manifest.NewParameter("value", smartcontract.StringType)) manifest.NewParameter("value", smartcontract.StringType))
md = newMethodAndPrice(s.atoi10, 1<<12, callflag.NoneFlag) md = newMethodAndPrice(s.atoi10, 1<<6, callflag.NoneFlag)
s.AddMethod(md, desc) s.AddMethod(md, desc)
desc = newDescriptor("base64Encode", smartcontract.StringType, desc = newDescriptor("base64Encode", smartcontract.StringType,
manifest.NewParameter("data", smartcontract.ByteArrayType)) manifest.NewParameter("data", smartcontract.ByteArrayType))
md = newMethodAndPrice(s.base64Encode, 1<<12, callflag.NoneFlag) md = newMethodAndPrice(s.base64Encode, 1<<5, callflag.NoneFlag)
s.AddMethod(md, desc) s.AddMethod(md, desc)
desc = newDescriptor("base64Decode", smartcontract.ByteArrayType, desc = newDescriptor("base64Decode", smartcontract.ByteArrayType,
manifest.NewParameter("s", smartcontract.StringType)) manifest.NewParameter("s", smartcontract.StringType))
md = newMethodAndPrice(s.base64Decode, 1<<12, callflag.NoneFlag) md = newMethodAndPrice(s.base64Decode, 1<<5, callflag.NoneFlag)
s.AddMethod(md, desc) s.AddMethod(md, desc)
desc = newDescriptor("base58Encode", smartcontract.StringType, desc = newDescriptor("base58Encode", smartcontract.StringType,
manifest.NewParameter("data", smartcontract.ByteArrayType)) manifest.NewParameter("data", smartcontract.ByteArrayType))
md = newMethodAndPrice(s.base58Encode, 1<<12, callflag.NoneFlag) md = newMethodAndPrice(s.base58Encode, 1<<13, callflag.NoneFlag)
s.AddMethod(md, desc) s.AddMethod(md, desc)
desc = newDescriptor("base58Decode", smartcontract.ByteArrayType, desc = newDescriptor("base58Decode", smartcontract.ByteArrayType,
manifest.NewParameter("s", smartcontract.StringType)) manifest.NewParameter("s", smartcontract.StringType))
md = newMethodAndPrice(s.base58Decode, 1<<12, callflag.NoneFlag) md = newMethodAndPrice(s.base58Decode, 1<<10, callflag.NoneFlag)
s.AddMethod(md, desc) s.AddMethod(md, desc)
return s return s
@ -186,7 +193,7 @@ func (s *Std) itoa(_ *interop.Context, args []stackitem.Item) stackitem.Item {
} }
func (s *Std) atoi10(_ *interop.Context, args []stackitem.Item) stackitem.Item { func (s *Std) atoi10(_ *interop.Context, args []stackitem.Item) stackitem.Item {
num := toString(args[0]) num := s.toLimitedString(args[0])
res := s.atoi10Aux(num) res := s.atoi10Aux(num)
return stackitem.NewBigInteger(res) return stackitem.NewBigInteger(res)
} }
@ -200,7 +207,7 @@ func (s *Std) atoi10Aux(num string) *big.Int {
} }
func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item { func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item {
num := toString(args[0]) num := s.toLimitedString(args[0])
base := toBigInt(args[1]) base := toBigInt(args[1])
if !base.IsInt64() { if !base.IsInt64() {
panic(ErrInvalidBase) panic(ErrInvalidBase)
@ -238,17 +245,14 @@ func reverse(b []byte) {
} }
func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item { func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src, err := args[0].TryBytes() src := s.toLimitedBytes(args[0])
if err != nil {
panic(err)
}
result := base64.StdEncoding.EncodeToString(src) result := base64.StdEncoding.EncodeToString(src)
return stackitem.NewByteArray([]byte(result)) return stackitem.NewByteArray([]byte(result))
} }
func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item { func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src := toString(args[0]) src := s.toLimitedString(args[0])
result, err := base64.StdEncoding.DecodeString(src) result, err := base64.StdEncoding.DecodeString(src)
if err != nil { if err != nil {
panic(err) panic(err)
@ -258,17 +262,14 @@ func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.
} }
func (s *Std) base58Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item { func (s *Std) base58Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src, err := args[0].TryBytes() src := s.toLimitedBytes(args[0])
if err != nil {
panic(err)
}
result := base58.Encode(src) result := base58.Encode(src)
return stackitem.NewByteArray([]byte(result)) return stackitem.NewByteArray([]byte(result))
} }
func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item { func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item {
src := toString(args[0]) src := s.toLimitedString(args[0])
result, err := base58.Decode(src) result, err := base58.Decode(src)
if err != nil { if err != nil {
panic(err) panic(err)
@ -296,3 +297,22 @@ func (s *Std) OnPersist(ic *interop.Context) error {
func (s *Std) PostPersist(ic *interop.Context) error { func (s *Std) PostPersist(ic *interop.Context) error {
return nil return nil
} }
func (s *Std) toLimitedBytes(item stackitem.Item) []byte {
src, err := item.TryBytes()
if err != nil {
panic(err)
}
if len(src) > stdMaxInputLength {
panic(ErrTooBigInput)
}
return src
}
func (s *Std) toLimitedString(item stackitem.Item) string {
src := toString(item)
if len(src) > stdMaxInputLength {
panic(ErrTooBigInput)
}
return src
}

View file

@ -5,6 +5,7 @@ import (
"encoding/hex" "encoding/hex"
"math" "math"
"math/big" "math/big"
"strings"
"testing" "testing"
"github.com/mr-tron/base58" "github.com/mr-tron/base58"
@ -102,6 +103,7 @@ func TestStdLibItoaAtoi(t *testing.T) {
{"1_000", big.NewInt(10), ErrInvalidFormat}, {"1_000", big.NewInt(10), ErrInvalidFormat},
{"FE", big.NewInt(10), ErrInvalidFormat}, {"FE", big.NewInt(10), ErrInvalidFormat},
{"XD", big.NewInt(16), ErrInvalidFormat}, {"XD", big.NewInt(16), ErrInvalidFormat},
{strings.Repeat("0", stdMaxInputLength+1), big.NewInt(10), ErrTooBigInput},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -164,18 +166,28 @@ func TestStdLibEncodeDecode(t *testing.T) {
ic := &interop.Context{VM: vm.New()} ic := &interop.Context{VM: vm.New()}
var actual stackitem.Item var actual stackitem.Item
bigInputArgs := []stackitem.Item{stackitem.Make(strings.Repeat("6", stdMaxInputLength+1))}
t.Run("Encode64", func(t *testing.T) { t.Run("Encode64", func(t *testing.T) {
require.NotPanics(t, func() { require.NotPanics(t, func() {
actual = s.base64Encode(ic, []stackitem.Item{stackitem.Make(original)}) actual = s.base64Encode(ic, []stackitem.Item{stackitem.Make(original)})
}) })
require.Equal(t, stackitem.Make(encoded64), actual) require.Equal(t, stackitem.Make(encoded64), actual)
}) })
t.Run("Encode64/error", func(t *testing.T) {
require.PanicsWithError(t, ErrTooBigInput.Error(),
func() { s.base64Encode(ic, bigInputArgs) })
})
t.Run("Encode58", func(t *testing.T) { t.Run("Encode58", func(t *testing.T) {
require.NotPanics(t, func() { require.NotPanics(t, func() {
actual = s.base58Encode(ic, []stackitem.Item{stackitem.Make(original)}) actual = s.base58Encode(ic, []stackitem.Item{stackitem.Make(original)})
}) })
require.Equal(t, stackitem.Make(encoded58), actual) require.Equal(t, stackitem.Make(encoded58), actual)
}) })
t.Run("Encode58/error", func(t *testing.T) {
require.PanicsWithError(t, ErrTooBigInput.Error(),
func() { s.base58Encode(ic, bigInputArgs) })
})
t.Run("Decode64/positive", func(t *testing.T) { t.Run("Decode64/positive", func(t *testing.T) {
require.NotPanics(t, func() { require.NotPanics(t, func() {
actual = s.base64Decode(ic, []stackitem.Item{stackitem.Make(encoded64)}) actual = s.base64Decode(ic, []stackitem.Item{stackitem.Make(encoded64)})
@ -189,6 +201,8 @@ func TestStdLibEncodeDecode(t *testing.T) {
require.Panics(t, func() { require.Panics(t, func() {
_ = s.base64Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)}) _ = s.base64Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)})
}) })
require.PanicsWithError(t, ErrTooBigInput.Error(),
func() { s.base64Decode(ic, bigInputArgs) })
}) })
t.Run("Decode58/positive", func(t *testing.T) { t.Run("Decode58/positive", func(t *testing.T) {
require.NotPanics(t, func() { require.NotPanics(t, func() {
@ -203,6 +217,8 @@ func TestStdLibEncodeDecode(t *testing.T) {
require.Panics(t, func() { require.Panics(t, func() {
_ = s.base58Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)}) _ = s.base58Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)})
}) })
require.PanicsWithError(t, ErrTooBigInput.Error(),
func() { s.base58Decode(ic, bigInputArgs) })
}) })
} }