Skip to content

Commit

Permalink
fuzz: Refactor Fuzzers based on Go native fuzzing
Browse files Browse the repository at this point in the history
Due to the structure of fluxcd/pkg, its oss_fuzz_build.sh file deviates
slightly from the same file in other repositories. It goes through all
the Go packages and ensure they have the required fuzz dependencies,
needed for oss-fuzz.

The generated fuzzers will be prefixed with fuzz_ plus the Go module
name, for improved discoverability within oss-fuzz.

This project does not contain a make target for Go native fuzzing,
as not all tests play nicely with that ecosystem. Once Fuzz_Eventf is
refactored this can be reviewed.

Signed-off-by: Paulo Gomes <paulo.gomes@weave.works>
  • Loading branch information
Paulo Gomes committed Sep 6, 2022
1 parent ba1e245 commit 8b7bf6e
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 368 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ rm -rf $$TMP_DIR ;\
}
endef

# Build fuzzers used by oss-fuzz.
fuzz-build:
rm -rf $(shell pwd)/build/fuzz/
mkdir -p $(shell pwd)/build/fuzz/out/
Expand All @@ -99,6 +100,7 @@ fuzz-build:
-v "$(shell pwd)/build/fuzz/out":/out \
local-fuzzing:latest

# Run each fuzzer once to ensure they will work when executed by oss-fuzz.
fuzz-smoketest: fuzz-build
docker run --rm \
-v "$(shell pwd)/build/fuzz/out":/out \
Expand Down
30 changes: 30 additions & 0 deletions gitutil/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,33 @@ func TestGoGitErrorUnchanged(t *testing.T) {
t.Errorf("expected %q, got %q", expectedReformat, reformattedMessage)
}
}

func Fuzz_LibGit2Error(f *testing.F) {
f.Add("")
f.Add("single line error")
f.Add("multi\nline\nerror")

f.Fuzz(func(t *testing.T, msg string) {
var err error
if msg != "" {
err = errors.New(msg)
}

_ = LibGit2Error(err)
})
}

func Fuzz_GoGitError(f *testing.F) {
f.Add("")
f.Add("unknown error: remote: ")
f.Add("some other error")

f.Fuzz(func(t *testing.T, msg string) {
var err error
if msg != "" {
err = errors.New(msg)
}

_ = GoGitError(err)
})
}
37 changes: 37 additions & 0 deletions runtime/conditions/getter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ package conditions
import (
"testing"

fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/fluxcd/pkg/apis/meta"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -380,6 +381,42 @@ func TestAggregate(t *testing.T) {
}
}

func Fuzz_Getter(f *testing.F) {
f.Fuzz(func(t *testing.T,
data []byte) {
fc := fuzz.NewConsumer(data)

// Set a reproduceable amount of conditions.
noOfConditions, err := fc.GetInt()
if err != nil {
return
}
maxNoOfConditions := 30
conditions := make([]metav1.Condition, 0)

// Add N conditions in the slice based on a reproduceable
// state provided by fc.
for i := 0; i < noOfConditions%maxNoOfConditions; i++ {
c := metav1.Condition{}
err = fc.GenerateStruct(&c)
if err != nil {
return
}
conditions = append(conditions, c)
}
obj := &testdata.Fake{}
obj.SetConditions(conditions)

targetCondition, err := fc.GetString()
if err != nil {
return
}

SetSummary(obj, targetCondition)
return
})
}

func getterWithConditions(conditions ...*metav1.Condition) Getter {
obj := &testdata.Fake{}
obj.SetConditions(conditionList(conditions...))
Expand Down
22 changes: 22 additions & 0 deletions runtime/conditions/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ package conditions
import (
"testing"

fuzz "github.com/AdaLogics/go-fuzz-headers"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand Down Expand Up @@ -296,3 +297,24 @@ func TestMatchCondition(t *testing.T) {
})
}
}

func Fuzz_Match(f *testing.F) {
f.Fuzz(func(t *testing.T,
data []byte) {

f := fuzz.NewConsumer(data)
condition := metav1.Condition{}
err := f.GenerateStruct(&condition)
if err != nil {
return
}
m := MatchCondition(condition)

actual := metav1.Condition{}
err = f.GenerateStruct(&actual)

if err == nil {
_, _ = m.Match(actual)
}
})
}
47 changes: 47 additions & 0 deletions runtime/conditions/patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"testing"
"time"

fuzz "github.com/AdaLogics/go-fuzz-headers"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

Expand Down Expand Up @@ -289,3 +290,49 @@ func TestApplyDoesNotAlterLastTransitionTime(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(latest.GetConditions()).To(Equal(after.GetConditions()))
}

func Fuzz_PatchApply(f *testing.F) {
f.Fuzz(func(t *testing.T,
beforeData, afterData, setterData []byte) {

before, err := newFake(fuzz.NewConsumer(beforeData))
if err != nil {
return
}

after, err := newFake(fuzz.NewConsumer(afterData))
if err != nil {
return
}

patch := NewPatch(before, after)
setter, err := newFake(fuzz.NewConsumer(setterData))
if err != nil {
return
}

_ = patch.Apply(setter)
})
}

