Repository: Eyevinn/mp4ff
Pull request #404 (2025-01-19 23:11:02)

fix: limit memory usage in reading invalid subs box
Found this when fuzzing:

```
ROUTINE ======================== github.com/Eyevinn/mp4ff/mp4.DecodeSubsSR in /Users/eric/src/mp4ff/mp4/subs.go
  255.17GB   255.17GB (flat, cum)   100% of Total
         .          .     69:func DecodeSubsSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, error) {
         .          .     70:	versionAndFlags := sr.ReadUint32()
         .          .     71:	version := byte(versionAndFlags >> 24)
         .          .     72:
         .          .     73:	b := SubsBox{
         .          .     74:		Version: version,
         .          .     75:		Flags:   versionAndFlags & flagsMask,
         .          .     76:	}
         .          .     77:	entryCount := sr.ReadUint32()
         .          .     78:	for i := uint32(0); i < entryCount; i++ {
         .          .     79:		e := SubsEntry{}
         .          .     80:		e.SampleDelta = sr.ReadUint32()
         .          .     81:		subsampleCount := sr.ReadUint16()
         .          .     82:		for j := uint16(0); j < subsampleCount; j++ {
         .          .     83:			ss := SubsSample{}
         .          .     84:			if version == 1 {
         .          .     85:				ss.SubsampleSize = sr.ReadUint32()
         .          .     86:			} else {
         .          .     87:				ss.SubsampleSize = uint32(sr.ReadUint16())
         .          .     88:			}
         .          .     89:			ss.SubsamplePriority = sr.ReadUint8()
         .          .     90:			ss.Discardable = sr.ReadUint8()
         .          .     91:			ss.CodecSpecificParameters = sr.ReadUint32()
    1.21MB     1.21MB     92:			e.SubSamples = append(e.SubSamples, ss)
         .          .     93:		}
  255.17GB   255.17GB     94:		b.Entries = append(b.Entries, e)
         .          .     95:	}
         .          .     96:	return &b, sr.AccError()
         .          .     97:}
         .          .     98:
         .          .     99:// Type - return box type
```


Patch:
diff --git a/mp4/mime.go b/mp4/mime.go
index 830eb15..c01499c 100644
--- a/mp4/mime.go
+++ b/mp4/mime.go
@@ -1,6 +1,7 @@
 package mp4
 
 import (
+	"fmt"
 	"io"
 
 	"github.com/Eyevinn/mp4ff/bits"
@@ -31,6 +32,9 @@ func DecodeMimeSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, err
 		Version: byte(versionAndFlags >> 24),
 		Flags:   versionAndFlags & flagsMask,
 	}
+	if hdr.payloadLen() < 5 {
+		return nil, fmt.Errorf("mime: box payload size %d less than 5", hdr.payloadLen())
+	}
 	rest := sr.ReadBytes(hdr.payloadLen() - 4)
 	if rest[len(rest)-1] == 0 { // zero-termination
 		b.ContentType = string(rest[:len(rest)-1])
diff --git a/mp4/ssix.go b/mp4/ssix.go
index dc2ad89..8792182 100644
--- a/mp4/ssix.go
+++ b/mp4/ssix.go
@@ -70,6 +70,9 @@ func DecodeSsixSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, err
 		Version: version,
 		Flags:   versionAndFlags & flagsMask,
 	}
+	if hdr.Size < 16 {
+		return nil, fmt.Errorf("ssix: box is too small")
+	}
 	subSegmentCount := sr.ReadUint32()
 	sizeLeft := hdr.Size - 16
 	if subSegmentCount > uint32(sizeLeft/8) {
diff --git a/mp4/subs.go b/mp4/subs.go
index f67cc0c..bd9ac27 100644
--- a/mp4/subs.go
+++ b/mp4/subs.go
@@ -91,6 +91,9 @@ func DecodeSubsSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, err
 			ss.CodecSpecificParameters = sr.ReadUint32()
 			e.SubSamples = append(e.SubSamples, ss)
 		}
