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 <paulo.gomes@weave.works>
This commit is contained in:
Paulo Gomes 2022-11-24 09:55:09 +00:00
parent a970fd774a
commit 04dd4b54fb
No known key found for this signature in database
GPG Key ID: 9995233870E99BEE
9 changed files with 569 additions and 354 deletions

View File

@ -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 ./...

View File

@ -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
}
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((&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
}
// FuzzRandomGitFiles implements a fuzzer that
// targets the GitRepository reconciler.
func FuzzRandomGitFiles(data []byte) int {
initter.Do(func() {
utilruntime.Must(ensureDependencies())
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()
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
}
commit, err := pushFilesToGit(gitrepo, wt, gitServerURL.String())
if err != nil {
return
}
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)
// 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())
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
}
gitServerURL, stopGitServer := createGitServer(f)
defer stopGitServer()
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
}
commit, err := pushFilesToGit(gitrepo, wt, gitServerURL.String())
if err != nil {
return
}
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
reference := &sourcev1.GitRepositoryRef{Branch: "some-branch"}
reference.Commit = strings.Replace(reference.Commit, "<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
}
defer k8sClient.Delete(context.Background(), repository)
// Let the reconciler do its thing.
time.Sleep(50 * time.Millisecond)
})
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
}
metaName, err := f.GetStringFrom(lettersNumbersAndDash, 59)
if err != nil {
return 0
}
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
}
// Add a file
ff, _ := fs.Create("fixture")
_ = ff.Close()
_, err = wt.Add(fs.Join("fixture"))
if err != nil {
return 0
}
commit, err := pushFilesToGit(gitrepo, wt, gitServerURL.String())
if err != nil {
return 0
}
namespace, deleteNamespace, err := createNamespace(f)
if err != nil {
return 0
}
defer deleteNamespace()
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>", 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)
// Let the reconciler do its thing.
time.Sleep(50 * time.Millisecond)
return 1
}
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
}

View File

@ -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

82
tests/fuzz/README.md Normal file
View File

@ -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

View File

@ -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

View File

@ -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 => ../../

39
tests/fuzz/native_go_run.sh Executable file
View File

@ -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

View File

@ -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}"
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
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
# 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
}
go_compile FuzzRandomGitFiles fuzz_gitrepository_fuzzer
go_compile FuzzGitResourceObject fuzz_git_resource_object
install_deps
# 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/
cd "${GO_SRC}/${PROJECT_PATH}"
popd
popd
# 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
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

65
tests/fuzz/oss_fuzz_prebuild.sh Executable file
View File

@ -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"