h264: fix crash in Annex-B decoding, add fuzz tests

This commit is contained in:
aler9
2023-01-08 11:53:56 +01:00
parent 73c9840bbe
commit 8a1dd54d61
16 changed files with 86 additions and 112 deletions

View File

@@ -8,25 +8,38 @@ import (
func AnnexBUnmarshal(byts []byte) ([][]byte, error) { func AnnexBUnmarshal(byts []byte) ([][]byte, error) {
bl := len(byts) bl := len(byts)
initZeroCount := 0 initZeroCount := 0
start := 0
outer: outer:
for i := 0; i < bl; i++ { for {
switch byts[i] { if start >= bl || start >= 4 {
case 0: return nil, fmt.Errorf("initial delimiter not found")
}
switch initZeroCount {
case 0, 1:
if byts[start] != 0 {
return nil, fmt.Errorf("initial delimiter not found")
}
initZeroCount++ initZeroCount++
case 1: case 2, 3:
break outer switch byts[start] {
case 1:
start++
break outer
default: case 0:
return nil, fmt.Errorf("unexpected byte: %d", byts[i])
default:
return nil, fmt.Errorf("initial delimiter not found")
}
initZeroCount++
} }
}
if initZeroCount != 2 && initZeroCount != 3 { start++
return nil, fmt.Errorf("initial delimiter not found")
} }
start := initZeroCount + 1
zeroCount := 0 zeroCount := 0
n := 0 n := 0
@@ -67,16 +80,15 @@ outer:
case 1: case 1:
if zeroCount == 2 || zeroCount == 3 { if zeroCount == 2 || zeroCount == 3 {
if (delimStart - start) > MaxNALUSize { l := delimStart - start
return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", delimStart-start, MaxNALUSize) if l == 0 {
return nil, fmt.Errorf("invalid NALU")
}
if l > MaxNALUSize {
return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", l, MaxNALUSize)
} }
nalu := byts[start:delimStart] ret[pos] = byts[start:delimStart]
if len(nalu) == 0 {
return nil, fmt.Errorf("empty NALU")
}
ret[pos] = nalu
pos++ pos++
start = i + 1 start = i + 1
} }
@@ -87,15 +99,15 @@ outer:
} }
} }
if (bl - start) > MaxNALUSize { l := bl - start
return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", bl-start, MaxNALUSize) if l == 0 {
return nil, fmt.Errorf("invalid NALU")
}
if l > MaxNALUSize {
return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", l, MaxNALUSize)
} }
nalu := byts[start:bl] ret[pos] = byts[start:bl]
if len(nalu) == 0 {
return nil, fmt.Errorf("empty NALU")
}
ret[pos] = nalu
return ret, nil return ret, nil
} }

View File

@@ -1,7 +1,6 @@
package h264 package h264
import ( import (
"bytes"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -95,50 +94,6 @@ func TestAnnexBMarshal(t *testing.T) {
} }
} }
func TestAnnexBUnmarshalError(t *testing.T) {
for _, ca := range []struct {
name string
enc []byte
err string
}{
{
"empty",
[]byte{},
"initial delimiter not found",
},
{
"invalid initial delimiter 1",
[]byte{0xaa, 0xbb},
"unexpected byte: 170",
},
{
"invalid initial delimiter 2",
[]byte{0x00, 0x00, 0x00, 0x00, 0x01},
"initial delimiter not found",
},
{
"empty NALU 1",
[]byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x01},
"empty NALU",
},
{
"empty NALU 2",
[]byte{0x00, 0x00, 0x01, 0xaa, 0x00, 0x00, 0x01},
"empty NALU",
},
{
"too many nalus",
bytes.Repeat([]byte{0x00, 0x00, 0x01, 0x0a}, 21),
"NALU count (21) exceeds maximum allowed (20)",
},
} {
t.Run(ca.name, func(t *testing.T) {
_, err := AnnexBUnmarshal(ca.enc)
require.EqualError(t, err, ca.err)
})
}
}
func BenchmarkAnnexBUnmarshal(b *testing.B) { func BenchmarkAnnexBUnmarshal(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
AnnexBUnmarshal([]byte{ AnnexBUnmarshal([]byte{
@@ -161,3 +116,9 @@ func BenchmarkAnnexBUnmarshal(b *testing.B) {
}) })
} }
} }
func FuzzAnnexBUnmarshal(f *testing.F) {
f.Fuzz(func(t *testing.T, b []byte) {
AnnexBUnmarshal(b)
})
}

