From a970fd774a68238cf86dec6b86d4f0992864ce4d Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 24 Nov 2022 09:15:43 +0000 Subject: [PATCH 1/3] Bump envtest to 1.24.0 Signed-off-by: Paulo Gomes --- Makefile | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 271e7115d..313413f42 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,11 @@ REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel) BUILD_DIR := $(REPOSITORY_ROOT)/build # Other dependency versions -ENVTEST_BIN_VERSION ?= 1.19.2 +ENVTEST_BIN_VERSION ?= 1.24.0 + +# FUZZ_TIME defines the max amount of time, in Go Duration, +# each fuzzer should run for. +FUZZ_TIME ?= 1m # Caches libgit2 versions per tag, "forcing" rebuild only when needed. LIBGIT2_PATH := $(BUILD_DIR)/libgit2/$(LIBGIT2_TAG) @@ -206,9 +210,9 @@ ifneq ($(shell grep -o 'LIBGIT2_IMG ?= \w.*' Makefile | cut -d ' ' -f 3):$(shell exit 1; \ } endif -ifneq ($(shell grep -o 'LIBGIT2_TAG ?= \w.*' Makefile | cut -d ' ' -f 3), $(shell grep -o "LIBGIT2_TAG=.*" tests/fuzz/oss_fuzz_build.sh | sed 's;LIBGIT2_TAG="$${LIBGIT2_TAG:-;;g' | sed 's;}";;g')) +ifneq ($(shell grep -o 'LIBGIT2_TAG ?= \w.*' Makefile | cut -d ' ' -f 3), $(shell grep -o "LIBGIT2_TAG=.*" tests/fuzz/oss_fuzz_prebuild.sh | sed 's;LIBGIT2_TAG="$${LIBGIT2_TAG:-;;g' | sed 's;}";;g')) @{ \ - echo "LIBGIT2_TAG must match in both Makefile and tests/fuzz/oss_fuzz_build.sh"; \ + echo "LIBGIT2_TAG must match in both Makefile and tests/fuzz/oss_fuzz_prebuild.sh"; \ exit 1; \ } endif @@ -232,26 +236,32 @@ rm -rf $$TMP_DIR ;\ } endef -# Build fuzzers +# Build fuzzers used by oss-fuzz. fuzz-build: $(LIBGIT2) - rm -rf $(BUILD_DIR)/fuzz/ - mkdir -p $(BUILD_DIR)/fuzz/out/ + rm -rf $(shell pwd)/build/fuzz/ + mkdir -p $(shell pwd)/build/fuzz/out/ - docker build . --pull --tag local-fuzzing:latest -f tests/fuzz/Dockerfile.builder + docker build . --tag local-fuzzing:latest -f tests/fuzz/Dockerfile.builder docker run --rm \ -e FUZZING_LANGUAGE=go -e SANITIZER=address \ -e CIFUZZ_DEBUG='True' -e OSS_FUZZ_PROJECT_NAME=fluxcd \ - -v "$(BUILD_DIR)/fuzz/out":/out \ + -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 "$(BUILD_DIR)/fuzz/out":/out \ - -v "$(shell go env GOMODCACHE):/root/go/pkg/mod" \ + -v "$(shell pwd)/build/fuzz/out":/out \ -v "$(shell pwd)/tests/fuzz/oss_fuzz_run.sh":/runner.sh \ local-fuzzing:latest \ bash -c "/runner.sh" +# Run fuzz tests for the duration set in FUZZ_TIME. +fuzz-native: + KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \ + FUZZ_TIME=$(FUZZ_TIME) \ + ./tests/fuzz/native_go_run.sh + # Creates an env file that can be used to load all source-controller's dependencies # this is handy when you want to run adhoc debug sessions on tests or start the # controller in a new debug session. From 04dd4b54fbefcb1bb6e38b7a0f1936bd0dad5550 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 24 Nov 2022 09:55:09 +0000 Subject: [PATCH 2/3] build: Migrate to Go Native fuzz and improve reliability Establish conventions which aligns with what is supported upstream today, whilst expanding on documentation to ensure folks have pointers on how to debug/check for issues going forwards. Signed-off-by: Paulo Gomes --- Makefile | 1 - .../gitrepository_controller_fuzz_test.go | 488 +++++++++--------- tests/fuzz/Dockerfile.builder | 5 +- tests/fuzz/README.md | 82 +++ tests/fuzz/compile_native_go_fuzzer | 62 +++ tests/fuzz/go.mod | 7 - tests/fuzz/native_go_run.sh | 39 ++ tests/fuzz/oss_fuzz_build.sh | 134 ++--- tests/fuzz/oss_fuzz_prebuild.sh | 65 +++ 9 files changed, 549 insertions(+), 334 deletions(-) rename tests/fuzz/gitrepository_fuzzer.go => controllers/gitrepository_controller_fuzz_test.go (77%) create mode 100644 tests/fuzz/README.md create mode 100755 tests/fuzz/compile_native_go_fuzzer delete mode 100644 tests/fuzz/go.mod create mode 100755 tests/fuzz/native_go_run.sh create mode 100755 tests/fuzz/oss_fuzz_prebuild.sh diff --git a/Makefile b/Makefile index 313413f42..436303d1b 100644 --- a/Makefile +++ b/Makefile @@ -138,7 +138,6 @@ tidy: ## Run go mod tidy fmt: ## Run go fmt against code go fmt ./... cd api; go fmt ./... - cd tests/fuzz; go fmt . vet: $(LIBGIT2) ## Run go vet against code go vet ./... diff --git a/tests/fuzz/gitrepository_fuzzer.go b/controllers/gitrepository_controller_fuzz_test.go similarity index 77% rename from tests/fuzz/gitrepository_fuzzer.go rename to controllers/gitrepository_controller_fuzz_test.go index 1f7a89ba7..f16779f0f 100644 --- a/tests/fuzz/gitrepository_fuzzer.go +++ b/controllers/gitrepository_controller_fuzz_test.go @@ -1,5 +1,5 @@ -//go:build gofuzz -// +build gofuzz +//go:build gofuzz_libfuzzer +// +build gofuzz_libfuzzer /* Copyright 2022 The Flux authors @@ -61,7 +61,6 @@ import ( "github.com/fluxcd/pkg/gittestserver" "github.com/fluxcd/pkg/runtime/testenv" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" - "github.com/fluxcd/source-controller/controllers" ) var ( @@ -75,7 +74,7 @@ var ( cfg *rest.Config testEnv *testenv.Environment - storage *controllers.Storage + storage *Storage examplePublicKey []byte examplePrivateKey []byte @@ -87,277 +86,140 @@ var ( var testFiles embed.FS const ( - defaultBinVersion = "1.23" + defaultBinVersion = "1.24" lettersAndNumbers = "abcdefghijklmnopqrstuvwxyz123456789" lettersNumbersAndDash = "abcdefghijklmnopqrstuvwxyz123456789-" ) -func envtestBinVersion() string { - if binVersion := os.Getenv("ENVTEST_BIN_VERSION"); binVersion != "" { - return binVersion - } - return defaultBinVersion -} - -func ensureDependencies() error { - if _, err := os.Stat("/.dockerenv"); os.IsNotExist(err) { - return nil - } +// FuzzRandomGitFiles implements a fuzzer that +// targets the GitRepository reconciler. +func FuzzRandomGitFiles(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + initter.Do(func() { + utilruntime.Must(ensureDependencies()) + }) + + f := fuzz.NewConsumer(data) + namespace, deleteNamespace, err := createNamespace(f) + if err != nil { + return + } + defer deleteNamespace() - 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)) + gitServerURL, stopGitServer := createGitServer(f) + defer stopGitServer() - cmd.Env = append(os.Environ(), "GOPATH=/root/go") - assetsPath, err := cmd.Output() + fs := memfs.New() + gitrepo, err := git.Init(memory.NewStorage(), fs) if err != nil { - return err + panic(err) } - os.Setenv("KUBEBUILDER_ASSETS", string(assetsPath)) - } - - // Output all embedded testdata files - embedDirs := []string{"testdata/crd", "testdata/certs"} - for _, dir := range embedDirs { - err := os.MkdirAll(dir, 0o700) + wt, err := gitrepo.Worktree() if err != nil { - return fmt.Errorf("mkdir %s: %v", dir, err) + panic(err) } - templates, err := fs.ReadDir(testFiles, dir) + // Create random files for the git source + err = createRandomFiles(f, fs, wt) if err != nil { - return fmt.Errorf("reading embedded dir: %v", err) + return } - for _, template := range templates { - fileName := fmt.Sprintf("%s/%s", dir, template.Name()) - fmt.Println(fileName) - - data, err := testFiles.ReadFile(fileName) - if err != nil { - return fmt.Errorf("reading embedded file %s: %v", fileName, err) - } - - os.WriteFile(fileName, data, 0o600) - if err != nil { - return fmt.Errorf("writing %s: %v", fileName, err) - } + commit, err := pushFilesToGit(gitrepo, wt, gitServerURL.String()) + if err != nil { + return } - } - - startEnvServer(func(m manager.Manager) { - utilruntime.Must((&controllers.GitRepositoryReconciler{ - Client: m.GetClient(), - Storage: storage, - }).SetupWithManager(m)) - }) - - return nil -} - -func startEnvServer(setupReconcilers func(manager.Manager)) *envtest.Environment { - testEnv := &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("testdata", "crd")}, - } - fmt.Println("Starting the test environment") - cfg, err := testEnv.Start() - if err != nil { - panic(fmt.Sprintf("Failed to start the test environment manager: %v", err)) - } - - utilruntime.Must(loadExampleKeys()) - utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme)) - - tmpStoragePath, err := os.MkdirTemp("", "source-controller-storage-") - if err != nil { - panic(err) - } - defer os.RemoveAll(tmpStoragePath) - storage, err = controllers.NewStorage(tmpStoragePath, "localhost:5050", time.Minute*1, 2) - if err != nil { - panic(err) - } - // serve artifacts from the filesystem, as done in main.go - fs := http.FileServer(http.Dir(tmpStoragePath)) - http.Handle("/", fs) - go http.ListenAndServe(":5050", nil) - - cert, err := tls.X509KeyPair(examplePublicKey, examplePrivateKey) - if err != nil { - panic(err) - } - - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(exampleCA) - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: caCertPool, - } - tlsConfig.BuildNameToCertificate() - - var transport = httptransport.NewClient(&http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - }) - gitclient.InstallProtocol("https", transport) - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - if err != nil { - panic(err) - } - if k8sClient == nil { - panic("cfg is nil but should not be") - } - - k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ - Scheme: scheme.Scheme, - }) - if err != nil { - panic(err) - } - - setupReconcilers(k8sManager) - - time.Sleep(2 * time.Second) - go func() { - fmt.Println("Starting k8sManager...") - utilruntime.Must(k8sManager.Start(context.TODO())) - }() - - return testEnv -} + created, err := createGitRepository(f, gitServerURL.String(), commit.String(), namespace.Name) + if err != nil { + return + } + err = k8sClient.Create(context.Background(), created) + if err != nil { + return + } + defer k8sClient.Delete(context.Background(), created) -// FuzzRandomGitFiles implements a fuzzer that -// targets the GitRepository reconciler. -func FuzzRandomGitFiles(data []byte) int { - initter.Do(func() { - utilruntime.Must(ensureDependencies()) + // Let the reconciler do its thing: + time.Sleep(60 * time.Millisecond) }) - - f := fuzz.NewConsumer(data) - namespace, deleteNamespace, err := createNamespace(f) - if err != nil { - return 0 - } - defer deleteNamespace() - - gitServerURL, stopGitServer := createGitServer(f) - defer stopGitServer() - - fs := memfs.New() - gitrepo, err := git.Init(memory.NewStorage(), fs) - if err != nil { - panic(err) - } - wt, err := gitrepo.Worktree() - if err != nil { - panic(err) - } - - // Create random files for the git source - err = createRandomFiles(f, fs, wt) - if err != nil { - return 0 - } - - commit, err := pushFilesToGit(gitrepo, wt, gitServerURL.String()) - if err != nil { - return 0 - } - created, err := createGitRepository(f, gitServerURL.String(), commit.String(), namespace.Name) - if err != nil { - return 0 - } - err = k8sClient.Create(context.Background(), created) - if err != nil { - return 0 - } - defer k8sClient.Delete(context.Background(), created) - - // Let the reconciler do its thing: - time.Sleep(60 * time.Millisecond) - - return 1 } // FuzzGitResourceObject implements a fuzzer that targets // the GitRepository reconciler. -func FuzzGitResourceObject(data []byte) int { - initter.Do(func() { - utilruntime.Must(ensureDependencies()) - }) - - f := fuzz.NewConsumer(data) - - // Create this early because if it fails, then the fuzzer - // does not need to proceed. - repository := &sourcev1.GitRepository{} - err := f.GenerateStruct(repository) - if err != nil { - return 0 - } +func FuzzGitResourceObject(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + initter.Do(func() { + utilruntime.Must(ensureDependencies()) + }) + + f := fuzz.NewConsumer(data) + + // Create this early because if it fails, then the fuzzer + // does not need to proceed. + repository := &sourcev1.GitRepository{} + err := f.GenerateStruct(repository) + if err != nil { + return + } - metaName, err := f.GetStringFrom(lettersNumbersAndDash, 59) - if err != nil { - return 0 - } + metaName, err := f.GetStringFrom(lettersNumbersAndDash, 59) + if err != nil { + return + } - gitServerURL, stopGitServer := createGitServer(f) - defer stopGitServer() + gitServerURL, stopGitServer := createGitServer(f) + defer stopGitServer() - fs := memfs.New() - gitrepo, err := git.Init(memory.NewStorage(), fs) - if err != nil { - return 0 - } - wt, err := gitrepo.Worktree() - if err != nil { - return 0 - } + fs := memfs.New() + gitrepo, err := git.Init(memory.NewStorage(), fs) + if err != nil { + return + } + wt, err := gitrepo.Worktree() + if err != nil { + return + } - // Add a file - ff, _ := fs.Create("fixture") - _ = ff.Close() - _, err = wt.Add(fs.Join("fixture")) - if err != nil { - return 0 - } + // Add a file + ff, _ := fs.Create("fixture") + _ = ff.Close() + _, err = wt.Add(fs.Join("fixture")) + if err != nil { + return + } - commit, err := pushFilesToGit(gitrepo, wt, gitServerURL.String()) - if err != nil { - return 0 - } + commit, err := pushFilesToGit(gitrepo, wt, gitServerURL.String()) + if err != nil { + return + } - namespace, deleteNamespace, err := createNamespace(f) - if err != nil { - return 0 - } - defer deleteNamespace() + namespace, deleteNamespace, err := createNamespace(f) + if err != nil { + return + } + defer deleteNamespace() - repository.Spec.URL = gitServerURL.String() - repository.Spec.Verification.Mode = "head" - repository.Spec.SecretRef = nil + repository.Spec.URL = gitServerURL.String() + repository.Spec.Verification.Mode = "head" + repository.Spec.SecretRef = nil - reference := &sourcev1.GitRepositoryRef{Branch: "some-branch"} - reference.Commit = strings.Replace(reference.Commit, "", commit.String(), 1) - repository.Spec.Reference = reference + reference := &sourcev1.GitRepositoryRef{Branch: "some-branch"} + reference.Commit = strings.Replace(reference.Commit, "", commit.String(), 1) + repository.Spec.Reference = reference - repository.ObjectMeta = metav1.ObjectMeta{ - Name: metaName, - Namespace: namespace.Name, - } - err = k8sClient.Create(context.Background(), repository) - if err != nil { - return 0 - } - defer k8sClient.Delete(context.Background(), repository) + repository.ObjectMeta = metav1.ObjectMeta{ + Name: metaName, + Namespace: namespace.Name, + } + err = k8sClient.Create(context.Background(), repository) + if err != nil { + return + } + defer k8sClient.Delete(context.Background(), repository) - // Let the reconciler do its thing. - time.Sleep(50 * time.Millisecond) - return 1 + // Let the reconciler do its thing. + time.Sleep(50 * time.Millisecond) + }) } func loadExampleKeys() (err error) { @@ -527,3 +389,141 @@ func createRandomFiles(f *fuzz.ConsumeFuzzer, fs billy.Filesystem, wt *git.Workt } return nil } + +func envtestBinVersion() string { + if binVersion := os.Getenv("ENVTEST_BIN_VERSION"); binVersion != "" { + return binVersion + } + return defaultBinVersion +} + +func ensureDependencies() error { + 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 { + return err + } + os.Setenv("KUBEBUILDER_ASSETS", string(assetsPath)) + } + + // Output all embedded testdata files + embedDirs := []string{"testdata/crd", "testdata/certs"} + for _, dir := range embedDirs { + err := os.MkdirAll(dir, 0o700) + if err != nil { + return fmt.Errorf("mkdir %s: %v", dir, err) + } + + templates, err := fs.ReadDir(testFiles, dir) + if err != nil { + return fmt.Errorf("reading embedded dir: %v", err) + } + + for _, template := range templates { + fileName := fmt.Sprintf("%s/%s", dir, template.Name()) + fmt.Println(fileName) + + data, err := testFiles.ReadFile(fileName) + if err != nil { + return fmt.Errorf("reading embedded file %s: %v", fileName, err) + } + + os.WriteFile(fileName, data, 0o600) + if err != nil { + return fmt.Errorf("writing %s: %v", fileName, err) + } + } + } + + startEnvServer(func(m manager.Manager) { + utilruntime.Must((&GitRepositoryReconciler{ + Client: m.GetClient(), + Storage: storage, + }).SetupWithManager(m)) + }) + + return nil +} + +func startEnvServer(setupReconcilers func(manager.Manager)) *envtest.Environment { + testEnv := &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("testdata", "crd")}, + } + fmt.Println("Starting the test environment") + cfg, err := testEnv.Start() + if err != nil { + panic(fmt.Sprintf("Failed to start the test environment manager: %v", err)) + } + + utilruntime.Must(loadExampleKeys()) + utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme)) + + tmpStoragePath, err := os.MkdirTemp("", "source-controller-storage-") + if err != nil { + panic(err) + } + defer os.RemoveAll(tmpStoragePath) + storage, err = NewStorage(tmpStoragePath, "localhost:5050", time.Minute*1, 2) + if err != nil { + panic(err) + } + // serve artifacts from the filesystem, as done in main.go + fs := http.FileServer(http.Dir(tmpStoragePath)) + http.Handle("/", fs) + go http.ListenAndServe(":5050", nil) + + cert, err := tls.X509KeyPair(examplePublicKey, examplePrivateKey) + if err != nil { + panic(err) + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(exampleCA) + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + tlsConfig.BuildNameToCertificate() + + var transport = httptransport.NewClient(&http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + }) + gitclient.InstallProtocol("https", transport) + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + if err != nil { + panic(err) + } + if k8sClient == nil { + panic("cfg is nil but should not be") + } + + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + if err != nil { + panic(err) + } + + setupReconcilers(k8sManager) + + time.Sleep(2 * time.Second) + go func() { + fmt.Println("Starting k8sManager...") + utilruntime.Must(k8sManager.Start(context.TODO())) + }() + + return testEnv +} diff --git a/tests/fuzz/Dockerfile.builder b/tests/fuzz/Dockerfile.builder index c98a6d819..a09a8e6e2 100644 --- a/tests/fuzz/Dockerfile.builder +++ b/tests/fuzz/Dockerfile.builder @@ -1,6 +1,9 @@ -FROM gcr.io/oss-fuzz-base/base-builder-go-codeintelligencetesting +FROM gcr.io/oss-fuzz-base/base-builder-go + +RUN apt-get update && apt-get install -y cmake pkg-config COPY ./ $GOPATH/src/github.com/fluxcd/source-controller/ COPY ./tests/fuzz/oss_fuzz_build.sh $SRC/build.sh +COPY tests/fuzz/compile_native_go_fuzzer /usr/local/bin/ WORKDIR $SRC diff --git a/tests/fuzz/README.md b/tests/fuzz/README.md new file mode 100644 index 000000000..2ae2cddb6 --- /dev/null +++ b/tests/fuzz/README.md @@ -0,0 +1,82 @@ +# 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]. + +### Testing locally + +Build fuzzers: + +```bash +make fuzz-build +``` +All fuzzers will be built into `./build/fuzz/out`. + +Smoke test fuzzers: + +All the fuzzers will be built and executed once, to ensure they are fully functional. + +```bash +make fuzz-smoketest +``` + +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 +``` + +### Caveats of creating oss-fuzz compatible tests + +#### Segregate fuzz tests + +OSS-Fuzz does not properly support mixed `*_test.go` files, in which there is a combination +of fuzz and non-fuzz tests. To mitigate this problem, ensure your fuzz tests are not in the +same file as other Go tests. As a pattern, call your fuzz test files `*_fuzz_test.go`. + +#### Build tags to avoid conflicts when running Go tests + +Due to the issue above, code duplication will occur when creating fuzz tests that rely on +helper functions that are shared with other tests. To avoid build issues, add a conditional +build tag at the top of the `*_fuzz_test.go` file: +```go +//go:build gofuzz_libfuzzer +// +build gofuzz_libfuzzer +``` + +The build tag above is set at [go-118-fuzz-build]. +At this point in time we can't pass on specific tags from [compile_native_go_fuzzer]. + +### Running oss-fuzz locally + +The `make fuzz-smoketest` is meant to be an easy way to reproduce errors that may occur +upstream. If our checks ever run out of sync with upstream, the upstream tests can be +executed locally with: + +``` +git clone --depth 1 https://github.com/google/oss-fuzz +cd oss-fuzz +python infra/helper.py build_image fluxcd +python infra/helper.py build_fuzzers --sanitizer address --architecture x86_64 fluxcd +python infra/helper.py check_build --sanitizer address --architecture x86_64 fluxcd +``` + +For latest info on testing oss-fuzz locally, refer to the [upstream guide]. + +[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 +[upstream guide]: https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally +[go-118-fuzz-build]: https://github.com/AdamKorcz/go-118-fuzz-build/blob/b2031950a318d4f2dcf3ec3e128f904d5cf84623/main.go#L40 +[compile_native_go_fuzzer]: https://github.com/google/oss-fuzz/blob/c2d827cb78529fdc757c9b0b4fea0f1238a54814/infra/base-images/base-builder/compile_native_go_fuzzer#L32 diff --git a/tests/fuzz/compile_native_go_fuzzer b/tests/fuzz/compile_native_go_fuzzer new file mode 100755 index 000000000..447c7477e --- /dev/null +++ b/tests/fuzz/compile_native_go_fuzzer @@ -0,0 +1,62 @@ +#!/bin/bash -eux +# Copyright 2022 Google LLC +# +# 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. +# +################################################################################ + +# This is a copy of the upstream script which is only needed to link +# additional static libraries. Orignal source: +# +# https://github.com/google/oss-fuzz/blob/9e8dd47cb902545efc60a5580126adc36d70bae3/infra/base-images/base-builder/compile_native_go_fuzzer + +function build_native_go_fuzzer() { + fuzzer=$1 + function=$2 + path=$3 + tags="-tags gofuzz" + + if [[ $SANITIZER == *coverage* ]]; then + current_dir=$(pwd) + mkdir $OUT/rawfuzzers || true + cd $abs_file_dir + go test -c -run $fuzzer -o $OUT/$fuzzer -cover + cp "${fuzzer_filename}" "${OUT}/rawfuzzers/${fuzzer}" + cd $current_dir + else + go-118-fuzz-build -o $fuzzer.a -func $function $abs_file_dir + # TODO: upstream support for linking $ADDITIONAL_LIBS + $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $fuzzer.a -o $OUT/$fuzzer \ + $ADDITIONAL_LIBS + fi +} + + +path=$1 +function=$2 +fuzzer=$3 +tags="-tags gofuzz" + +# Get absolute path. +abs_file_dir=$(go list $tags -f {{.Dir}} $path) + +# TODO(adamkorcz): Get rid of "-r" flag here. +fuzzer_filename=$(grep -r -l --include='*.go' -s "$function" "${abs_file_dir}") + +# Test if file contains a line with "func $function" and "testing.F". +if [ $(grep -r "func $function" $fuzzer_filename | grep "testing.F" | wc -l) -eq 1 ] +then + build_native_go_fuzzer $fuzzer $function $abs_file_dir +else + echo "Could not find the function: func ${function}(f *testing.F)" +fi diff --git a/tests/fuzz/go.mod b/tests/fuzz/go.mod deleted file mode 100644 index c018fb37b..000000000 --- a/tests/fuzz/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module github.com/fluxcd/source-controller/tests/fuzz - -go 1.18 - -replace github.com/fluxcd/source-controller/api => ../../api - -replace github.com/fluxcd/source-controller => ../../ diff --git a/tests/fuzz/native_go_run.sh b/tests/fuzz/native_go_run.sh new file mode 100755 index 000000000..a62410273 --- /dev/null +++ b/tests/fuzz/native_go_run.sh @@ -0,0 +1,39 @@ +#!/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 + +# This script iterates through all go fuzzing targets, running each one +# through the period of time established by FUZZ_TIME. + +FUZZ_TIME=${FUZZ_TIME:-"5s"} + +# kustomization_fuzzer_test is not fully compatible with Go native fuzz, +# so it is ignored here. +test_files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' . | \ + grep -v "controllers_fuzzer_test.go") + +for file in ${test_files} +do + targets=$(grep -oP 'func \K(Fuzz\w*)' "${file}") + for target_name in ${targets} + do + echo "Running ${file}.${target_name} for ${FUZZ_TIME}." + file_dir=$(dirname "${file}") + + go test -fuzz="^${target_name}\$" -fuzztime "${FUZZ_TIME}" "${file_dir}" + done +done diff --git a/tests/fuzz/oss_fuzz_build.sh b/tests/fuzz/oss_fuzz_build.sh index 8bc1d2542..45c2e2785 100755 --- a/tests/fuzz/oss_fuzz_build.sh +++ b/tests/fuzz/oss_fuzz_build.sh @@ -16,93 +16,65 @@ set -euxo pipefail -LIBGIT2_TAG="${LIBGIT2_TAG:-v0.4.0}" +# This file aims for: +# - Dynamically discover and build all fuzz tests within the repository. +# - Work for both local make fuzz-smoketest and the upstream oss-fuzz. + GOPATH="${GOPATH:-/root/go}" GO_SRC="${GOPATH}/src" PROJECT_PATH="github.com/fluxcd/source-controller" -pushd "${GO_SRC}/${PROJECT_PATH}" - -export TARGET_DIR="$(/bin/pwd)/build/libgit2/${LIBGIT2_TAG}" - -# For most cases, libgit2 will already be present. -# The exception being at the oss-fuzz integration. -if [ ! -d "${TARGET_DIR}" ]; then - curl -o output.tar.gz -LO "https://github.com/fluxcd/golang-with-libgit2/releases/download/${LIBGIT2_TAG}/linux-x86_64-libgit2-only.tar.gz" - - DIR=linux-libgit2-only - NEW_DIR="$(/bin/pwd)/build/libgit2/${LIBGIT2_TAG}" - INSTALLED_DIR="/home/runner/work/golang-with-libgit2/golang-with-libgit2/build/${DIR}" +# install_deps installs all dependencies needed for upstream oss-fuzz. +# Unfortunately we can't pin versions here, as we want to always +# have the latest, so that we can reproduce errors occuring upstream. +install_deps(){ + if ! command -v go-118-fuzz-build &> /dev/null; then + go install github.com/AdamKorcz/go-118-fuzz-build@latest + fi +} - mkdir -p ./build/libgit2 +install_deps - tar -xf output.tar.gz - rm output.tar.gz - mv "${DIR}" "${LIBGIT2_TAG}" - mv "${LIBGIT2_TAG}/" "./build/libgit2" +cd "${GO_SRC}/${PROJECT_PATH}" - # Update the prefix paths included in the .pc files. - # This will make it easier to update to the location in which they will be used. - find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {} +# Ensure any project-specific requirements are catered for ahead of +# the generic build process. +if [ -f "tests/fuzz/oss_fuzz_prebuild.sh" ]; then + . tests/fuzz/oss_fuzz_prebuild.sh fi -apt-get update && apt-get install -y pkg-config - -export CGO_ENABLED=1 -export PKG_CONFIG_PATH="${TARGET_DIR}/lib/pkgconfig" -export CGO_LDFLAGS="$(pkg-config --libs --static --cflags libgit2)" -export LIBRARY_PATH="${TARGET_DIR}/lib" -export CGO_CFLAGS="-I${TARGET_DIR}/include" - -go get -d github.com/AdaLogics/go-fuzz-headers - -# The implementation of libgit2 is sensitive to the versions of git2go. -# Leaving it to its own devices, the minimum version of git2go used may not -# be compatible with the currently implemented version. Hence the modifications -# of the existing go.mod. -sed "s;\./api;$(/bin/pwd)/api;g" go.mod > tests/fuzz/go.mod -sed -i 's;module github.com/fluxcd/source-controller;module github.com/fluxcd/source-controller/tests/fuzz;g' tests/fuzz/go.mod -echo "replace github.com/fluxcd/source-controller => $(/bin/pwd)/" >> tests/fuzz/go.mod - -cp go.sum tests/fuzz/go.sum - -pushd "tests/fuzz" - -go mod download - -go get -d github.com/AdaLogics/go-fuzz-headers -go get -d github.com/fluxcd/source-controller - -# Setup files to be embedded into controllers_fuzzer.go's testFiles variable. -mkdir -p testdata/crd -cp ../../config/crd/bases/*.yaml testdata/crd/ -cp -r ../../controllers/testdata/certs testdata/ - -go get -d github.com/AdaLogics/go-fuzz-headers - -# Using compile_go_fuzzer to compile fails when statically linking libgit2 dependencies -# via CFLAGS/CXXFLAGS. -function go_compile(){ - function=$1 - fuzzer=$2 - - if [[ $SANITIZER = *coverage* ]]; then - # ref: https://github.com/google/oss-fuzz/blob/master/infra/base-images/base-builder/compile_go_fuzzer - compile_go_fuzzer "${PROJECT_PATH}/tests/fuzz" "${function}" "${fuzzer}" - else - go-fuzz -tags gofuzz -func="${function}" -o "${fuzzer}.a" . - ${CXX} ${CXXFLAGS} ${LIB_FUZZING_ENGINE} -o "${OUT}/${fuzzer}" \ - "${fuzzer}.a" "${TARGET_DIR}/lib/libgit2.a" \ - -fsanitize="${SANITIZER}" - fi -} - -go_compile FuzzRandomGitFiles fuzz_gitrepository_fuzzer -go_compile FuzzGitResourceObject fuzz_git_resource_object - -# By now testdata is embedded in the binaries and no longer needed. -# Remove the dir given that it will be owned by root otherwise. -rm -rf testdata/ - -popd -popd +modules=$(find . -mindepth 1 -maxdepth 4 -type f -name 'go.mod' | cut -c 3- | sed 's|/[^/]*$$||' | sort -u | sed 's;/go.mod;;g' | sed 's;go.mod;.;g') + +for module in ${modules}; do + + cd "${GO_SRC}/${PROJECT_PATH}/${module}" + + test_files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' . || echo "") + if [ -z "${test_files}" ]; then + continue + fi + + go get github.com/AdamKorcz/go-118-fuzz-build/testing + + # Iterate through all Go Fuzz targets, compiling each into a fuzzer. + for file in ${test_files}; do + # If the subdir is a module, skip this file, as it will be handled + # at the next iteration of the outer loop. + if [ -f "$(dirname "${file}")/go.mod" ]; then + continue + fi + + targets=$(grep -oP 'func \K(Fuzz\w*)' "${file}") + for target_name in ${targets}; do + # Transform module path into module name (e.g. git/libgit2 to git_libgit2). + module_name="$(echo ${module} | tr / _)_" + # Compose fuzzer name based on the lowercase version of the func names. + # The module name is added after the fuzz prefix, for better discoverability. + fuzzer_name=$(echo "${target_name}" | tr '[:upper:]' '[:lower:]' | sed "s;fuzz_;fuzz_${module_name//._/};g") + target_dir=$(dirname "${file}") + + echo "Building ${file}.${target_name} into ${fuzzer_name}" + compile_native_go_fuzzer "${target_dir}" "${target_name}" "${fuzzer_name}" + done + done +done diff --git a/tests/fuzz/oss_fuzz_prebuild.sh b/tests/fuzz/oss_fuzz_prebuild.sh new file mode 100755 index 000000000..29cd7d615 --- /dev/null +++ b/tests/fuzz/oss_fuzz_prebuild.sh @@ -0,0 +1,65 @@ +#!/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 + +# This file is executed by upstream oss-fuzz for any requirements that +# are specific for building this project. + +# Some tests requires embedded resources. Embedding does not allow +# for traversing into ascending dirs, therefore we copy those contents here: +mkdir -p controllers/testdata/crd +cp config/crd/bases/*.yaml controllers/testdata/crd/ + +# libgit2, cmake and pkg-config are requirements to support libgit2. +LIBGIT2_TAG="${LIBGIT2_TAG:-v0.4.0}" + +# Avoid updating apt get and installing dependencies, if they are already in place. +if (! command -v cmake &> /dev/null) || (! command -v pkg-config &> /dev/null) then + apt-get update && apt-get install -y cmake pkg-config +fi + +export TARGET_DIR="$(/bin/pwd)/build/libgit2/${LIBGIT2_TAG}" + +# For most cases, libgit2 will already be present. +# The exception being at the oss-fuzz integration. +if [ ! -d "${TARGET_DIR}" ]; then + curl --connect-timeout 2 --retry 3 --retry-delay 1 --retry-max-time 30 \ + -o output.tar.gz -LO "https://github.com/fluxcd/golang-with-libgit2/releases/download/${LIBGIT2_TAG}/linux-$(uname -m)-libgit2-only.tar.gz" + + DIR=linux-libgit2-only + NEW_DIR="$(/bin/pwd)/build/libgit2/${LIBGIT2_TAG}" + INSTALLED_DIR="/home/runner/work/golang-with-libgit2/golang-with-libgit2/build/${DIR}" + + mkdir -p ./build/libgit2 + + tar -xf output.tar.gz + rm output.tar.gz + mv "${DIR}" "${LIBGIT2_TAG}" + mv "${LIBGIT2_TAG}/" "./build/libgit2" + + # Update the prefix paths included in the .pc files. + # This will make it easier to update to the location in which they will be used. + find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {} +fi + +export CGO_ENABLED=1 +export LIBRARY_PATH="${TARGET_DIR}/lib" +export PKG_CONFIG_PATH="${TARGET_DIR}/lib/pkgconfig" +export CGO_CFLAGS="-I${TARGET_DIR}/include" +export CGO_LDFLAGS="$(pkg-config --libs --static --cflags libgit2)" + +export ADDITIONAL_LIBS="${TARGET_DIR}/lib/libgit2.a" From c5ded6f5492a807516a8b6d8e0572e699a4142b3 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Thu, 24 Nov 2022 09:55:17 +0000 Subject: [PATCH 3/3] build: Update attributions Signed-off-by: Paulo Gomes --- ATTRIBUTIONS.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md index 696ab9fa4..37172b257 100644 --- a/ATTRIBUTIONS.md +++ b/ATTRIBUTIONS.md @@ -1199,3 +1199,34 @@ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- + +The built-in git_fs_path_basename_r() function is based on the +Android implementation, BSD licensed: + +Copyright (C) 2008 The Android Open Source Project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE.