diff --git a/codec/h265parser/malformed_confrecord_test.go b/codec/h265parser/malformed_confrecord_test.go new file mode 100644 index 00000000..801ac900 --- /dev/null +++ b/codec/h265parser/malformed_confrecord_test.go @@ -0,0 +1,24 @@ +package h265parser + +import ( + "testing" +) + +// A truncated HEVC decoder-config record (30 zero bytes: vpscount=0, spscount=0) +// drives Unmarshal past the end of the slice: after the SPS loop it does +// n++; n++; b[n] with no bounds check (the h264parser twin checks len(b) < n+1 +// before reading ppscount). On the unfixed code this panics with +// index-out-of-range; with the fix it must return ErrDecconfInvalid and not panic. +func TestH265ConfRecordTruncated(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("Unmarshal panicked on truncated input (bug present): %v", r) + } + }() + + b := make([]byte, 30) // >= 30 passes the only length guard; all zero + var rec AVCDecoderConfRecord + if _, err := rec.Unmarshal(b); err == nil { + t.Fatalf("expected an error on truncated input, got nil") + } +} diff --git a/codec/h265parser/parser.go b/codec/h265parser/parser.go index 746525ba..be06b08d 100644 --- a/codec/h265parser/parser.go +++ b/codec/h265parser/parser.go @@ -566,6 +566,10 @@ func (self *AVCDecoderConfRecord) Unmarshal(b []byte) (n int, err error) { n++ n++ + if len(b) < n+1 { + err = ErrDecconfInvalid + return + } spscount := int(b[n]) n++ @@ -588,6 +592,10 @@ func (self *AVCDecoderConfRecord) Unmarshal(b []byte) (n int, err error) { n++ n++ + if len(b) < n+1 { + err = ErrDecconfInvalid + return + } ppscount := int(b[n]) n++ diff --git a/format/fmp4/fmp4io/avc1.go b/format/fmp4/fmp4io/avc1.go index 8b9427aa..79bb6d57 100644 --- a/format/fmp4/fmp4io/avc1.go +++ b/format/fmp4/fmp4io/avc1.go @@ -201,6 +201,10 @@ func (a *AVC1Desc) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/extend.go b/format/fmp4/fmp4io/extend.go index f0d1cb32..4d0f7725 100644 --- a/format/fmp4/fmp4io/extend.go +++ b/format/fmp4/fmp4io/extend.go @@ -44,6 +44,10 @@ func (a *MovieExtend) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/fragment.go b/format/fmp4/fmp4io/fragment.go index 791bae2e..961172a0 100644 --- a/format/fmp4/fmp4io/fragment.go +++ b/format/fmp4/fmp4io/fragment.go @@ -55,6 +55,10 @@ func (a *MovieFrag) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return @@ -499,6 +503,10 @@ func (a *TrackFrag) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/media.go b/format/fmp4/fmp4io/media.go index 71623cd0..323803b0 100644 --- a/format/fmp4/fmp4io/media.go +++ b/format/fmp4/fmp4io/media.go @@ -62,6 +62,10 @@ func (a *Media) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return @@ -306,6 +310,10 @@ func (a *MediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return @@ -578,6 +586,10 @@ func (a *DataInfo) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/movie.go b/format/fmp4/fmp4io/movie.go index 6d90e0fc..512a7524 100644 --- a/format/fmp4/fmp4io/movie.go +++ b/format/fmp4/fmp4io/movie.go @@ -63,6 +63,10 @@ func (a *Movie) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return @@ -327,6 +331,10 @@ func (a *Track) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/mp4a.go b/format/fmp4/fmp4io/mp4a.go index c19a8262..62dfb070 100644 --- a/format/fmp4/fmp4io/mp4a.go +++ b/format/fmp4/fmp4io/mp4a.go @@ -133,6 +133,10 @@ func (a *MP4ADesc) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/opus.go b/format/fmp4/fmp4io/opus.go index 2eaefe7f..11f86c9b 100644 --- a/format/fmp4/fmp4io/opus.go +++ b/format/fmp4/fmp4io/opus.go @@ -97,6 +97,10 @@ func (a *OpusSampleEntry) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/refer.go b/format/fmp4/fmp4io/refer.go index 49b3fbe2..0eec1590 100644 --- a/format/fmp4/fmp4io/refer.go +++ b/format/fmp4/fmp4io/refer.go @@ -73,6 +73,10 @@ func (a *DataRefer) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/sampletable.go b/format/fmp4/fmp4io/sampletable.go index 4e639104..ceb9d41f 100644 --- a/format/fmp4/fmp4io/sampletable.go +++ b/format/fmp4/fmp4io/sampletable.go @@ -83,6 +83,10 @@ func (a *SampleTable) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return @@ -270,6 +274,10 @@ func (a *SampleDesc) Unmarshal(b []byte, offset int) (n int, err error) { for n+8 < len(b) { tag := Tag(pio.U32BE(b[n+4:])) size := int(pio.U32BE(b[n:])) + if size < 8 { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } if len(b) < n+size { err = parseErr("TagSizeInvalid", n+offset, err) return diff --git a/format/fmp4/fmp4io/zerosize_box_test.go b/format/fmp4/fmp4io/zerosize_box_test.go new file mode 100644 index 00000000..11105350 --- /dev/null +++ b/format/fmp4/fmp4io/zerosize_box_test.go @@ -0,0 +1,51 @@ +package fmp4io_test + +import ( + "testing" + "time" + + "github.com/deepch/vdk/format/fmp4/fmp4io" + "github.com/deepch/vdk/format/mp4/mp4io" +) + +// A crafted moov box whose single child declares size==0 with an unknown tag. +// The child-box loop does `n += size`; with size==0, n never advances, so the +// loop appends a Dummy to Unknowns forever and allocates without bound. The +// mp4io twin caps this (len(self.Unknowns) > 100 -> "too many unknowns"); the +// fmp4io copy had no such guard. With the size<8 guard the loop must error out +// instead of spinning. +// +// b[0:8] moov box's own header (Movie.Unmarshal just does n += 8) +// b[8:12] child size = 0x00000000 (malicious zero length) +// b[12:16] child tag = 0x00000000 (unknown -> Dummy branch) +// b[16:24] padding so that n+8 (=16) < len(b) (=24) stays true +func craftZeroSizeMoov() []byte { return make([]byte, 24) } + +func returnsPromptly(t *testing.T, name string, f func() error) { + t.Helper() + done := make(chan error, 1) + go func() { done <- f() }() + select { + case err := <-done: + if err == nil { + t.Fatalf("%s: expected an error on a zero-size child box, got nil", name) + } + t.Logf("%s returned promptly, err=%v", name, err) + case <-time.After(2 * time.Second): + t.Fatalf("%s did NOT return in 2s on a zero-size child box (infinite loop / DoS)", name) + } +} + +func TestFmp4MovieZeroSizeChild(t *testing.T) { + b := craftZeroSizeMoov() + returnsPromptly(t, "mp4io.Movie.Unmarshal", func() error { + var mov mp4io.Movie + _, err := mov.Unmarshal(b, 0) + return err + }) + returnsPromptly(t, "fmp4io.Movie.Unmarshal", func() error { + var mov fmp4io.Movie + _, err := mov.Unmarshal(b, 0) + return err + }) +}