View File

@@ -15,15 +15,15 @@ func AVCCUnmarshal(buf []byte) ([][]byte, error) {
return nil, fmt.Errorf("invalid length") return nil, fmt.Errorf("invalid length")
} }
le := int(uint32(buf[pos])<<24 | uint32(buf[pos+1])<<16 | uint32(buf[pos+2])<<8 | uint32(buf[pos+3])) l := int(uint32(buf[pos])<<24 | uint32(buf[pos+1])<<16 | uint32(buf[pos+2])<<8 | uint32(buf[pos+3]))
pos += 4 pos += 4
if (bl - pos) < le { if l == 0 {
return nil, fmt.Errorf("invalid length") return nil, fmt.Errorf("invalid NALU")
} }
if (bl - pos) > MaxNALUSize { if l > MaxNALUSize {
return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", bl-pos, MaxNALUSize) return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", l, MaxNALUSize)
} }
if (len(ret) + 1) > MaxNALUsPerGroup { if (len(ret) + 1) > MaxNALUsPerGroup {
@@ -31,8 +31,12 @@ func AVCCUnmarshal(buf []byte) ([][]byte, error) {
len(ret)+1, MaxNALUsPerGroup) len(ret)+1, MaxNALUsPerGroup)
} }
ret = append(ret, buf[pos:pos+le]) if (bl - pos) < l {
pos += le return nil, fmt.Errorf("invalid length")
}
ret = append(ret, buf[pos:pos+l])
pos += l
if (bl - pos) == 0 { if (bl - pos) == 0 {
break break

View File

@@ -1,7 +1,6 @@
package h264 package h264
import ( import (
"bytes"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -60,36 +59,8 @@ func TestAVCCMarshal(t *testing.T) {
} }
} }
func TestAVCCUnmarshalError(t *testing.T) { func FuzzAVCCUnmarshal(f *testing.F) {
for _, ca := range []struct { f.Fuzz(func(t *testing.T, b []byte) {
name string AVCCUnmarshal(b)
enc []byte })
err string
}{
{
"empty",
[]byte{},
"invalid length",
},
{
"invalid length",
[]byte{0x01},
"invalid length",
},
{
"invalid length",
[]byte{0x00, 0x00, 0x00, 0x03},
"invalid length",
},
{
"too many nalus",
bytes.Repeat([]byte{0x00, 0x00, 0x00, 0x01, 0x0a}, 21),
"NALU count (21) exceeds maximum allowed (20)",
},
} {
t.Run(ca.name, func(t *testing.T) {
_, err := AVCCUnmarshal(ca.enc)
require.EqualError(t, err, ca.err)
})
}
} }

View File

@@ -1,6 +1,6 @@
package h264 package h264
// EmulationPreventionRemove removes emlation prevention bytes from a NALU. // EmulationPreventionRemove removes emulation prevention bytes from a NALU.
func EmulationPreventionRemove(nalu []byte) []byte { func EmulationPreventionRemove(nalu []byte) []byte {
// 0x00 0x00 0x03 0x00 -> 0x00 0x00 0x00 // 0x00 0x00 0x03 0x00 -> 0x00 0x00 0x00
// 0x00 0x00 0x03 0x01 -> 0x00 0x00 0x01 // 0x00 0x00 0x03 0x01 -> 0x00 0x00 0x01

View File

@@ -54,3 +54,9 @@ func TestEmulationPreventionRemove(t *testing.T) {
}) })
} }
} }
func FuzzEmulationPreventionRemove(f *testing.F) {
f.Fuzz(func(t *testing.T, b []byte) {
EmulationPreventionRemove(b)
})
}

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x00\x00")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x000\x00\x00")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x00\x00")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x01")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x01\x00\x00\x01")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00")