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
10 changes: 8 additions & 2 deletions projects/go-avahi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ This directory contains fuzzers for the [`go-avahi`](https://github.com/OpenPrin

## Fuzzers

- `fuzz_domain.go`: Fuzzes the `DomainNormalize` function to validate the CGo boundary and `unsafe.Pointer` usage.
- `fuzz_domain.go`: Targets `DomainNormalize`.
- `fuzz_domain_roundtrip.go`: Verifies round-trip consistency between `DomainFrom` and `DomainSlice`.
- `fuzz_service_name.go`: Tests split/join consistency for service names.
- `fuzz_state_strings.go`: Tests stability of `String()` methods for library states.

## Build with OSS-Fuzz locally:
1. Clone the OSS-Fuzz repo:
Expand All @@ -24,7 +27,10 @@ cd oss-fuzz
python3 infra/helper.py build_fuzzers go-avahi
```

4. Run the fuzzer:
4. Run a fuzzer:
```bash
python3 infra/helper.py run_fuzzer go-avahi fuzz_domain_normalize
python3 infra/helper.py run_fuzzer go-avahi fuzz_domain_roundtrip
python3 infra/helper.py run_fuzzer go-avahi fuzz_service_name
python3 infra/helper.py run_fuzzer go-avahi fuzz_state_strings
```
96 changes: 96 additions & 0 deletions projects/go-avahi/fuzzer/fuzz_domain_roundtrip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Fuzz target for go-avahi's Domain round-trip consistency.
*
* Tests that DomainFrom and DomainSlice are inverse operations,
* DomainEqual is reflexive for valid domains, and DomainToLower /
* DomainToUpper are idempotent.
*/

package fuzzer

import (
"testing"

"github.com/OpenPrinting/go-avahi"
)

func FuzzDomainRoundTrip(f *testing.F) {
// Seed corpus: representative domain name strings
f.Add("example.local")
f.Add("printer._ipp._tcp.local")
f.Add("My\\.Printer._ipp._tcp.local")
f.Add("Kyocera ECOSYS M2040dn._ipp._tcp.local")
f.Add("")

f.Fuzz(func(t *testing.T, data string) {
// 1. Round-trip: DomainSlice → DomainFrom → DomainSlice
labels := avahi.DomainSlice(data)
if labels == nil {
// Invalid domain, skip further checks
return
}

// Reconstruct the domain from parsed labels
reconstructed := avahi.DomainFrom(labels)

// Re-parse the reconstructed domain
labels2 := avahi.DomainSlice(reconstructed)
if labels2 == nil {
t.Errorf("DomainSlice(DomainFrom(%q)) returned nil; "+
"labels=%q reconstructed=%q",
data, labels, reconstructed)
return
}

// Labels must be identical after round-trip
if len(labels) != len(labels2) {
t.Errorf("Round-trip label count mismatch: %d vs %d; "+
"input=%q labels=%q reconstructed=%q labels2=%q",
len(labels), len(labels2),
data, labels, reconstructed, labels2)
return
}

for i := range labels {
if labels[i] != labels2[i] {
t.Errorf("Round-trip label mismatch at %d: %q vs %q; "+
"input=%q",
i, labels[i], labels2[i], data)
}
}

// 2. DomainNormalize must equal DomainFrom(DomainSlice(data))
normalized := avahi.DomainNormalize(data)
if normalized != reconstructed {
t.Errorf("DomainNormalize(%q) = %q != DomainFrom(DomainSlice(%q)) = %q",
data, normalized, data, reconstructed)
}

// 3. DomainEqual: valid domain must equal itself
if !avahi.DomainEqual(data, data) {
t.Errorf("DomainEqual(%q, %q) = false; expected true",
data, data)
}

// 4. DomainToLower / DomainToUpper idempotency
lower := avahi.DomainToLower(data)
lowerLower := avahi.DomainToLower(lower)
if lower != lowerLower {
t.Errorf("DomainToLower not idempotent: %q → %q → %q",
data, lower, lowerLower)
}

upper := avahi.DomainToUpper(data)
upperUpper := avahi.DomainToUpper(upper)
if upper != upperUpper {
t.Errorf("DomainToUpper not idempotent: %q → %q → %q",
data, upper, upperUpper)
}

// 5. Case-insensitive equality
if !avahi.DomainEqual(lower, upper) {
t.Errorf("DomainEqual(lower=%q, upper=%q) = false",
lower, upper)
}
})
}
62 changes: 62 additions & 0 deletions projects/go-avahi/fuzzer/fuzz_service_name.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Fuzz target for go-avahi's DomainServiceNameSplit / Join consistency.
*
* Tests that splitting a service name and re-joining it produces a
* name that splits back to the same components.
*/

package fuzzer

import (
"testing"

"github.com/OpenPrinting/go-avahi"
)

func FuzzServiceName(f *testing.F) {
// Seed corpus: realistic mDNS service names
f.Add("Kyocera ECOSYS M2040dn._ipp._tcp.local")
f.Add("HP LaserJet._ipps._tcp.local")
f.Add("My\\.Printer._ipp._tcp.example.com")
f.Add("_http._tcp.local")
f.Add("")

f.Fuzz(func(t *testing.T, data string) {
// Split the input into components
instance, svctype, domain := avahi.DomainServiceNameSplit(data)

if instance == "" && svctype == "" && domain == "" {
// Invalid service name, nothing more to check
return
}

// Re-join the components
joined := avahi.DomainServiceNameJoin(instance, svctype, domain)
if joined == "" {
t.Errorf("DomainServiceNameJoin(%q, %q, %q) returned empty; "+
"input=%q", instance, svctype, domain, data)
return
}

// Split the re-joined name and verify consistency
instance2, svctype2, domain2 := avahi.DomainServiceNameSplit(joined)

if instance != instance2 {
t.Errorf("Instance mismatch: %q vs %q; "+
"input=%q joined=%q",
instance, instance2, data, joined)
}

if svctype != svctype2 {
t.Errorf("Svctype mismatch: %q vs %q; "+
"input=%q joined=%q",
svctype, svctype2, data, joined)
}

if domain != domain2 {
t.Errorf("Domain mismatch: %q vs %q; "+
"input=%q joined=%q",
domain, domain2, data, joined)
}
})
}
54 changes: 54 additions & 0 deletions projects/go-avahi/fuzzer/fuzz_state_strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Fuzz target for go-avahi's enum .String() methods.
*
* Tests BrowserEvent, ClientState, EntryGroupState, and ResolverEvent
* String() methods with arbitrary integer values to ensure they never
* panic and always return non-empty strings.
*/

package fuzzer

import (
"testing"

"github.com/OpenPrinting/go-avahi"
)

func FuzzStateStrings(f *testing.F) {
// Seed corpus: boundary values
f.Add(int32(0))
f.Add(int32(1))
f.Add(int32(-1))
f.Add(int32(4))
f.Add(int32(999))
f.Add(int32(2147483647)) // MaxInt32
f.Add(int32(-2147483648)) // MinInt32

f.Fuzz(func(t *testing.T, val int32) {
n := int(val)

// BrowserEvent.String()
bs := avahi.BrowserEvent(n).String()
if bs == "" {
t.Errorf("BrowserEvent(%d).String() returned empty", n)
}

// ClientState.String()
cs := avahi.ClientState(n).String()
if cs == "" {
t.Errorf("ClientState(%d).String() returned empty", n)
}

// EntryGroupState.String()
es := avahi.EntryGroupState(n).String()
if es == "" {
t.Errorf("EntryGroupState(%d).String() returned empty", n)
}

// ResolverEvent.String()
rs := avahi.ResolverEvent(n).String()
if rs == "" {
t.Errorf("ResolverEvent(%d).String() returned empty", n)
}
})
}
51 changes: 46 additions & 5 deletions projects/go-avahi/oss_fuzz_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,36 @@
#
################################################################################

# Copy fuzzer source into the target library tree
mkdir -p $SRC/go-avahi/fuzzer
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_domain.go $SRC/go-avahi/fuzzer/

# Package seed corpus
# Package seed corpus — domain normalize
mkdir -p $WORK/domain_seed_corpus
cp $SRC/fuzzing/projects/go-avahi/seeds/domain_seed_corpus/* $WORK/domain_seed_corpus/
cd $WORK
zip -r $OUT/fuzz_domain_normalize_seed_corpus.zip domain_seed_corpus/

# Package seed corpus — domain round-trip
mkdir -p $WORK/roundtrip_seed_corpus
cp $SRC/fuzzing/projects/go-avahi/seeds/roundtrip_seed_corpus/* $WORK/roundtrip_seed_corpus/
zip -r $OUT/fuzz_domain_roundtrip_seed_corpus.zip roundtrip_seed_corpus/

# Package seed corpus — service name
mkdir -p $WORK/service_name_seed_corpus
cp $SRC/fuzzing/projects/go-avahi/seeds/service_name_seed_corpus/* $WORK/service_name_seed_corpus/
zip -r $OUT/fuzz_service_name_seed_corpus.zip service_name_seed_corpus/

# Package seed corpus — state strings
mkdir -p $WORK/state_strings_seed_corpus
cp $SRC/fuzzing/projects/go-avahi/seeds/state_strings_seed_corpus/* $WORK/state_strings_seed_corpus/
zip -r $OUT/fuzz_state_strings_seed_corpus.zip state_strings_seed_corpus/

# Standard build environment: the library is at /src/go-avahi
# We clean the fuzzer directory first to ensure a fresh start
rm -rf $SRC/go-avahi/fuzzer
mkdir -p $SRC/go-avahi/fuzzer
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_domain.go $SRC/go-avahi/fuzzer/
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_domain_roundtrip.go $SRC/go-avahi/fuzzer/
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_service_name.go $SRC/go-avahi/fuzzer/
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_state_strings.go $SRC/go-avahi/fuzzer/

# CGo environment: use pkg-config for architecture-agnostic library resolution
export CGO_ENABLED=1
export CGO_CFLAGS="-D_REENTRANT"
Expand All @@ -34,10 +54,31 @@ export CGO_LDFLAGS="$(pkg-config --libs avahi-client) -lpthread -lresolv"
# clang++ link step can resolve the C symbols from the .a archive.
export CXXFLAGS="${CXXFLAGS:-} $(pkg-config --libs avahi-client) -lpthread -lresolv"

# Copy required shared libraries to $OUT for the runner container
# We use ldconfig to find the exact paths.
for lib in libavahi-client.so.3 libavahi-common.so.3 libdbus-1.so.3 \
libsystemd.so.0 libgcrypt.so.20 libgpg-error.so.0 \
liblzma.so.5 liblz4.so.1 libcap.so.2 libz.so.1; do
LIB_PATH=$(ldconfig -p | grep -m 1 " => .*"$lib | awk '{print $4}')
if [ -n "$LIB_PATH" ]; then
cp "$LIB_PATH" "$OUT/"
fi
done

# Build dependencies and fuzzers
cd $SRC/go-avahi
go mod tidy
go install github.com/AdamKorcz/go-118-fuzz-build@latest
go get github.com/AdamKorcz/go-118-fuzz-build/testing

compile_native_go_fuzzer ./fuzzer FuzzDomainNormalize fuzz_domain_normalize
compile_native_go_fuzzer ./fuzzer FuzzDomainRoundTrip fuzz_domain_roundtrip
compile_native_go_fuzzer ./fuzzer FuzzServiceName fuzz_service_name
compile_native_go_fuzzer ./fuzzer FuzzStateStrings fuzz_state_strings

# RPATH fix: use patchelf to ensure $ORIGIN is set for all binaries
for fuzzer in fuzz_domain_normalize fuzz_domain_roundtrip fuzz_service_name fuzz_state_strings; do
if [ -f "$OUT/$fuzzer" ]; then
patchelf --set-rpath '$ORIGIN' "$OUT/$fuzzer"
fi
done
1 change: 1 addition & 0 deletions projects/go-avahi/seeds/roundtrip_seed_corpus/deep_nested
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a.b.c.d.e.f.g.h.i.j.local
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
My\\Service.example.com
1 change: 1 addition & 0 deletions projects/go-avahi/seeds/roundtrip_seed_corpus/escaped_dot
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
My\.Service.example.com
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
example.com
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
привет.example.com
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
My\.Printer._ipp._tcp.example.com
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Kyocera ECOSYS M2040dn._ipp._tcp.local
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
HP LaserJet._ipps._tcp.local
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test Device._subtype._ipp._tcp.local
1 change: 1 addition & 0 deletions projects/go-avahi/seeds/state_strings_seed_corpus/neg_one
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
����
Binary file not shown.
Binary file not shown.
Binary file not shown.