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) errTooBigInteger = fmt.Errorf("%w: integer", ErrTooBig)
errTooBigKey = fmt.Errorf("%w: map key", ErrTooBig) errTooBigKey = fmt.Errorf("%w: map key", ErrTooBig)
errTooBigSize = fmt.Errorf("%w: size", ErrTooBig) errTooBigSize = fmt.Errorf("%w: size", ErrTooBig)
errTooBigElements = fmt.Errorf("%w: many elements", ErrTooBig)
) )
// mkInvConversion creates conversion error with additional metadata (from and // mkInvConversion creates conversion error with additional metadata (from and

View file

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