diff --git a/fuzz/Dockerfile b/fuzz/Dockerfile new file mode 100644 index 000000000..31d5e049b --- /dev/null +++ b/fuzz/Dockerfile @@ -0,0 +1,62 @@ +FROM golang:1.16-buster as builder +RUN set -eux; \ + apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y \ + clang \ + curl \ + vim + +RUN git clone https://github.com/fluxcd/pkg /workspace +RUN mkdir /workspace/fuzzing + + +RUN go get -u github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest +RUN go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest +RUN go get github.com/AdaLogics/go-fuzz-headers + +RUN go get golang.org/x/sync + +WORKDIR /workspace/fuzzing +COPY fuzz.go /workspace/fuzzing/ +COPY conditions_fuzzer.go /workspace/runtime/conditions/ +COPY tls_fuzzer.go /workspace/runtime/tls/ +RUN cd /workspace/fuzzing && go mod init fuzzing && go mod tidy && go mod download && go mod tidy +RUN go mod download github.com/dvyukov/go-fuzz + + +# Build the fuzzers +RUN mkdir /fuzzers + +RUN go-fuzz-build -libfuzzer -func=FuzzUntar\ + && clang -o /fuzzers/FuzzUntar reflect-fuzz.a \ + -fsanitize=fuzzer +RUN go-fuzz-build -libfuzzer -func=FuzzLibGit2Error\ + && clang -o /fuzzers/FuzzLibGit2Error reflect-fuzz.a \ + -fsanitize=fuzzer +RUN go-fuzz-build -libfuzzer -func=FuzzEventInfof\ + && clang -o /fuzzers/FuzzEventInfof reflect-fuzz.a \ + -fsanitize=fuzzer + +WORKDIR /workspace/runtime/tls +RUN go get github.com/AdaLogics/go-fuzz-headers +RUN go get github.com/dvyukov/go-fuzz/go-fuzz-dep +RUN go-fuzz-build -libfuzzer -func=FuzzTlsConfig\ + && clang -o /fuzzers/FuzzTlsConfig reflect-fuzz.a \ + -fsanitize=fuzzer + +WORKDIR /workspace/runtime/conditions +RUN go-fuzz-build -libfuzzer -func=FuzzGetterConditions\ + && clang -o /fuzzers/FuzzGetterConditions reflect-fuzz.a \ + -fsanitize=fuzzer +RUN go-fuzz-build -libfuzzer -func=FuzzConditionsMatch\ + && clang -o /fuzzers/FuzzConditionsMatch reflect-fuzz.a \ + -fsanitize=fuzzer +RUN go-fuzz-build -libfuzzer -func=FuzzPatchApply\ + && clang -o /fuzzers/FuzzPatchApply reflect-fuzz.a \ + -fsanitize=fuzzer +RUN go-fuzz-build -libfuzzer -func=FuzzConditionsUnstructured\ + && clang -o /fuzzers/FuzzConditionsUnstructured reflect-fuzz.a \ + -fsanitize=fuzzer + +#RUN /fuzzers/FuzzLibGit2Error \ No newline at end of file diff --git a/fuzz/conditions_fuzzer.go b/fuzz/conditions_fuzzer.go new file mode 100644 index 000000000..02dd8fcbb --- /dev/null +++ b/fuzz/conditions_fuzzer.go @@ -0,0 +1,161 @@ +//go:build gofuzz +// +build gofuzz + +/* +Copyright 2021 The Flux authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package conditions + +import ( + fuzz "github.com/AdaLogics/go-fuzz-headers" + "github.com/fluxcd/pkg/runtime/conditions/testdata" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// FuzzGetterConditions implements a fuzzer that +// targets SetSummary() +func FuzzGetterConditions(data []byte) int { + f := fuzz.NewConsumer(data) + + // Create slice of metav1.Condition + noOfConditions, err := f.GetInt() + if err != nil { + return 0 + } + maxNoOfConditions := 30 + conditions := make([]metav1.Condition, 0) + + // Add Conditions in the slice + for i := 0; i < noOfConditions%maxNoOfConditions; i++ { + c := metav1.Condition{} + err = f.GenerateStruct(&c) + if err != nil { + return 0 + } + conditions = append(conditions, c) + } + obj := &testdata.Fake{} + obj.SetConditions(conditions) + + targetCondition, err := f.GetString() + if err != nil { + return 0 + } + + // Call the target + SetSummary(obj, targetCondition) + return 1 +} + +// FuzzConditionsMatch implements a fuzzer that +// that targets Match() +func FuzzConditionsMatch(data []byte) int { + f := fuzz.NewConsumer(data) + condition := metav1.Condition{} + err := f.GenerateStruct(&condition) + if err != nil { + return 0 + } + m := MatchCondition(condition) + + actual := metav1.Condition{} + err = f.GenerateStruct(&actual) + if err != nil { + return 0 + } + + // Call the target + _, _ = m.Match(actual) + return 1 +} + +// newGetter allows the fuzzer to create a Getter +// This is just a utility +func newGetter(f *fuzz.ConsumeFuzzer) (Getter, error) { + obj := &testdata.Fake{} + noOfConditions, err := f.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 = f.GenerateStruct(&c) + if err != nil { + return obj, err + } + conditions = append(conditions, c) + } + + obj.SetConditions(conditions) + return obj, nil +} + +// newSetter allows the fuzzer to create a Setter +// This is just a utility +func newSetter(f *fuzz.ConsumeFuzzer) (Setter, error) { + obj := &testdata.Fake{} + noOfConditions, err := f.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 = f.GenerateStruct(&c) + if err != nil { + return obj, err + } + conditions = append(conditions, c) + } + obj.SetConditions(conditions) + return obj, nil +} + +// FuzzPatchApply implements a fuzzer that targets patch.Apply +func FuzzPatchApply(data []byte) int { + f := fuzz.NewConsumer(data) + + before, err := newGetter(f) + if err != nil { + return 0 + } + after, err := newGetter(f) + if err != nil { + return 0 + } + patch := NewPatch(before, after) + + setter, err := newSetter(f) + if err != nil { + return 0 + } + _ = patch.Apply(setter) + return 1 +} + +// FuzzConditionsUnstructured implements a fuzzer +// that targets GetConditions() +func FuzzConditionsUnstructured(data []byte) int { + u := &unstructured.Unstructured{} + f := fuzz.NewConsumer(data) + err := f.GenerateStruct(u) + if err != nil { + return 0 + } + g := UnstructuredGetter(u) + _ = g.GetConditions() + return 1 +} diff --git a/fuzz/fuzz.go b/fuzz/fuzz.go new file mode 100644 index 000000000..50352817e --- /dev/null +++ b/fuzz/fuzz.go @@ -0,0 +1,91 @@ +//go:build gofuzz +// +build gofuzz + +/* +Copyright 2021 The Flux authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package fuzzing + +import ( + "bytes" + "encoding/json" + "errors" + fuzz "github.com/AdaLogics/go-fuzz-headers" + "github.com/fluxcd/pkg/gitutil" + "github.com/fluxcd/pkg/runtime/events" + "github.com/fluxcd/pkg/untar" + "io" + corev1 "k8s.io/api/core/v1" + "net/http" + "net/http/httptest" + "os" +) + +// FuzzUntar implements a fuzzer that +// targets untar.Untar() +func FuzzUntar(data []byte) int { + r := bytes.NewReader(data) + tmpDir, err := os.MkdirTemp("", "dir-") + if err != nil { + return 0 + } + defer os.RemoveAll(tmpDir) + _, _ = untar.Untar(r, tmpDir) + return 1 +} + +// FuzzLibGit2Error implements a fuzzer that +// targets gitutil.LibGit2Error +func FuzzLibGit2Error(data []byte) int { + err := errors.New(string(data)) + _ = gitutil.LibGit2Error(err) + return 1 +} + +// FuzzEventInfof implements a fuzzer that +// targets eventRecorder.EventInfof() +func FuzzEventInfof(data []byte) int { + f := fuzz.NewConsumer(data) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, err := io.ReadAll(r.Body) + if err != nil { + return + } + + var payload events.Event + err = json.Unmarshal(b, &payload) + if err != nil { + return + } + })) + defer ts.Close() + eventRecorder, err := events.NewRecorder(ts.URL, "test-controller") + if err != nil { + return 0 + } + eventRecorder.Client.RetryMax = 2 + obj := corev1.ObjectReference{} + err = f.GenerateStruct(&obj) + if err != nil { + return 0 + } + severity, err := f.GetString() + if err != nil { + return 0 + } + reason, err := f.GetString() + if err != nil { + return 0 + } + _ = eventRecorder.EventInfof(obj, nil, severity, reason, obj.Name) + return 1 +} diff --git a/fuzz/tls_fuzzer.go b/fuzz/tls_fuzzer.go new file mode 100644 index 000000000..d03559376 --- /dev/null +++ b/fuzz/tls_fuzzer.go @@ -0,0 +1,34 @@ +//go:build gofuzz +// +build gofuzz + +/* +Copyright 2021 The Flux authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package tls + +import ( + fuzz "github.com/AdaLogics/go-fuzz-headers" + corev1 "k8s.io/api/core/v1" +) + +// FuzzTlsConfig implements a fuzzer that +// targets ConfigFromSecret() +func FuzzTlsConfig(data []byte) int { + secret := &corev1.Secret{} + f := fuzz.NewConsumer(data) + err := f.GenerateStruct(secret) + if err != nil { + return 0 + } + _, _ = ConfigFromSecret(secret) + return 1 +}