diff --git a/.github/workflows/cifuzz.yaml b/.github/workflows/cifuzz.yaml new file mode 100644 index 00000000..ea71ebc9 --- /dev/null +++ b/.github/workflows/cifuzz.yaml @@ -0,0 +1,27 @@ +name: CIFuzz +on: + pull_request: + branches: + - main +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'fluxcd' + language: go + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'fluxcd' + language: go + fuzz-seconds: 60 + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/.gitignore b/.gitignore index 66fd13c9..3c964d53 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +build/ diff --git a/Makefile b/Makefile index 73d39f77..4a02f57b 100644 --- a/Makefile +++ b/Makefile @@ -71,3 +71,24 @@ SETUP_ENVTEST=$(GOBIN)/setup-envtest else SETUP_ENVTEST=$(shell which setup-envtest) endif + + +fuzz-build: + rm -rf $(shell pwd)/build/fuzz/ + mkdir -p $(shell pwd)/build/fuzz/out/ + + docker build . --tag local-fuzzing:latest -f tests/fuzz/Dockerfile.builder + docker run --rm -it \ + -e FUZZING_LANGUAGE=go -e FUZZ_SECONDS=600 -e MODE=batch \ + -e CIFUZZ_DEBUG='True' -e OSS_FUZZ_PROJECT_NAME=fluxcd \ + -e SANITIZER=address \ + -v "$(shell pwd)/build/fuzz/out":/out \ + local-fuzzing:latest + +fuzz-smoketest: fuzz-build + docker run --rm -ti \ + -v "$(shell pwd)/build/fuzz/out":/out \ + -v "$(shell pwd)/tests/fuzz/oss_fuzz_run.sh":/runner.sh \ + -e ENVTEST_BIN_VERSION=$(ENVTEST_BIN_VERSION) \ + gcr.io/oss-fuzz/fluxcd \ + bash -c "/runner.sh" diff --git a/runtime/go.mod b/runtime/go.mod index 9da6e7c9..ca186f60 100644 --- a/runtime/go.mod +++ b/runtime/go.mod @@ -37,7 +37,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.1.2 // indirect + github.com/google/uuid v1.2.0 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/hashicorp/go-cleanhttp v0.5.1 // indirect github.com/imdario/mergo v0.3.12 // indirect @@ -52,14 +52,15 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 // indirect - golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect - golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/runtime/go.sum b/runtime/go.sum index 196ecb61..af15ac13 100644 --- a/runtime/go.sum +++ b/runtime/go.sum @@ -236,8 +236,9 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= @@ -309,6 +310,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -347,7 +350,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -605,8 +607,9 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -682,8 +685,9 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 h1:M69LAlWZCshgp0QSzyDcSsSIejIEeuaCVpmwcKwyLMk= golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 h1:WecRHqgE09JBkh/584XIE6PMz5KKE/vER4izNUi30AQ= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -884,8 +888,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= diff --git a/tests/fuzz/Dockerfile.builder b/tests/fuzz/Dockerfile.builder new file mode 100644 index 00000000..0199be87 --- /dev/null +++ b/tests/fuzz/Dockerfile.builder @@ -0,0 +1,6 @@ +FROM gcr.io/oss-fuzz-base/base-builder-go + +COPY ./ $GOPATH/src/github.com/fluxcd/pkg/ +COPY ./tests/fuzz/oss_fuzz_build.sh $SRC/build.sh + +WORKDIR $SRC diff --git a/tests/fuzz/README.md b/tests/fuzz/README.md new file mode 100644 index 00000000..f2d23396 --- /dev/null +++ b/tests/fuzz/README.md @@ -0,0 +1,45 @@ +# fuzz testing + +Flux is part of Google's [oss fuzz] program which provides continuous fuzzing for +open source projects. + +The long running fuzzing execution is configured in the [oss-fuzz repository]. +Shorter executions are done on a per-PR basis, configured as a [github workflow]. + +For fuzzers to be called, they must be compiled within [oss_fuzz_build.sh](./oss_fuzz_build.sh). + +### Testing locally + +Build fuzzers: + +```bash +make fuzz-build +``` +All fuzzers will be built into `./build/fuzz/out`. + +Smoke test fuzzers: + +```bash +make fuzz-smoketest +``` + +The smoke test runs each fuzzer once to ensure they are fully functional. + +Run fuzzer locally: +```bash +./build/fuzz/out/fuzz_conditions_match +``` + +Run fuzzer inside a container: + +```bash + docker run --rm -ti \ + -v "$(pwd)/build/fuzz/out":/out \ + gcr.io/oss-fuzz/fluxcd \ + /out/fuzz_conditions_match +``` + + +[oss fuzz]: https://github.com/google/oss-fuzz +[oss-fuzz repository]: https://github.com/google/oss-fuzz/tree/master/projects/fluxcd +[github workflow]: .github/workflows/cifuzz.yaml diff --git a/tests/fuzz/conditions_fuzzer.go b/tests/fuzz/conditions_fuzzer.go new file mode 100644 index 00000000..2b197a12 --- /dev/null +++ b/tests/fuzz/conditions_fuzzer.go @@ -0,0 +1,162 @@ +//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 +// conditions.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 +// MatchCondition.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. +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. +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 +// Getter.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/tests/fuzz/events_fuzzer.go b/tests/fuzz/events_fuzzer.go new file mode 100644 index 00000000..4ee293e9 --- /dev/null +++ b/tests/fuzz/events_fuzzer.go @@ -0,0 +1,144 @@ +//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 events + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "sync" + + fuzz "github.com/AdaLogics/go-fuzz-headers" + "github.com/fluxcd/pkg/runtime/testenv" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + ctrl "sigs.k8s.io/controller-runtime" +) + +var ( + doOnce sync.Once + env *testenv.Environment + ts *httptest.Server + ctx = ctrl.SetupSignalHandler() +) + +const defaultBinVersion = "1.23" + +func envtestBinVersion() string { + if binVersion := os.Getenv("ENVTEST_BIN_VERSION"); binVersion != "" { + return binVersion + } + return defaultBinVersion +} + +func ensureDependencies() error { + // only install dependencies when running inside a container + if _, err := os.Stat("/.dockerenv"); os.IsNotExist(err) { + return nil + } + + if os.Getenv("KUBEBUILDER_ASSETS") == "" { + binVersion := envtestBinVersion() + cmd := exec.Command("/usr/bin/bash", "-c", fmt.Sprintf(`go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest && \ + /root/go/bin/setup-envtest use -p path %s`, binVersion)) + + cmd.Env = append(os.Environ(), "GOPATH=/root/go") + assetsPath, err := cmd.Output() + if err == nil { + os.Setenv("KUBEBUILDER_ASSETS", string(assetsPath)) + } + return err + } + + 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)) + } + }) + + ts = 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 ts.Close() + + scheme := runtime.NewScheme() + utilruntime.Must(corev1.AddToScheme(scheme)) + + env = testenv.New( + testenv.WithScheme(scheme), + ) + + go func() { + fmt.Println("Starting the test environment") + if err := env.Start(ctx); err != nil { + panic(fmt.Sprintf("Failed to start the test environment manager: %v", err)) + } + }() + <-env.Manager.Elected() + + eventRecorder, err := NewRecorder(env, ctrl.Log, ts.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 = env.Stop(); err != nil { + return 0 + } + + return 1 +} diff --git a/tests/fuzz/gitutil_fuzzer.go b/tests/fuzz/gitutil_fuzzer.go new file mode 100644 index 00000000..7195d092 --- /dev/null +++ b/tests/fuzz/gitutil_fuzzer.go @@ -0,0 +1,28 @@ +//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 gitutil + +import "errors" + +// FuzzLibGit2Error implements a fuzzer that targets gitutil.LibGit2Error. +func FuzzLibGit2Error(data []byte) int { + err := errors.New(string(data)) + _ = LibGit2Error(err) + return 1 +} diff --git a/tests/fuzz/oss_fuzz_build.sh b/tests/fuzz/oss_fuzz_build.sh new file mode 100755 index 00000000..c79bfb34 --- /dev/null +++ b/tests/fuzz/oss_fuzz_build.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# Copyright 2022 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. + +set -euxo pipefail + +GOPATH="${GOPATH:-/root/go}" +GO_SRC="${GOPATH}/src" +PROJECT_PATH="github.com/fluxcd/pkg" + +cd "${GO_SRC}" + +# Move fuzzer to their respective directories. +# This removes dependency noises from the modules' go.mod and go.sum files. +cp "${PROJECT_PATH}/tests/fuzz/conditions_fuzzer.go" "${PROJECT_PATH}/runtime/conditions" +cp "${PROJECT_PATH}/tests/fuzz/events_fuzzer.go" "${PROJECT_PATH}/runtime/events" +cp "${PROJECT_PATH}/tests/fuzz/tls_fuzzer.go" "${PROJECT_PATH}/runtime/tls" +cp "${PROJECT_PATH}/tests/fuzz/untar_fuzzer.go" "${PROJECT_PATH}/untar" +cp "${PROJECT_PATH}/tests/fuzz/gitutil_fuzzer.go" "${PROJECT_PATH}/gitutil" + + +# compile fuzz tests for the runtime module +pushd "${PROJECT_PATH}/runtime" + +go mod tidy +compile_go_fuzzer "${PROJECT_PATH}/runtime/conditions" FuzzGetterConditions fuzz_getter_conditions +compile_go_fuzzer "${PROJECT_PATH}/runtime/conditions" FuzzConditionsMatch fuzz_conditions_match +compile_go_fuzzer "${PROJECT_PATH}/runtime/conditions" FuzzPatchApply fuzz_patch_apply +compile_go_fuzzer "${PROJECT_PATH}/runtime/conditions" FuzzConditionsUnstructured fuzz_conditions_unstructured +compile_go_fuzzer "${PROJECT_PATH}/runtime/events" FuzzEventf fuzz_eventf +compile_go_fuzzer "${PROJECT_PATH}/runtime/tls" FuzzTlsConfig fuzz_tls_config + +popd + + +# compile fuzz tests for the untar module +pushd "${PROJECT_PATH}/untar" + +go mod tidy +compile_go_fuzzer "${PROJECT_PATH}/untar" FuzzUntar fuzz_untar + +popd + + +# compile fuzz tests for the gitutil module +pushd "${PROJECT_PATH}/gitutil" + +go mod tidy +compile_go_fuzzer "${PROJECT_PATH}/gitutil" FuzzLibGit2Error fuzz_libgit2_error + +popd diff --git a/tests/fuzz/oss_fuzz_run.sh b/tests/fuzz/oss_fuzz_run.sh new file mode 100755 index 00000000..b812467f --- /dev/null +++ b/tests/fuzz/oss_fuzz_run.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Copyright 2022 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. + +set -euxo pipefail + +# run each fuzzer once to ensure they are working properly +find /out -type f -name "fuzz*" -exec '{}' {} \; diff --git a/tests/fuzz/tls_fuzzer.go b/tests/fuzz/tls_fuzzer.go new file mode 100644 index 00000000..f2267093 --- /dev/null +++ b/tests/fuzz/tls_fuzzer.go @@ -0,0 +1,36 @@ +//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 +} diff --git a/tests/fuzz/untar_fuzzer.go b/tests/fuzz/untar_fuzzer.go new file mode 100644 index 00000000..a0bae71f --- /dev/null +++ b/tests/fuzz/untar_fuzzer.go @@ -0,0 +1,37 @@ +//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 untar + +import ( + "bytes" + "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(r, tmpDir) + return 1 +}