From e1d3341b46648ddec59519cc33f50cbb7e8777e7 Mon Sep 17 00:00:00 2001 From: Wolfy-J Date: Sat, 15 Aug 2026 22:13:42 -0400 Subject: [PATCH] fix manifest decoder bounds --- types/io/manifest.go | 9 +++++++++ types/io/manifest_test.go | 37 +++++++++++++++++++++++++++++++++++++ types/io/reader.go | 10 ++++++---- types/io/serialize.go | 2 -- types/io/serialize_test.go | 11 +++++++++++ 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/types/io/manifest.go b/types/io/manifest.go index b29743cec..a832ed55f 100644 --- a/types/io/manifest.go +++ b/types/io/manifest.go @@ -772,6 +772,9 @@ func DecodeManifest(data []byte) (*Manifest, error) { // Types count := r.readUint32() + if !r.checkSliceLen(count) { + return nil, r.err + } for i := uint32(0); i < count; i++ { name := r.readString() m.Types[name] = r.readType() @@ -779,6 +782,9 @@ func DecodeManifest(data []byte) (*Manifest, error) { // Summaries count = r.readUint32() + if !r.checkSliceLen(count) { + return nil, r.err + } for i := uint32(0); i < count; i++ { name := r.readString() m.Summaries[name] = r.readSummary() @@ -786,6 +792,9 @@ func DecodeManifest(data []byte) (*Manifest, error) { // Globals count = r.readUint32() + if !r.checkSliceLen(count) { + return nil, r.err + } for i := uint32(0); i < count; i++ { name := r.readString() m.Globals[name] = r.readType() diff --git a/types/io/manifest_test.go b/types/io/manifest_test.go index 4370d4b2b..20bffa2d1 100644 --- a/types/io/manifest_test.go +++ b/types/io/manifest_test.go @@ -1,7 +1,10 @@ package io import ( + "bytes" "errors" + "fmt" + "strings" "testing" "github.com/wippyai/go-lua/types/constraint" @@ -237,6 +240,25 @@ func TestManifest_Encode_Decode(t *testing.T) { } } +func TestManifest_Encode_Decode_LargeValidManifest(t *testing.T) { + m := NewManifest(strings.Repeat("module/", 32)) + for i := 0; i < 2048; i++ { + m.AddGlobal(fmt.Sprintf("global_%04d", i), typ.String) + } + + data, err := m.Encode() + if err != nil { + t.Fatalf("Encode failed: %v", err) + } + decoded, err := DecodeManifest(data) + if err != nil { + t.Fatalf("DecodeManifest failed: %v", err) + } + if decoded.Path != m.Path || len(decoded.Globals) != len(m.Globals) { + t.Fatalf("decoded manifest differs: path=%q globals=%d", decoded.Path, len(decoded.Globals)) + } +} + func TestManifest_EnrichedExport_DoesNotApplySummaryToNestedSameName(t *testing.T) { m := NewManifest("test") @@ -331,6 +353,21 @@ func TestDecodeManifest_InvalidMagic(t *testing.T) { } } +func TestDecodeManifest_RejectsCollectionLengthBeyondInput(t *testing.T) { + var buf bytes.Buffer + w := &manifestWriter{typeWriter: &typeWriter{w: &buf}} + w.writeUint32(manifestMagic) + w.writeByte(manifestVersion) + w.writeUint64(0) + w.writeString("test") + w.writeBool(false) + w.writeUint32(1024) + + if _, err := DecodeManifest(buf.Bytes()); !errors.Is(err, ErrCorruptedData) { + t.Fatalf("DecodeManifest error = %v", err) + } +} + func TestApplyFunctionSummary_Nil(t *testing.T) { fn := typ.Func().Build() result := ApplyFunctionSummary(fn, nil) diff --git a/types/io/reader.go b/types/io/reader.go index 9ce5b13e4..a1138f332 100644 --- a/types/io/reader.go +++ b/types/io/reader.go @@ -13,8 +13,10 @@ import ( "github.com/wippyai/go-lua/types/typ" ) -const maxTypeDepth = 32 -const maxTypeNodes = 1024 +const maxTypeDepth = 256 +const maxTypeNodes = 1 << 20 +const maxCollectionLen = 1 << 20 +const maxStringLen = 16 << 20 type typeReader struct { r *bytes.Reader @@ -71,7 +73,7 @@ func (r *typeReader) readString() string { return "" } - if length > maxSliceLen { + if length > maxStringLen || uint64(length) > uint64(r.r.Len()) { r.err = ErrCorruptedData return "" } @@ -87,7 +89,7 @@ func (r *typeReader) readBool() bool { } func (r *typeReader) checkSliceLen(n uint32) bool { - if n > maxSliceLen { + if n > maxCollectionLen || uint64(n) > uint64(r.r.Len()) { r.err = ErrCorruptedData return false } diff --git a/types/io/serialize.go b/types/io/serialize.go index c6c8f0efa..fb342c1b5 100644 --- a/types/io/serialize.go +++ b/types/io/serialize.go @@ -42,8 +42,6 @@ var ( ErrCorruptedData = errors.New("corrupted type data") ) -const maxSliceLen = 64 - const ( annotationArgNil byte = iota annotationArgString diff --git a/types/io/serialize_test.go b/types/io/serialize_test.go index 5a125314e..0ba0b6d88 100644 --- a/types/io/serialize_test.go +++ b/types/io/serialize_test.go @@ -1397,6 +1397,17 @@ func TestDecode_ReadString_Empty(t *testing.T) { } } +func TestDecode_ReadString_RejectsLengthBeyondInput(t *testing.T) { + var buf bytes.Buffer + tw := &typeWriter{w: &buf} + tw.writeUint32(1024) + + r := &typeReader{r: bytes.NewReader(buf.Bytes())} + if got := r.readString(); got != "" || !errors.Is(r.err, ErrCorruptedData) { + t.Fatalf("readString() = %q, err=%v", got, r.err) + } +} + func TestDecode_Literal_UnknownBase(t *testing.T) { // kind.Literal is 23 (0x17) var buf bytes.Buffer