func newFake(fc *fuzz.ConsumeFuzzer) (*testdata.Fake, error) {
obj := &testdata.Fake{}
noOfConditions, err := fc.GetInt()
if err != nil {
return obj, err
}

maxNoOfConditions := 30
conditions := make([]metav1.Condition, 0)
for i := 0; i < noOfConditions%maxNoOfConditions; i++ {
c := metav1.Condition{}
err = fc.GenerateStruct(&c)
if err != nil {
return obj, err
}

conditions = append(conditions, c)
}
obj.SetConditions(conditions)
return obj, nil
}
24 changes: 24 additions & 0 deletions runtime/conditions/unstructured_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,27 @@ func TestUnstructuredSetConditions(t *testing.T) {
s.SetConditions(conditions)
g.Expect(s.GetConditions()).To(Equal(conditions))
}

func Fuzz_Unstructured(f *testing.F) {
f.Add("type", "reason true", "condition message")

f.Fuzz(func(t *testing.T,
ct, reason, message string) {

cs := []metav1.Condition{{
Type: ct,
Status: metav1.ConditionUnknown,
Reason: reason,
Message: message,
}}

u := &unstructured.Unstructured{
Object: map[string]interface{}{},
}
s := UnstructuredSetter(u)
s.SetConditions(cs)

g := UnstructuredGetter(u)
_ = g.GetConditions()
})
}
140 changes: 70 additions & 70 deletions tests/fuzz/events_fuzzer.go → runtime/events/recorder_fuzzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"os"
"os/exec"
"sync"
"testing"

fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/fluxcd/pkg/runtime/testenv"
Expand All @@ -43,7 +44,75 @@ var (
fuzzCtx = ctrl.SetupSignalHandler()
)

const defaultBinVersion = "1.23"
const defaultBinVersion = "1.24"

// Fuzz_Eventf is locked behind a build tag, as its test setup conflicts with the
// suite_test.go. This test should be refactored to no longer require testenv,
// which will resolve the problem whilst making the test more effient.
//
// TODO: refactor and remove build tag.
func Fuzz_Eventf(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
doOnce.Do(func() {
if err := ensureDependencies(); err != nil {
panic(fmt.Sprintf("Failed to start the test environment manager: %v", err))
}
})

fuzzTs = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
if err != nil {
return
}

var payload Event
err = json.Unmarshal(b, &payload)
if err != nil {
return
}
}))
defer fuzzTs.Close()

scheme := runtime.NewScheme()
utilruntime.Must(corev1.AddToScheme(scheme))

fuzzEnv = testenv.New(
testenv.WithScheme(scheme),
)

go func() {
fmt.Println("Starting the test environment")
if err := fuzzEnv.Start(fuzzCtx); err != nil {
panic(fmt.Sprintf("Failed to start the test environment manager: %v", err))
}
}()
<-fuzzEnv.Manager.Elected()

eventRecorder, err := NewRecorder(fuzzEnv, ctrl.Log, fuzzTs.URL, "test-controller")
if err != nil {
return
}
eventRecorder.Client.RetryMax = 2

f := fuzz.NewConsumer(data)
obj := corev1.ConfigMap{}
err = f.GenerateStruct(&obj)
if err != nil {
return
}
eventtype, err := f.GetString()
if err != nil {
return
}
reason, err := f.GetString()
if err != nil {
return
}
eventRecorder.Eventf(&obj, eventtype, reason, obj.Name)

_ = fuzzEnv.Stop()
})
}

func envtestBinVersion() string {
if binVersion := os.Getenv("ENVTEST_BIN_VERSION"); binVersion != "" {
Expand Down Expand Up @@ -73,72 +142,3 @@ func ensureDependencies() error {

return nil
}

// FuzzEventInfof implements a fuzzer that targets eventRecorder.Eventf().
func FuzzEventf(data []byte) int {
doOnce.Do(func() {
if err := ensureDependencies(); err != nil {
panic(fmt.Sprintf("Failed to start the test environment manager: %v", err))
}
})

fuzzTs = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
if err != nil {
return
}

var payload Event
err = json.Unmarshal(b, &payload)
if err != nil {
return
}
}))
defer fuzzTs.Close()

scheme := runtime.NewScheme()
utilruntime.Must(corev1.AddToScheme(scheme))

fuzzEnv = testenv.New(
testenv.WithScheme(scheme),
)

go func() {
fmt.Println("Starting the test environment")
if err := fuzzEnv.Start(fuzzCtx); err != nil {
panic(fmt.Sprintf("Failed to start the test environment manager: %v", err))
}
}()
<-fuzzEnv.Manager.Elected()

eventRecorder, err := NewRecorder(fuzzEnv, ctrl.Log, fuzzTs.URL, "test-controller")
if err != nil {
return 0
}
eventRecorder.Client.RetryMax = 2
//TODO: Reuse the setup above across fuzzing calls
// this will be easier once fuzzing is migrated to
// native golang fuzz.

f := fuzz.NewConsumer(data)
obj := corev1.ConfigMap{}
err = f.GenerateStruct(&obj)
if err != nil {
return 0
}
eventtype, err := f.GetString()
if err != nil {
return 0
}
reason, err := f.GetString()
if err != nil {
return 0
}
eventRecorder.Eventf(&obj, eventtype, reason, obj.Name)

if err = fuzzEnv.Stop(); err != nil {
return 0
}

return 1
}
Loading

0 comments on commit 8b7bf6e

Please sign in to comment.