Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions common/domain/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package domain_test

import (
"bytes"
"testing"

"github.com/sagernet/sing/common/domain"
"github.com/sagernet/sing/common/varbin"
)

// FuzzReadMatcher / FuzzReadAdGuardMatcher fuzz the binary domain-matcher readers, which parse
// untrusted serialized data (e.g. reached from sing-box .srs rule-sets). They guard against
// panics and unbounded allocations when reading the succinct-set length fields.

func FuzzReadMatcher(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
_, _ = domain.ReadMatcher(varbin.StubReader(bytes.NewReader(data)))
})
}

func FuzzReadAdGuardMatcher(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
_, _ = domain.ReadAdGuardMatcher(varbin.StubReader(bytes.NewReader(data)))
})
}
25 changes: 18 additions & 7 deletions common/domain/set.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package domain

import (
"bytes"
"encoding/binary"
"io"
"math/bits"
Expand Down Expand Up @@ -122,10 +123,15 @@ func readUint64Slice(reader varbin.Reader) ([]uint64, error) {
if length == 0 {
return nil, nil
}
result := make([]uint64, length)
err = binary.Read(reader, binary.BigEndian, result)
if err != nil {
return nil, err
// length is untrusted; grow via append so a crafted huge length hits EOF while reading
// the elements instead of OOMing the process on the allocation.
result := make([]uint64, 0, min(length, 64))
for i := uint64(0); i < length; i++ {
var value uint64
if err = binary.Read(reader, binary.BigEndian, &value); err != nil {
return nil, err
}
result = append(result, value)
}
return result, nil
}
Expand All @@ -149,12 +155,17 @@ func readByteSlice(reader varbin.Reader) ([]byte, error) {
if length == 0 {
return nil, nil
}
result := make([]byte, length)
_, err = io.ReadFull(reader, result)
// length is untrusted; read through io.CopyN so the buffer grows only as bytes actually
// arrive, instead of pre-allocating make([]byte, length) and OOMing on a crafted length.
var buffer bytes.Buffer
_, err = io.CopyN(&buffer, reader, int64(length))
if err != nil {
return nil, err
}
return result, nil
if uint64(buffer.Len()) != length {
return nil, io.ErrUnexpectedEOF
}
return buffer.Bytes(), nil
}

func writeByteSlice(writer varbin.Writer, value []byte) error {
Expand Down