stackitem: limit deserialization to MaxDeserialized items

Follow neo-project/neo#2531. Even though it's not strictly required (our node
handles problematic script just fine) we better be compliant wrt
deserialization behavior. MaxDeserialized is introduced to avoid moving
MaxStackSize which is a VM parameter.
This commit is contained in:
Roman Khimov 2021-07-17 22:21:33 +03:00
parent aab18c3083
commit df2430d5e4
3 changed files with 53 additions and 9 deletions

View file

@ -74,6 +74,7 @@ var (
errTooBigInteger = fmt.Errorf("%w: integer", ErrTooBig)
errTooBigKey = fmt.Errorf("%w: map key", ErrTooBig)
errTooBigSize = fmt.Errorf("%w: size", ErrTooBig)
errTooBigElements = fmt.Errorf("%w: many elements", ErrTooBig)
)
// mkInvConversion creates conversion error with additional metadata (from and

View file

@ -9,6 +9,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/io"
)
// MaxDeserialized is the maximum number one deserialized item can contain
// (including itself).
const MaxDeserialized = 2048
// ErrRecursive is returned on attempts to serialize some recursive stack item
// (like array including an item with reference to the same array).
var ErrRecursive = errors.New("recursive item")
@ -25,6 +29,13 @@ type serContext struct {
seen map[Item]sliceNoPointer
}
// deserContext is an internal deserialization context.
type deserContext struct {
*io.BinReader
allowInvalid bool
limit int
}
// Serialize encodes given Item into the byte slice.
func Serialize(item Item) ([]byte, error) {
sc := serContext{
@ -179,21 +190,36 @@ func Deserialize(data []byte) (Item, error) {
// as a function because Item itself is an interface. Caveat: always check
// reader's error value before using the returned Item.
func DecodeBinary(r *io.BinReader) Item {
return decodeBinary(r, false)
dc := deserContext{
BinReader: r,
allowInvalid: false,
limit: MaxDeserialized,
}
return dc.decodeBinary()
}
// DecodeBinaryProtected is similar to DecodeBinary but allows Interop and
// Invalid values to be present (making it symmetric to EncodeBinaryProtected).
func DecodeBinaryProtected(r *io.BinReader) Item {
return decodeBinary(r, true)
dc := deserContext{
BinReader: r,
allowInvalid: true,
limit: MaxDeserialized,
}
return dc.decodeBinary()
}
func decodeBinary(r *io.BinReader, allowInvalid bool) Item {
func (r *deserContext) decodeBinary() Item {
var t = Type(r.ReadB())
if r.Err != nil {
return nil
}
r.limit--
if r.limit < 0 {
r.Err = errTooBigElements
return nil
}
switch t {
case ByteArrayT, BufferT:
data := r.ReadVarBytes(MaxSize)
@ -216,7 +242,7 @@ func decodeBinary(r *io.BinReader, allowInvalid bool) Item {
}
arr := make([]Item, size)
for i := 0; i < size; i++ {
arr[i] = decodeBinary(r, allowInvalid)
arr[i] = r.decodeBinary()
}
if t == ArrayT {
@ -231,8 +257,8 @@ func decodeBinary(r *io.BinReader, allowInvalid bool) Item {
}
m := NewMap()
for i := 0; i < size; i++ {
key := decodeBinary(r, allowInvalid)
value := decodeBinary(r, allowInvalid)
key := r.decodeBinary()
value := r.decodeBinary()
if r.Err != nil {
break
}
@ -242,12 +268,12 @@ func decodeBinary(r *io.BinReader, allowInvalid bool) Item {
case AnyT:
return Null{}
case InteropT:
if allowInvalid {
if r.allowInvalid {
return NewInterop(nil)
}
fallthrough
default:
if t == InvalidT && allowInvalid {
if t == InvalidT && r.allowInvalid {
return nil
}
r.Err = fmt.Errorf("%w: %v", ErrInvalidType, t)

View file

@ -144,7 +144,7 @@ func TestSerialize(t *testing.T) {
for i := 0; i < MaxArraySize; i++ {
m.Add(Make(i), zeroByteArray)
}
testSerialize(t, nil, m)
// testSerialize(t, nil, m) // It contains too many elements already, so ErrTooBig.
m.Add(Make(100500), zeroByteArray)
data, err := Serialize(m)
@ -154,6 +154,23 @@ func TestSerialize(t *testing.T) {
})
}
func TestDeserializeTooManyElements(t *testing.T) {
item := Make(0)
for i := 0; i < MaxDeserialized-1; i++ { // 1 for zero inner element.
item = Make([]Item{item})
}
data, err := Serialize(item)
require.NoError(t, err)
_, err = Deserialize(data)
require.NoError(t, err)
item = Make([]Item{item})
data, err = Serialize(item)
require.NoError(t, err)
_, err = Deserialize(data)
require.True(t, errors.Is(err, ErrTooBig), err)
}
func BenchmarkEncodeBinary(b *testing.B) {
arr := getBigArray(15)