+		if sr.AccError() != nil {
+			return nil, sr.AccError()
+		}
 		b.Entries = append(b.Entries, e)
 	}
 	return &b, sr.AccError()


Test patch:
diff --git a/mp4/fuzz_test.go b/mp4/fuzz_test.go
index ed4326e..bd23aab 100644
--- a/mp4/fuzz_test.go
+++ b/mp4/fuzz_test.go
@@ -9,10 +9,12 @@ import (
 	"errors"
 	"io"
 	"os"
+	"path/filepath"
 	"runtime"
-	"strings"
 	"testing"
 	"time"
+
+	"github.com/Eyevinn/mp4ff/bits"
 )
 
 func monitorMemory(ctx context.Context, t *testing.T, memoryLimit int) {
@@ -43,8 +45,18 @@ func FuzzDecodeBox(f *testing.F) {
 		f.Fatal(err)
 	}
 
+	validExts := map[string]bool{
+		".mp4":  true,
+		".m4s":  true,
+		".cmfv": true,
+	}
+
 	for _, entry := range entries {
-		if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".mp4") {
+		if entry.IsDir() {
+			continue
+		}
+
+		if validExts[filepath.Ext(entry.Name())] {
 			testData, err := os.ReadFile("testdata/" + entry.Name())
 			if err != nil {
 				f.Fatal(err)
@@ -59,7 +71,6 @@ func FuzzDecodeBox(f *testing.F) {
 		monitorMemory(ctx, t, 500*1024*1024) // 500MB
 
 		r := bytes.NewReader(b)
-
 		var pos uint64 = 0
 		for {
 			box, err := DecodeBox(pos, r)
@@ -73,5 +84,20 @@ func FuzzDecodeBox(f *testing.F) {
 			}
 			pos += box.Size()
 		}
+
+		pos = 0
+		sr := bits.NewFixedSliceReader(b)
+		for {
+			box, err := DecodeBoxSR(pos, sr)
+			if err != nil {
+				if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+					break
+				}
+			}
+			if box == nil {
+				break
+			}
+			pos += box.Size()
+		}
 	})
 }
diff --git a/mp4/testdata/fuzz/FuzzDecodeBox/6041c517bf46e9ee b/mp4/testdata/fuzz/FuzzDecodeBox/6041c517bf46e9ee
new file mode 100644
index 0000000..77a8f68
--- /dev/null
+++ b/mp4/testdata/fuzz/FuzzDecodeBox/6041c517bf46e9ee
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\x00\x00\x00\bssix\x00\x00\x00\x0100,\x00")
diff --git a/mp4/testdata/fuzz/FuzzDecodeBox/77cf6e30648805ea b/mp4/testdata/fuzz/FuzzDecodeBox/77cf6e30648805ea
new file mode 100644
index 0000000..5f053b3
--- /dev/null
+++ b/mp4/testdata/fuzz/FuzzDecodeBox/77cf6e30648805ea
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\x00\x00\x00\fmime0000")
diff --git a/mp4/testdata/fuzz/FuzzDecodeBox/dc68c3d7e3180551 b/mp4/testdata/fuzz/FuzzDecodeBox/dc68c3d7e3180551
new file mode 100644
index 0000000..b8db8ae
--- /dev/null
+++ b/mp4/testdata/fuzz/FuzzDecodeBox/dc68c3d7e3180551
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\x00\x00\x04\x1cdreftrl \x00\x00\x00\x01\x00\x00\x017subsed Caption\x00\x00\x00\x00\xa4minf\x00\x00\x00\fnmhd\x00\x00\x00\x00\x00\x00\x00$dinf\x00\x00\x00\x1cd\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00#\x00\furl \x00\x00\x00\x01\x00\x00\x00lstbl\x00\x00\x00 sts\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x10c608\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x10stts\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x10stsc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14stsz\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10stco\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Pudta\x00\x00\x00Hswre\x00\x00\x00\x00U\xc4mediafilesegmenter: Version 1.10.4 (524.12i-210805) Linux\x00\x00\x00\x00Hmvex\x00\x00\x00 trex\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 trex\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")