moq: fix several panics and OOM errors (#5964)

Check for limits before allocating memory by using sizes passed from
the remote peer. Also add fuzzing to all MoQ primitives.
This commit is contained in:
Alessandro Ros
2026-07-19 12:13:45 +02:00
committed by GitHub
parent 83cc54418c
commit 363ac206f7
46 changed files with 230 additions and 22 deletions
@@ -172,3 +172,18 @@ func TestMarshal(t *testing.T) {
})
}
}
func FuzzUnmarshal(f *testing.F) {
for _, ca := range cases {
f.Add(ca.enc)
}
f.Fuzz(func(_ *testing.T, buf []byte) {
m, err := Read(bytes.NewReader(buf))
if err != nil {
return
}
m.Marshal()
})
}
@@ -10,6 +10,10 @@ import (
const typePublish varint.Varint = 0x1d
const (
maxNamespaceFieldCount = 16
)
// Publish is the PUBLISH control message.
// spec: draft-18, section 10.10
type Publish struct {
@@ -29,9 +33,10 @@ func (m *Publish) unmarshal(buf []byte) error {
if err != nil {
return err
}
m.RequestID = uint64(requestID)
buf = buf[n:]
m.RequestID = uint64(requestID)
var nsCount varint.Varint
n, err = nsCount.Unmarshal(buf)
if err != nil {
@@ -39,6 +44,10 @@ func (m *Publish) unmarshal(buf []byte) error {
}
buf = buf[n:]
if nsCount > maxNamespaceFieldCount {
return fmt.Errorf("too many namespace fields: %d", nsCount)
}
m.Namespace = make([]string, nsCount)
for i := range m.Namespace {
var l varint.Varint
@@ -47,9 +56,11 @@ func (m *Publish) unmarshal(buf []byte) error {
return err
}
buf = buf[n:]
if len(buf) < int(l) {
if uint64(len(buf)) < uint64(l) {
return fmt.Errorf("not enough bytes for namespace part")
}
m.Namespace[i] = string(buf[:l])
buf = buf[int(l):]
}
@@ -60,9 +71,11 @@ func (m *Publish) unmarshal(buf []byte) error {
return err
}
buf = buf[n:]
if len(buf) < int(tnLen) {
return fmt.Errorf("not enough bytes for track name")
if uint64(len(buf)) < uint64(tnLen) {
return fmt.Errorf("invalid track name length: %d", tnLen)
}
m.TrackName = string(buf[:tnLen])
buf = buf[int(tnLen):]
@@ -71,9 +84,10 @@ func (m *Publish) unmarshal(buf []byte) error {
if err != nil {
return err
}
m.TrackAlias = uint64(trackAlias)
buf = buf[n:]
m.TrackAlias = uint64(trackAlias)
var paramCount varint.Varint
n, err = paramCount.Unmarshal(buf)
if err != nil {
@@ -34,9 +34,10 @@ func (m *RequestError) unmarshal(buf []byte) error {
if err != nil {
return err
}
m.Code = RequestErrorCode(code)
buf = buf[n:]
m.Code = RequestErrorCode(code)
var retry varint.Varint
n, err = retry.Unmarshal(buf)
if err != nil {
@@ -50,9 +51,11 @@ func (m *RequestError) unmarshal(buf []byte) error {
return err
}
buf = buf[n:]
if len(buf) < int(l) {
if uint64(len(buf)) < uint64(l) {
return fmt.Errorf("not enough bytes")
}
m.Reason = string(buf[:l])
return nil
@@ -26,9 +26,10 @@ func (m *Subscribe) unmarshal(buf []byte) error {
if err != nil {
return err
}
m.RequestID = uint64(requestID)
buf = buf[n:]
m.RequestID = uint64(requestID)
var nsCount varint.Varint
n, err = nsCount.Unmarshal(buf)
if err != nil {
@@ -36,6 +37,10 @@ func (m *Subscribe) unmarshal(buf []byte) error {
}
buf = buf[n:]
if nsCount > maxNamespaceFieldCount {
return fmt.Errorf("too many namespace fields: %d", nsCount)
}
m.Namespace = make([]string, nsCount)
for i := range m.Namespace {
var l varint.Varint
@@ -44,9 +49,11 @@ func (m *Subscribe) unmarshal(buf []byte) error {
return err
}
buf = buf[n:]
if len(buf) < int(l) {
if uint64(len(buf)) < uint64(l) {
return fmt.Errorf("not enough bytes for namespace part")
}
m.Namespace[i] = string(buf[:l])
buf = buf[int(l):]
}
@@ -57,9 +64,11 @@ func (m *Subscribe) unmarshal(buf []byte) error {
return err
}
buf = buf[n:]
if len(buf) < int(tnLen) {
return fmt.Errorf("not enough bytes for track name")
if uint64(len(buf)) < uint64(tnLen) {
return fmt.Errorf("invalid track name length: %d", tnLen)
}
m.TrackName = string(buf[:tnLen])
buf = buf[int(tnLen):]
@@ -24,9 +24,10 @@ func (m *SubscribeOk) unmarshal(buf []byte) error {
if err != nil {
return err
}
m.TrackAlias = uint64(v)
buf = buf[n:]
m.TrackAlias = uint64(v)
var numParams varint.Varint
n, err = numParams.Unmarshal(buf)
if err != nil {
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x04\x00\x060\x000010")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x1d\x00\f0\x01\xff\xff00000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\a\x00\x010")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x04\x00\x06000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\a\x000\x0000000000000000000000000000000000000000000000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x1d\x00\x160\x01\x03000\x030000\x001\xff\xff0000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x03\x00\v0\x01\xff\xff0000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0\x00\x00")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x03\x00\x150\x01\x03000\x030000\x03\xff\xff0000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x1d\x00\x160\x00\x01000\x03\xff\xff\xff\xff\xff\xff\xff\xff\xff000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x05\x00\x15\x9700\xff\xff0000000000000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x03\x00\x15000000000000000000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x1d\x00\f0\x01\x00\xff\xff0000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x1d\x00\x03000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x1d\x00\x160\x01\x03000\x0300000\x03\b\x03\xf2000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x03\x00\x15\x970\x00\xff\xff0000000000000000")
@@ -33,35 +33,41 @@ func (*AuthorizationToken) paramType() uint64 {
func (t *AuthorizationToken) unmarshal(buf []byte) (int, error) {
var le varint.Varint
llen, err := le.Unmarshal(buf)
n1, err := le.Unmarshal(buf)
if err != nil {
return 0, err
}
if len(buf)-llen < int(le) {
buf = buf[n1:]
if uint64(len(buf)) < uint64(le) {
return 0, fmt.Errorf("not enough bytes for parameter value")
}
buf = buf[llen : llen+int(le)]
buf = buf[:le]
var aliasType varint.Varint
n, err := aliasType.Unmarshal(buf)
if err != nil {
return 0, err
}
buf = buf[n:]
t.AliasType = AuthorizationTokenAliasType(aliasType)
if t.AliasType != AuthorizationTokenAliasTypeUseValue {
return 0, fmt.Errorf("unsupported token alias type: %d", aliasType)
}
buf = buf[n:]
var tokenType varint.Varint
n, err = tokenType.Unmarshal(buf)
if err != nil {
return 0, err
}
t.TokenType = uint64(tokenType)
t.TokenValue = buf[n:]
return llen + int(le), nil
return n1 + int(le), nil
}
func (t AuthorizationToken) marshalSize() int {
@@ -31,8 +31,8 @@ func (p *Parameters) Unmarshal(count int, buf []byte) (int, error) {
if err != nil {
return 0, err
}
buf = buf[n:]
total += n
currentType += uint64(typeDelta)
@@ -50,9 +50,9 @@ func (p *Parameters) Unmarshal(count int, buf []byte) (int, error) {
if err != nil {
return 0, fmt.Errorf("failed to unmarshal authorization token: %w", err)
}
buf = buf[n:]
*p = append(*p, param)
buf = buf[n:]
total += n
}
@@ -58,3 +58,20 @@ func TestMarshal(t *testing.T) {
})
}
}
func FuzzUnmarshal(f *testing.F) {
for _, ca := range cases {
f.Add(ca.count, ca.enc)
}
f.Fuzz(func(_ *testing.T, count int, buf []byte) {
var params Parameters
_, err := params.Unmarshal(count, buf)
if err != nil {
return
}
buf = make([]byte, params.MarshalSize())
params.MarshalTo(buf)
})
}
@@ -0,0 +1,3 @@
go test fuzz v1
int(23)
[]byte("\x03\x03000")
@@ -0,0 +1,3 @@
go test fuzz v1
int(68)
[]byte("\x030")
@@ -0,0 +1,3 @@
go test fuzz v1
int(-17)
[]byte("00")
+4 -2
View File
@@ -31,8 +31,8 @@ func (p *Properties) Unmarshal(buf []byte) error {
if err != nil {
return err
}
buf = buf[n:]
currentType += uint64(delta)
switch currentType {
@@ -54,9 +54,11 @@ func (p *Properties) Unmarshal(buf []byte) error {
if err != nil {
return err
}
if len(buf)-n2 < int(length) {
if uint64(len(buf))-uint64(n2) < uint64(length) {
return fmt.Errorf("not enough bytes for unknown property")
}
buf = buf[n2+int(length):]
} else {
var skip varint.Varint
@@ -48,3 +48,20 @@ func TestMarshal(t *testing.T) {
})
}
}
func FuzzUnmarshal(f *testing.F) {
for _, ca := range cases {
f.Add(ca.enc)
}
f.Fuzz(func(_ *testing.T, buf []byte) {
var props Properties
err := props.Unmarshal(buf)
if err != nil {
return
}
buf = make([]byte, props.MarshalSize())
props.MarshalTo(buf)
})
}
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("10")
+8 -1
View File
@@ -8,7 +8,10 @@ import (
"github.com/bluenviron/mediamtx/internal/protocols/moq/varint"
)
const maxPayloadSize = 10 * 1024 * 1024
const (
maxPropsLen = 128 * 1024
maxPayloadSize = 10 * 1024 * 1024
)
// Object is an object of a subgroup stream.
// spec: draft-18, section 11.4.2
@@ -34,6 +37,10 @@ func (o *Object) read(r io.Reader, header *Header) error {
}
if propsLen > 0 {
if propsLen > maxPropsLen {
return fmt.Errorf("properties too large: %d", propsLen)
}
props := make([]byte, propsLen)
_, err = io.ReadFull(r, props)
if err != nil {
@@ -90,3 +90,19 @@ func TestMarshal(t *testing.T) {
})
}
}
func FuzzUnmarshal(f *testing.F) {
for _, ca := range cases {
f.Add(ca.enc)
}
f.Fuzz(func(_ *testing.T, buf []byte) {
var s SubGroup
err := s.Read(bytes.NewReader(buf))
if err != nil {
return
}
s.Marshal()
})
}
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000\xff00000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000\x0100\x0500000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("1000\xd000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000\x000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\xff0")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\xff")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\xf8\x000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\xfe\x00\x00\x00\x00\x0200")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\xff\x000000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\xc0")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("")
@@ -89,3 +89,35 @@ func TestMarshal(t *testing.T) {
})
}
}
func FuzzUnmarshal(f *testing.F) {
for _, ca := range cases {
f.Add(ca.enc)
}
f.Fuzz(func(_ *testing.T, buf []byte) {
var v Varint
_, err := v.Unmarshal(buf)
if err != nil {
return
}
v.Marshal()
})
}
func FuzzRead(f *testing.F) {
for _, ca := range cases {
f.Add(ca.enc)
}
f.Fuzz(func(_ *testing.T, buf []byte) {
var v Varint
err := v.Read(bytes.NewReader(buf))
if err != nil {
return
}
v.Marshal()
})
}