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
78 changes: 78 additions & 0 deletions projects/go-avahi/fuzzer/fuzz_client_lifecycle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Fuzz target for go-avahi's Client lifecycle.
*
* Tests rapid creation and tear-down of Clients to detect race
* conditions, resource leaks, and panics in the CGo initialization
* path. Requires a running avahi-daemon; skips if unavailable.
*
* Also exercises GetVersionString, GetHostName, GetDomainName, and
* GetHostFQDN with fuzz-driven repetition to stress the threaded poll
* lock/unlock cycle.
*/

package fuzzer

import (
"context"
"testing"
"time"

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

func FuzzClientLifecycle(f *testing.F) {
f.Fuzz(func(t *testing.T, iterations uint8) {
// Clamp to a sane range to avoid exhausting file descriptors
if iterations == 0 {
iterations = 1
}
if iterations > 16 {
iterations = 16
}

for i := 0; i < int(iterations); i++ {
clnt, err := avahi.NewClient(0)
if err != nil {
// avahi-daemon not running; skip gracefully
t.Skip("avahi-daemon not available")
return
}

// Give the client a short window to connect and
// report its initial state via the event channel.
ctx, cancel := context.WithTimeout(
context.Background(), 200*time.Millisecond)

// Drain the first event (connecting / running)
clnt.Get(ctx) //nolint:errcheck
cancel()

// Exercise the query methods that call into CGo
// under the threaded poll lock, verifying no panics
// and no empty return values.
version := clnt.GetVersionString()
if version == "" {
t.Error("GetVersionString returned empty string")
}

host := clnt.GetHostName()
if host == "" {
t.Error("GetHostName returned empty string")
}

domain := clnt.GetDomainName()
if domain == "" {
t.Error("GetDomainName returned empty string")
}

fqdn := clnt.GetHostFQDN()
if fqdn == "" {
t.Error("GetHostFQDN returned empty string")
}

// Close must be idempotent — call twice to verify
clnt.Close()
clnt.Close()
}
})
}
27 changes: 27 additions & 0 deletions projects/go-avahi/fuzzer/fuzz_dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build linux || freebsd

package fuzzer

import (
"testing"

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

func FuzzDecodeDNSA(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
_ = avahi.DNSDecodeA(data)
})
}

func FuzzDNSAAAA(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
_ = avahi.DNSDecodeAAAA(data)
})
}

func FuzzDNSTXT(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
_ = avahi.DNSDecodeTXT(data)
})
}
64 changes: 64 additions & 0 deletions projects/go-avahi/fuzzer/fuzz_entry_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// CGo binding for Avahi
//
// Copyright (C) 2024 and up by Alexander Pevzner (pzz@apevzner.com)
// See LICENSE for license terms and conditions
//
// Fuzz target for go-avahi's EntryGroup lifecycle
//
//go:build linux || freebsd

package fuzzer

