Skip to content

Commit

Permalink
Merge pull request #396 from pjbgf/fuzz
Browse files Browse the repository at this point in the history
Refactor Fuzz implementation
  • Loading branch information
stefanprodan authored Jan 19, 2022
2 parents 0246b7d + 5572b8a commit 836f84c
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 4 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/cifuzz.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CIFuzz
on:
pull_request:
branches:
- main
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Restore Go cache
uses: actions/cache@v1
with:
path: /home/runner/work/_temp/_github_home/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Smoke test Fuzzers
run: make fuzz-smoketest
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*.so
*.dylib
bin
testbin

# Test binary, build with `go test -c`
*.test
Expand All @@ -22,3 +21,5 @@ testbin
*.swp
*.swo
*~

build/
22 changes: 21 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ GEN_CRD_API_REFERENCE_DOCS = $(shell pwd)/bin/gen-crd-api-reference-docs
gen-crd-api-reference-docs:
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/[email protected])

ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
ENVTEST_ASSETS_DIR=$(shell pwd)/build/testbin
ENVTEST_KUBERNETES_VERSION?=latest
install-envtest: setup-envtest
mkdir -p ${ENVTEST_ASSETS_DIR}
Expand All @@ -137,3 +137,23 @@ GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
rm -rf $$TMP_DIR ;\
}
endef

# Build fuzzers
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 \
-e FUZZING_LANGUAGE=go -e SANITIZER=address \
-e CIFUZZ_DEBUG='True' -e OSS_FUZZ_PROJECT_NAME=fluxcd \
-v "$(shell pwd)/build/fuzz/out":/out \
local-fuzzing:latest

# Run each fuzzer once to ensure they are working
fuzz-smoketest: fuzz-build
docker run --rm \
-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"
6 changes: 4 additions & 2 deletions api/v2beta1/helmrelease_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,8 +820,10 @@ func (in HelmReleaseStatus) GetHelmChart() (string, string) {
if in.HelmChart == "" {
return "", ""
}
split := strings.Split(in.HelmChart, string(types.Separator))
return split[0], split[1]
if split := strings.Split(in.HelmChart, string(types.Separator)); len(split) > 1 {
return split[0], split[1]
}
return "", ""
}

// HelmReleaseProgressing resets any failures and registers progress toward
Expand Down
6 changes: 6 additions & 0 deletions tests/fuzz/Dockerfile.builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM gcr.io/oss-fuzz-base/base-builder-go

COPY ./ $GOPATH/src/github.com/fluxcd/helm-controller/
COPY ./tests/fuzz/oss_fuzz_build.sh $SRC/build.sh

WORKDIR $SRC
45 changes: 45 additions & 0 deletions tests/fuzz/README.md
Original file line number Diff line number Diff line change
@@ -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
139 changes: 139 additions & 0 deletions tests/fuzz/fuzz_controllers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//go:build gofuzz
// +build gofuzz

/*
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.
*/
package controllers

import (
"context"
"sync"

v2 "github.com/fluxcd/helm-controller/api/v2beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

fuzz "github.com/AdaLogics/go-fuzz-headers"
)

var (
doOnce sync.Once
scheme = runtime.NewScheme()
)

// An init function that is invoked by way of sync.Do
func initFunc() {
_ = corev1.AddToScheme(scheme)
_ = v2.AddToScheme(scheme)
_ = sourcev1.AddToScheme(scheme)
}

// FuzzHelmreleaseComposeValues implements a fuzzer
// that targets HelmReleaseReconciler.composeValues().
func FuzzHelmreleaseComposeValues(data []byte) int {
doOnce.Do(initFunc)

f := fuzz.NewConsumer(data)

resources, err := getResources(f)
if err != nil {
return 0
}

c := fake.NewFakeClientWithScheme(scheme, resources...)
r := &HelmReleaseReconciler{Client: c}

hr := v2.HelmRelease{}
err = f.GenerateStruct(&hr)
if err != nil {
return 0
}

_, _ = r.composeValues(logr.NewContext(context.TODO(), logr.Discard()), hr)

return 1
}

// FuzzHelmreleaseComposeValues implements a fuzzer
// that targets HelmReleaseReconciler.reconcile().
func FuzzHelmreleaseReconcile(data []byte) int {
doOnce.Do(initFunc)

f := fuzz.NewConsumer(data)

resources, err := getResources(f)
if err != nil {
return 0
}

hr := v2.HelmRelease{}
err = f.GenerateStruct(&hr)
if err != nil {
return 0
}

hc := sourcev1.HelmChart{}
err = f.GenerateStruct(&hc)
if err != nil {
return 0
}

hc.ObjectMeta.Name = hr.GetHelmChartName()
hc.ObjectMeta.Namespace = hr.Spec.Chart.GetNamespace(hr.Namespace)
resources = append(resources, &hc)

c := fake.NewFakeClientWithScheme(scheme, resources...)
r := &HelmReleaseReconciler{Client: c}

_, _, _ = r.reconcile(logr.NewContext(context.TODO(), logr.Discard()), hr)

return 1
}

func getResources(f *fuzz.ConsumeFuzzer) ([]runtime.Object, error) {
resources := make([]runtime.Object, 0)

name, err := f.GetString()
if err != nil {
return nil, err
}

if createSecret, _ := f.GetBool(); createSecret {
inputByte := make(map[string][]byte)
f.FuzzMap(&inputByte) // ignore error, as empty is still valid
resources = append(resources,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: name},
Data: inputByte,
})
}

if createConfigMap, _ := f.GetBool(); createConfigMap {
inputString := make(map[string]string)
f.FuzzMap(&inputString) // ignore error, as empty is still valid
resources = append(resources,
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: name},
Data: inputString,
})
}

return resources, nil
}
5 changes: 5 additions & 0 deletions tests/fuzz/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/fluxcd/helm-controller/tests/fuzz
// This module is used only to avoid polluting the main module
// with fuzz dependencies.

go 1.17
37 changes: 37 additions & 0 deletions tests/fuzz/oss_fuzz_build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/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/helm-controller"

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/fuzz_controllers.go" "${PROJECT_PATH}/controllers/"


# compile fuzz tests for the runtime module
pushd "${PROJECT_PATH}"

go mod tidy
compile_go_fuzzer "${PROJECT_PATH}/controllers/" FuzzHelmreleaseComposeValues fuzz_helmrelease_composevalues
compile_go_fuzzer "${PROJECT_PATH}/controllers/" FuzzHelmreleaseReconcile fuzz_helmrelease_reconcile

popd
20 changes: 20 additions & 0 deletions tests/fuzz/oss_fuzz_run.sh
Original file line number Diff line number Diff line change
@@ -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 echo {} -runs=1 \; | bash -e

0 comments on commit 836f84c

Please sign in to comment.