From 47a75017deda660fbc52ab21726f9de52886b018 Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Thu, 4 Dec 2025 11:57:07 +0100 Subject: [PATCH] Update fuzz tests Decided to make ReadArray always stop after an error. There is a too high risk of running into an infinite loop. Added size checks for (Writer.)WriteBytes/WriteString/WriteStringFromBytes Add panic recovery to binary marshal helpers. --- msgp/fuzz_test.go | 33 +++++++++++++++++++++++++++++++++ msgp/iter.go | 3 ++- msgp/read.go | 16 ++++++++++++++++ msgp/write.go | 38 ++++++++++++++++++++++++++++++++------ 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/msgp/fuzz_test.go b/msgp/fuzz_test.go index e894c65a..52c5abb9 100644 --- a/msgp/fuzz_test.go +++ b/msgp/fuzz_test.go @@ -117,6 +117,21 @@ func FuzzReader(f *testing.F) { reset() r.ReadUint8() reset() + r.ReadBinaryUnmarshal(encodingReader{}) + reset() + r.ReadTextUnmarshal(encodingReader{}) + reset() + r.ReadTextUnmarshalString(encodingReader{}) + reset() + for range ReadArray(r, r.ReadInt) { + } + reset() + iter, errfn := ReadMap(r, r.ReadString, r.ReadUint64) + for range iter { + } + errfn() + reset() + r.Skip() reset() @@ -170,6 +185,14 @@ func FuzzReadBytes(f *testing.F) { ReadUint64Bytes(data) ReadUintBytes(data) UnmarshalAsJSON(io.Discard, data) + iter, errfn := ReadArrayBytes(data, ReadIntBytes) + for range iter { + } + errfn() + iter2, errfn2 := ReadMapBytes(data, ReadStringBytes, ReadUint64Bytes) + for range iter2 { + } + errfn2() Skip(data) }) } @@ -306,3 +329,13 @@ func parseCorpusValue(line []byte) ([]byte, error) { } return nil, fmt.Errorf("expected []byte") } + +type encodingReader struct{} + +func (e encodingReader) UnmarshalBinary(data []byte) error { + return nil +} + +func (e encodingReader) UnmarshalText(data []byte) error { + return nil +} diff --git a/msgp/iter.go b/msgp/iter.go index 19657224..a7c3b030 100644 --- a/msgp/iter.go +++ b/msgp/iter.go @@ -14,6 +14,7 @@ import ( // The type parameter V specifies the type of the elements in the array. // The returned iterator implements the iter.Seq[V] interface, // allowing for sequential access to the array elements. +// The iterator will always stop after one error has been encountered. func ReadArray[T any](m *Reader, readFn func() (T, error)) iter.Seq2[T, error] { return func(yield func(T, error) bool) { // Check if nil @@ -31,7 +32,7 @@ func ReadArray[T any](m *Reader, readFn func() (T, error)) iter.Seq2[T, error] { for range length { var v T v, err = readFn() - if !yield(v, err) { + if !yield(v, err) || err != nil { return } } diff --git a/msgp/read.go b/msgp/read.go index c4160f6c..264933ad 100644 --- a/msgp/read.go +++ b/msgp/read.go @@ -4,6 +4,7 @@ import ( "encoding" "encoding/binary" "encoding/json" + "fmt" "io" "math" "strconv" @@ -1571,6 +1572,11 @@ func (m *Reader) ReadIntf() (i any, err error) { // ReadBinaryUnmarshal reads a binary-encoded object from the reader and unmarshals it into dst. func (m *Reader) ReadBinaryUnmarshal(dst encoding.BinaryUnmarshaler) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("msgp: panic during UnmarshalBinary: %v", r) + } + }() tmp := bytesPool.Get().([]byte) defer bytesPool.Put(tmp) //nolint:staticcheck tmp, err = m.ReadBytes(tmp[:0]) @@ -1582,6 +1588,11 @@ func (m *Reader) ReadBinaryUnmarshal(dst encoding.BinaryUnmarshaler) (err error) // ReadTextUnmarshal reads a text-encoded bin array from the reader and unmarshals it into dst. func (m *Reader) ReadTextUnmarshal(dst encoding.TextUnmarshaler) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("msgp: panic during UnmarshalText: %v", r) + } + }() tmp := bytesPool.Get().([]byte) defer bytesPool.Put(tmp) //nolint:staticcheck tmp, err = m.ReadBytes(tmp[:0]) @@ -1593,6 +1604,11 @@ func (m *Reader) ReadTextUnmarshal(dst encoding.TextUnmarshaler) (err error) { // ReadTextUnmarshalString reads a text-encoded string from the reader and unmarshals it into dst. func (m *Reader) ReadTextUnmarshalString(dst encoding.TextUnmarshaler) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("msgp: panic during UnmarshalText: %v", r) + } + }() tmp := bytesPool.Get().([]byte) defer bytesPool.Put(tmp) //nolint:staticcheck tmp, err = m.ReadStringAsBytes(tmp[:0]) diff --git a/msgp/write.go b/msgp/write.go index f071635f..d4e88089 100644 --- a/msgp/write.go +++ b/msgp/write.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/json" "errors" + "fmt" "io" "math" "reflect" @@ -447,6 +448,9 @@ func (mw *Writer) WriteUint(u uint) error { return mw.WriteUint64(uint64(u)) } // WriteBytes writes binary as 'bin' to the writer func (mw *Writer) WriteBytes(b []byte) error { + if uint64(len(b)) > math.MaxInt32 { + return ErrLimitExceeded + } sz := uint32(len(b)) var err error switch { @@ -489,6 +493,10 @@ func (mw *Writer) WriteBool(b bool) error { // WriteString writes a messagepack string to the writer. // (This is NOT an implementation of io.StringWriter) func (mw *Writer) WriteString(s string) error { + if uint64(len(s)) > math.MaxInt32 { + return ErrLimitExceeded + } + sz := uint32(len(s)) var err error switch { @@ -527,6 +535,9 @@ func (mw *Writer) WriteStringHeader(sz uint32) error { // WriteStringFromBytes writes a 'str' object // from a []byte. func (mw *Writer) WriteStringFromBytes(str []byte) error { + if uint64(len(str)) > math.MaxInt32 { + return ErrLimitExceeded + } sz := uint32(len(str)) var err error switch { @@ -891,10 +902,15 @@ var bytesPool = sync.Pool{New: func() any { return make([]byte, 0, 1024) }} // WriteBinaryAppender will write the bytes from the given // encoding.BinaryAppender as a bin array. -func (mw *Writer) WriteBinaryAppender(b encoding.BinaryAppender) error { +func (mw *Writer) WriteBinaryAppender(b encoding.BinaryAppender) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("msgp: panic during AppendBinary: %v", r) + } + }() dst := bytesPool.Get().([]byte) defer bytesPool.Put(dst) //nolint:staticcheck - dst, err := b.AppendBinary(dst[:0]) + dst, err = b.AppendBinary(dst[:0]) if err != nil { return err } @@ -903,10 +919,15 @@ func (mw *Writer) WriteBinaryAppender(b encoding.BinaryAppender) error { // WriteTextAppender will write the bytes from the given // encoding.TextAppender as a bin array. -func (mw *Writer) WriteTextAppender(b encoding.TextAppender) error { +func (mw *Writer) WriteTextAppender(b encoding.TextAppender) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("msgp: panic during AppendText: %v", r) + } + }() dst := bytesPool.Get().([]byte) defer bytesPool.Put(dst) //nolint:staticcheck - dst, err := b.AppendText(dst[:0]) + dst, err = b.AppendText(dst[:0]) if err != nil { return err } @@ -915,10 +936,15 @@ func (mw *Writer) WriteTextAppender(b encoding.TextAppender) error { // WriteTextAppenderString will write the bytes from the given // encoding.TextAppender as a string. -func (mw *Writer) WriteTextAppenderString(b encoding.TextAppender) error { +func (mw *Writer) WriteTextAppenderString(b encoding.TextAppender) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("msgp: panic during AppendText: %v", r) + } + }() dst := bytesPool.Get().([]byte) defer bytesPool.Put(dst) //nolint:staticcheck - dst, err := b.AppendText(dst[:0]) + dst, err = b.AppendText(dst[:0]) if err != nil { return err }