From 82a6c3266c34bd2c5adab81177863dc29f42f35b Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 30 Apr 2021 11:44:06 +0300 Subject: [PATCH] native/std: limit input size for some methods Also fix prices accordingly. --- pkg/core/native/std.go | 58 +++++++++++++++++++++++++------------ pkg/core/native/std_test.go | 16 ++++++++++ 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/pkg/core/native/std.go b/pkg/core/native/std.go index 8c386c4c9..96bd3bc78 100644 --- a/pkg/core/native/std.go +++ b/pkg/core/native/std.go @@ -22,13 +22,20 @@ type Std struct { interop.ContractMD } -const stdContractID = -2 +const ( + stdContractID = -2 + + // stdMaxInputLength is the maximum input length for string-related methods. + stdMaxInputLength = 1024 +) var ( // ErrInvalidBase is returned when base is invalid. ErrInvalidBase = errors.New("invalid base") // ErrInvalidFormat is returned when string is not a number. ErrInvalidFormat = errors.New("invalid format") + // ErrTooBigInput is returned when input exceeds size limit. + ErrTooBigInput = errors.New("input is too big") ) func newStd() *Std { @@ -69,32 +76,32 @@ func newStd() *Std { desc = newDescriptor("atoi", smartcontract.IntegerType, manifest.NewParameter("value", smartcontract.StringType), 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) desc = newDescriptor("atoi", smartcontract.IntegerType, 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) desc = newDescriptor("base64Encode", smartcontract.StringType, 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) desc = newDescriptor("base64Decode", smartcontract.ByteArrayType, 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) desc = newDescriptor("base58Encode", smartcontract.StringType, 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) desc = newDescriptor("base58Decode", smartcontract.ByteArrayType, 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) 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 { - num := toString(args[0]) + num := s.toLimitedString(args[0]) res := s.atoi10Aux(num) 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 { - num := toString(args[0]) + num := s.toLimitedString(args[0]) base := toBigInt(args[1]) if !base.IsInt64() { panic(ErrInvalidBase) @@ -238,17 +245,14 @@ func reverse(b []byte) { } func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item { - src, err := args[0].TryBytes() - if err != nil { - panic(err) - } + src := s.toLimitedBytes(args[0]) result := base64.StdEncoding.EncodeToString(src) return stackitem.NewByteArray([]byte(result)) } 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) if err != nil { 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 { - src, err := args[0].TryBytes() - if err != nil { - panic(err) - } + src := s.toLimitedBytes(args[0]) result := base58.Encode(src) return stackitem.NewByteArray([]byte(result)) } 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) if err != nil { panic(err) @@ -296,3 +297,22 @@ func (s *Std) OnPersist(ic *interop.Context) error { func (s *Std) PostPersist(ic *interop.Context) error { 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 +} diff --git a/pkg/core/native/std_test.go b/pkg/core/native/std_test.go index bfe4e3f43..49a24b37f 100644 --- a/pkg/core/native/std_test.go +++ b/pkg/core/native/std_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "math" "math/big" + "strings" "testing" "github.com/mr-tron/base58" @@ -102,6 +103,7 @@ func TestStdLibItoaAtoi(t *testing.T) { {"1_000", big.NewInt(10), ErrInvalidFormat}, {"FE", big.NewInt(10), ErrInvalidFormat}, {"XD", big.NewInt(16), ErrInvalidFormat}, + {strings.Repeat("0", stdMaxInputLength+1), big.NewInt(10), ErrTooBigInput}, } for _, tc := range testCases { @@ -164,18 +166,28 @@ func TestStdLibEncodeDecode(t *testing.T) { ic := &interop.Context{VM: vm.New()} var actual stackitem.Item + bigInputArgs := []stackitem.Item{stackitem.Make(strings.Repeat("6", stdMaxInputLength+1))} + t.Run("Encode64", func(t *testing.T) { require.NotPanics(t, func() { actual = s.base64Encode(ic, []stackitem.Item{stackitem.Make(original)}) }) 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) { require.NotPanics(t, func() { actual = s.base58Encode(ic, []stackitem.Item{stackitem.Make(original)}) }) 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) { require.NotPanics(t, func() { actual = s.base64Decode(ic, []stackitem.Item{stackitem.Make(encoded64)}) @@ -189,6 +201,8 @@ func TestStdLibEncodeDecode(t *testing.T) { require.Panics(t, func() { _ = 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) { require.NotPanics(t, func() { @@ -203,6 +217,8 @@ func TestStdLibEncodeDecode(t *testing.T) { require.Panics(t, func() { _ = s.base58Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)}) }) + require.PanicsWithError(t, ErrTooBigInput.Error(), + func() { s.base58Decode(ic, bigInputArgs) }) }) }