Skip to content

Commit

Permalink
fix: make senc parsing more robust to bad input
Browse files Browse the repository at this point in the history
  • Loading branch information
tobbee committed Jan 19, 2025
1 parent 9409e9b commit 3262da0
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 37 deletions.
4 changes: 0 additions & 4 deletions mp4/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ func FuzzDecodeBox(f *testing.F) {
}

f.Fuzz(func(t *testing.T, b []byte) {
if t.Name() == "FuzzDecodeBox/75565444c6c2f1dd" {
t.Skip("There is a bug in SencBox.Size() that needs to be fixed for " + t.Name())
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
monitorMemory(ctx, t, 500*1024*1024) // 500MB
Expand Down
48 changes: 41 additions & 7 deletions mp4/senc.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type SencBox struct {
rawData []byte // intermediate storage when reading
IVs []InitializationVector // 8 or 16 bytes if present
SubSamples [][]SubSamplePattern
readBoxSize uint64 // As read from box header
}

// CreateSencBox - create an empty SencBox
Expand Down Expand Up @@ -86,25 +87,39 @@ func (s *SencBox) AddSample(sample SencSample) error {

// DecodeSenc - box-specific decode
func DecodeSenc(hdr BoxHeader, startPos uint64, r io.Reader) (Box, error) {
if hdr.Size < 16 {
return nil, fmt.Errorf("box size %d less than min size 16", hdr.Size)
}
data, err := readBoxBody(r, hdr)
if err != nil {
return nil, err
}

versionAndFlags := binary.BigEndian.Uint32(data[0:4])
version := byte(versionAndFlags >> 24)
flags := versionAndFlags & flagsMask
if version > 0 {
return nil, fmt.Errorf("version %d not supported", version)
}
sampleCount := binary.BigEndian.Uint32(data[4:8])

if len(data) < 8 {
return nil, fmt.Errorf("senc: box size %d less than 16", hdr.Size)
}

senc := SencBox{
Version: byte(versionAndFlags >> 24),
Version: version,
rawData: data[8:], // After the first 8 bytes of box content
Flags: versionAndFlags & flagsMask,
Flags: flags,
StartPos: startPos,
SampleCount: sampleCount,
readButNotParsed: true,
readBoxSize: hdr.Size,
}

if flags&UseSubSampleEncryption != 0 && (len(senc.rawData) < 2*int(sampleCount)) {
return nil, fmt.Errorf("box size %d too small for %d samples and subSampleEncryption",
hdr.Size, sampleCount)
}

if senc.SampleCount == 0 || len(senc.rawData) == 0 {
Expand All @@ -116,15 +131,31 @@ func DecodeSenc(hdr BoxHeader, startPos uint64, r io.Reader) (Box, error) {

// DecodeSencSR - box-specific decode
func DecodeSencSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, error) {
if hdr.Size < 16 {
return nil, fmt.Errorf("box size %d less than min size 16", hdr.Size)
}

versionAndFlags := sr.ReadUint32()
version := byte(versionAndFlags >> 24)
if version > 0 {
return nil, fmt.Errorf("version %d not supported", version)
}
flags := versionAndFlags & flagsMask
sampleCount := sr.ReadUint32()

if flags&UseSubSampleEncryption != 0 && ((hdr.Size - 16) < 2*uint64(sampleCount)) {
return nil, fmt.Errorf("box size %d too small for %d samples and subSampleEncryption",
hdr.Size, sampleCount)
}

senc := SencBox{
Version: byte(versionAndFlags >> 24),
Version: version,
rawData: sr.ReadBytes(hdr.payloadLen() - 8), // After the first 8 bytes of box content
Flags: versionAndFlags & flagsMask,
Flags: flags,
StartPos: startPos,
SampleCount: sampleCount,
readButNotParsed: true,
readBoxSize: hdr.Size,
}

if senc.SampleCount == 0 || len(senc.rawData) == 0 {
Expand Down Expand Up @@ -254,11 +285,14 @@ func (s *SencBox) setSubSamplesUsedFlag() {

// Size - box-specific type
func (s *SencBox) Size() uint64 {
if s.readButNotParsed {
return boxHeaderSize + 8 + uint64(len(s.rawData)) // read 8 bytes after header
if s.readBoxSize > 0 {
return s.readBoxSize
}
totalSize := uint64(boxHeaderSize + 8)
return s.calcSize()
}

func (s *SencBox) calcSize() uint64 {
totalSize := uint64(boxHeaderSize + 8)
perSampleIVSize := uint64(s.GetPerSampleIVSize())
for i := uint32(0); i < s.SampleCount; i++ {
totalSize += perSampleIVSize
Expand Down
117 changes: 91 additions & 26 deletions mp4/senc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,66 @@ import (
"bytes"
"testing"

"github.com/Eyevinn/mp4ff/bits"
"github.com/go-test/deep"
)

func TestSencDirectValues(t *testing.T) {
iv8 := InitializationVector("12345678")
iv16 := InitializationVector("0123456789abcdef")
sencBoxes := []*SencBox{
cases := []struct {
desc string
senc *SencBox
}{
{
Version: 0,
Flags: 0,
SampleCount: 431, // No perSampleIVs
perSampleIVSize: 0,
desc: "No perSampleIVs",
senc: &SencBox{
Version: 0,
Flags: 0,
SampleCount: 431, // No perSampleIVs
perSampleIVSize: 0,
},
},
{
Version: 0,
Flags: 0,
SampleCount: 1,
perSampleIVSize: 8,
IVs: []InitializationVector{iv8},
SubSamples: [][]SubSamplePattern{{{10, 1000}}},
desc: "perSampleIVSize 8",
senc: &SencBox{
Version: 0,
Flags: 0,
SampleCount: 1,
perSampleIVSize: 8,
IVs: []InitializationVector{iv8},
SubSamples: [][]SubSamplePattern{{{10, 1000}}},
},
},
{
Version: 0,
Flags: 0,
SampleCount: 1,
perSampleIVSize: 16,
IVs: []InitializationVector{iv16},
SubSamples: [][]SubSamplePattern{{{10, 1000}, {20, 2000}}},
desc: "perSampleIVSize 16",
senc: &SencBox{
Version: 0,
Flags: 0,
SampleCount: 1,
perSampleIVSize: 16,
IVs: []InitializationVector{iv16},
SubSamples: [][]SubSamplePattern{{{10, 1000}, {20, 2000}}},
},
},
{
Version: 0,
Flags: 0,
SampleCount: 2,
perSampleIVSize: 16,
IVs: []InitializationVector{iv16, iv16},
SubSamples: [][]SubSamplePattern{{{10, 1000}}, {{20, 2000}}},
desc: "perSampleIVSize 16, 2 subsamples",
senc: &SencBox{
Version: 0,
Flags: 0,
SampleCount: 2,
perSampleIVSize: 16,
IVs: []InitializationVector{iv16, iv16},
SubSamples: [][]SubSamplePattern{{{10, 1000}}, {{20, 2000}}},
},
},
}

for _, senc := range sencBoxes {
sencDiffAfterEncodeAndDecode(t, senc, 0)
sencDiffAfterEncodeAndDecode(t, senc, senc.perSampleIVSize)
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
sencDiffAfterEncodeAndDecode(t, c.senc, 0)
sencDiffAfterEncodeAndDecode(t, c.senc, c.senc.perSampleIVSize)
})
}
}

Expand Down Expand Up @@ -147,3 +165,50 @@ func TestImplicitIVSize(t *testing.T) {
}
}
}

func TestBadSencData(t *testing.T) {
// raw senc box with version > 2 */
cases := []struct {
desc string
raw []byte
err string
}{
{
desc: "too short",
raw: []byte{0x00, 0x00, 0x00, 0x0f, 's', 'e', 'n', 'c', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
err: "decode senc pos 0: box size 15 less than min size 16",
},
{
desc: "v1 not supported",
raw: []byte{0x00, 0x00, 0x00, 0x10, 's', 'e', 'n', 'c', 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
err: "decode senc pos 0: version 1 not supported",
},
{
desc: "too short for subsample encryption",
raw: []byte{0x00, 0x00, 0x00, 0x10, 's', 'e', 'n', 'c', 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff},
err: "decode senc pos 0: box size 16 too small for 255 samples and subSampleEncryption",
},
}

for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
buf := bytes.NewBuffer(c.raw)
_, err := DecodeBox(0, buf)
if err == nil {
t.Errorf("expected error %q, but got nil", c.err)
}
if err.Error() != c.err {
t.Errorf("expected error %q, got %q", c.err, err.Error())
}

sr := bits.NewFixedSliceReader(c.raw)
_, err = DecodeBoxSR(0, sr)
if err == nil {
t.Errorf("expected error %q, but got nil", c.err)
}
if err.Error() != c.err {
t.Errorf("expected error %q, got %q", c.err, err.Error())
}
})
}
}
2 changes: 2 additions & 0 deletions mp4/testdata/fuzz/FuzzDecodeBox/0881196294cc083f
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x00\x10senc\x00000\xfd\xfd\xfd\xbc")

0 comments on commit 3262da0

Please sign in to comment.