import (
"context"
"testing"
"time"

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

func FuzzEntryGroupLifecycle(f *testing.F) {
f.Fuzz(func(t *testing.T, count uint8, svcName string, svcType string) {
if count == 0 {
count = 1
}
if count > 5 {
count = 5
}

clnt, err := avahi.NewClient(0)
if err != nil {
t.Skip("avahi-daemon not available")
return
}
defer clnt.Close()

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
clnt.Get(ctx) //nolint:errcheck
cancel()

egrp, err := avahi.NewEntryGroup(clnt)
if err != nil {
return
}
defer egrp.Close()

for i := 0; i < int(count); i++ {
_ = egrp.AddService(&avahi.EntryGroupService{
IfIdx: avahi.IfIndexUnspec,
Proto: avahi.ProtocolUnspec,
InstanceName: svcName,
SvcType: svcType,
Domain: "local",
Port: 8080,
Txt: []string{"key=value"},
}, 0)

_ = egrp.Commit()
_ = egrp.Reset()

// Optional intermediate commits
_ = egrp.Commit()
}
})
}
54 changes: 54 additions & 0 deletions projects/go-avahi/fuzzer/fuzz_service_browser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// CGo binding for Avahi
//
// Copyright (C) 2024 and up by Alexander Pevzner (pzz@apevzner.com)
// See LICENSE for license terms and conditions
//
// Fuzz target for go-avahi's EntryGroup lifecycle
//
//go:build linux || freebsd

package fuzzer

import (
"context"
"testing"
"time"

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

func FuzzServiceBrowserLifecycle(f *testing.F) {
f.Fuzz(func(t *testing.T, count uint8, svcType string, domain string) {
if count == 0 {
count = 1
}
if count > 5 {
count = 5
}

clnt, err := avahi.NewClient(0)
if err != nil {
t.Skip("avahi-daemon not available")
return
}
defer clnt.Close()

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
clnt.Get(ctx) //nolint:errcheck
cancel()

for i := 0; i < int(count); i++ {
browser, err := avahi.NewServiceBrowser(clnt, avahi.IfIndexUnspec, avahi.ProtocolUnspec, svcType, domain, 0)
if err != nil {
continue
}

// Optional: try to fetch an event rapidly before tearing down
ctx2, cancel2 := context.WithTimeout(context.Background(), 10*time.Millisecond)
browser.Get(ctx2) //nolint:errcheck
cancel2()

browser.Close()
}
})
}
15 changes: 8 additions & 7 deletions projects/go-avahi/fuzzer/fuzz_state_strings.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/*
* 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.
*/
// CGo binding for Avahi
//
// Copyright (C) 2024 and up by Alexander Pevzner (pzz@apevzner.com)
// See LICENSE for license terms and conditions
//
// Fuzz target for go-avahi's Domain round-trip consistency
//
//go:build linux || freebsd

package fuzzer

Expand Down
73 changes: 73 additions & 0 deletions projects/go-avahi/fuzzer/fuzz_string_array.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// CGo binding for Avahi
//
// Copyright (C) 2024 and up by Alexander Pevzner (pzz@apevzner.com)
// See LICENSE for license terms and conditions
//
// Fuzz target for go-avahi's string list CGo conversion path
//
//go:build linux || freebsd

package fuzzer

import (
"strings"
"testing"
"unicode/utf8"

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

func FuzzStringArray(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
// Only valid UTF-8: Avahi TXT records are byte strings but
// the Go layer uses string, and invalid UTF-8 would not
// represent real-world TXT record inputs.
if !utf8.ValidString(data) {
return
}

// Split fuzz data into TXT record entries using newline as
// delimiter, simulating key=value pairs in mDNS TXT records.
raw := strings.Split(data, "\n")

var txt []string
for _, e := range raw {
if len(e) > 0 && len(e) <= 255 {
// TXT record strings are limited to 255 bytes
// per the DNS spec; enforce the limit.
txt = append(txt, e)
}
}

if len(txt) == 0 {
return
}

// Create a client to exercise the full EntryGroup path that
// calls makeAvahiStringList internally. Skip if no daemon.
clnt, err := avahi.NewClient(0)
if err != nil {
t.Skip("avahi-daemon not available")
return
}
defer clnt.Close()

egrp, err := avahi.NewEntryGroup(clnt)
if err != nil {
return
}
defer egrp.Close()

// AddService calls makeAvahiStringList on the txt slice.
// We do not assert success — we assert no panic and no crash.
_ = egrp.AddService(&avahi.EntryGroupService{
IfIdx: avahi.IfIndexUnspec,
Proto: avahi.ProtocolUnspec,
InstanceName: "fuzz-test",
SvcType: "_fuzz._tcp",
Domain: "",
Port: 9999,
Txt: txt,
}, 0)
})
}
24 changes: 23 additions & 1 deletion projects/go-avahi/oss_fuzz_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ 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/

# Package seed corpus — string array
mkdir -p $WORK/string_array_seed_corpus
cp $SRC/fuzzing/projects/go-avahi/seeds/string_array_seed_corpus/* $WORK/string_array_seed_corpus/
zip -r $OUT/fuzz_string_array_seed_corpus.zip string_array_seed_corpus/

# Package seed corpus — client lifecycle
mkdir -p $WORK/client_lifecycle_seed_corpus
cp $SRC/fuzzing/projects/go-avahi/seeds/client_lifecycle_seed_corpus/* $WORK/client_lifecycle_seed_corpus/
zip -r $OUT/fuzz_client_lifecycle_seed_corpus.zip client_lifecycle_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
Expand All @@ -29,6 +39,11 @@ 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/
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_string_array.go $SRC/go-avahi/fuzzer/
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_client_lifecycle.go $SRC/go-avahi/fuzzer/
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_dns.go $SRC/go-avahi/fuzzer/
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_entry_group.go $SRC/go-avahi/fuzzer/
cp $SRC/fuzzing/projects/go-avahi/fuzzer/fuzz_service_browser.go $SRC/go-avahi/fuzzer/

# CGo environment: use pkg-config for architecture-agnostic library resolution
export CGO_ENABLED=1
Expand Down Expand Up @@ -60,9 +75,16 @@ 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
compile_native_go_fuzzer ./fuzzer FuzzStringArray fuzz_string_array
compile_native_go_fuzzer ./fuzzer FuzzClientLifecycle fuzz_client_lifecycle
compile_native_go_fuzzer ./fuzzer FuzzDecodeDNSA fuzz_dns_decode_a
compile_native_go_fuzzer ./fuzzer FuzzDNSAAAA fuzz_dns_decode_aaaa
compile_native_go_fuzzer ./fuzzer FuzzDNSTXT fuzz_dns_decode_txt
compile_native_go_fuzzer ./fuzzer FuzzEntryGroupLifecycle fuzz_entry_group
compile_native_go_fuzzer ./fuzzer FuzzServiceBrowserLifecycle fuzz_service_browser

# 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
for fuzzer in fuzz_domain_normalize fuzz_domain_roundtrip fuzz_service_name fuzz_state_strings fuzz_string_array fuzz_client_lifecycle fuzz_dns_decode_a fuzz_dns_decode_aaaa fuzz_dns_decode_txt fuzz_entry_group fuzz_service_browser; do
if [ -f "$OUT/$fuzzer" ]; then
patchelf --set-rpath '$ORIGIN' "$OUT/$fuzzer"
fi
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
txtvers=1
pdl=application/pdf,image/jpeg
printer-state=3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
key=value
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
adminurl=http://localhost:631/printers/test
color=T
copies=T