Skip to content

Commit

Permalink
fuzz: Refactor Fuzzers based on Go native fuzzing
Browse files Browse the repository at this point in the history
The existing fuzzers were converted into the Go native format.
Based on how the code was structured on this project, the fuzzers
can be quite effective, allowing for entire E2E fuzzing in some
cases, but with very low execution cost.

Signed-off-by: Paulo Gomes <paulo.gomes@weave.works>
  • Loading branch information
Paulo Gomes committed Sep 6, 2022
1 parent 02d8d10 commit c2978c4
Show file tree
Hide file tree
Showing 39 changed files with 770 additions and 976 deletions.
14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ BUILD_PLATFORMS ?= linux/amd64
# Architecture to use envtest with
ENVTEST_ARCH ?= amd64

# FUZZ_TIME defines the max amount of time, in Go Duration,
# each fuzzer should run for.
FUZZ_TIME ?= 1m

all: manager

# Run tests
Expand Down Expand Up @@ -109,7 +113,7 @@ docker-push:
docker-deploy:
kubectl -n flux-system set image deployment/notification-controller manager=${IMG}

# Build fuzzers
# Build fuzzers used by oss-fuzz.
fuzz-build:
rm -rf $(shell pwd)/build/fuzz/
mkdir -p $(shell pwd)/build/fuzz/out/
Expand All @@ -122,14 +126,20 @@ fuzz-build:
-v "$(shell pwd)/build/fuzz/out":/out \
local-fuzzing:latest

# Run each fuzzer once to ensure they are working
# Run each fuzzer once to ensure they will work when executed by oss-fuzz.
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"

# 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

# Find or download controller-gen
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
.PHONY: controller-gen
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.18
replace github.com/fluxcd/notification-controller/api => ./api

require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20220903154154-e8044f6e4c72
github.com/Azure/azure-amqp-common-go/v3 v3.2.3
github.com/Azure/azure-event-hubs-go/v3 v3.3.18
github.com/containrrr/shoutrrr v0.6.1
Expand Down Expand Up @@ -68,6 +69,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/devigned/tab v0.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20220903154154-e8044f6e4c72 h1:1sCHCT0xRr7UArrI1WJxsl9S8QeYdf0fmuGIl2xb7YI=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20220903154154-e8044f6e4c72/go.mod h1:i9fr2JpcEcY/IHEvzCM3qXUZYOQHgR89dt4es1CgMhc=
github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk=
github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas=
github.com/Azure/azure-event-hubs-go/v3 v3.3.18 h1:jgWDk2qmknA0UsfyzjHiW5yciOw3aBY0Oq9p/M9lz2Q=
Expand Down Expand Up @@ -169,6 +171,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -450,6 +454,7 @@ 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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand Down Expand Up @@ -1151,6 +1156,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
Expand Down
39 changes: 39 additions & 0 deletions internal/notifier/alertmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import (
"net/http/httptest"
"testing"

"github.com/fluxcd/pkg/runtime/events"
"github.com/stretchr/testify/require"

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

func TestAlertmanager_Post(t *testing.T) {
Expand All @@ -44,3 +47,39 @@ func TestAlertmanager_Post(t *testing.T) {
err = alertmanager.Post(context.TODO(), testEvent())
require.NoError(t, err)
}

func Fuzz_AlertManager(f *testing.F) {
f.Add("update", "", []byte{}, []byte("{}"))
f.Add("something", "else", []byte{}, []byte(""))

f.Fuzz(func(t *testing.T,
commitStatus, summary string, seed, response []byte) {

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.Copy(io.Discard, r.Body)
w.Write(response)
r.Body.Close()
}))
defer ts.Close()

alertmanager, err := NewAlertmanager(ts.URL, "", nil)
if err != nil {
return
}

event := events.Event{}

// Try to fuzz the event object, but if it fails (not enough seed),
// ignore it, as other inputs are also being used in this test.
_ = fuzz.NewConsumer(seed).GenerateStruct(&event)

if event.Metadata == nil && (commitStatus != "" || summary != "") {
event.Metadata = map[string]string{
"commit_status": commitStatus,
"summary": summary,
}
}

_ = alertmanager.Post(context.TODO(), event)
})
}
57 changes: 57 additions & 0 deletions internal/notifier/azure_devops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,20 @@ limitations under the License.
package notifier

import (
"context"
"crypto/x509"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/fluxcd/pkg/runtime/events"
"github.com/microsoft/azure-devops-go-api/azuredevops/v6/git"
"github.com/stretchr/testify/assert"

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

func TestNewAzureDevOpsBasic(t *testing.T) {
Expand Down Expand Up @@ -60,6 +70,53 @@ func TestDuplicateAzureDevOpsStatus(t *testing.T) {
}
}

const apiLocations = `{"count":0,"value":[{"area":"","id":"428dd4fb-fda5-4722-af02-9313b80305da","routeTemplate":"","resourceName":"","maxVersion":"6.0","minVersion":"5.0","releasedVersion":"6.0"}]}`

func FuzzAzureDevOps(f *testing.F) {
f.Add("alakazam", "org/proj/_git/repo", "revision/dsa123a", "error", "", []byte{}, []byte(`{"count":1,"value":[{"state":"error","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
f.Add("alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":1,"value":[{"state":"info","description":"","context":{"genre":"fluxcd","name":"/"}}]}`))
f.Add("alakazam", "org/proj/_git/repo", "revision/dsa123a", "info", "", []byte{}, []byte(`{"count":0,"value":[]}`))
f.Add("alakazam", "org/proj/_git/repo", "", "", "Progressing", []byte{}, []byte{})

f.Fuzz(func(t *testing.T,
token, urlSuffix, revision, severity, reason string, seed, response []byte) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "_apis") {
w.Write([]byte(apiLocations))
} else {
w.Write(response)
}

io.Copy(io.Discard, r.Body)
r.Body.Close()
}))
defer ts.Close()

var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)

azureDevOps, err := NewAzureDevOps(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
if err != nil {
return
}

event := events.Event{}

// Try to fuzz the event object, but if it fails (not enough seed),
// ignore it, as other inputs are also being used in this test.
_ = fuzz.NewConsumer(seed).GenerateStruct(&event)

if event.Metadata == nil && (revision != "") {
event.Metadata = map[string]string{
"revision": revision,
}
}
event.Severity = severity

_ = azureDevOps.Post(context.TODO(), event)
})
}

func azStatus(state git.GitStatusState, context string, description string) *git.GitStatus {
genre := "fluxcd"
return &git.GitStatus{
Expand Down
55 changes: 55 additions & 0 deletions internal/notifier/bitbucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,17 @@ limitations under the License.
package notifier

import (
"context"
"crypto/x509"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"

fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/fluxcd/pkg/runtime/events"
"github.com/stretchr/testify/assert"
)

Expand All @@ -38,3 +47,49 @@ func TestNewBitbucketInvalidToken(t *testing.T) {
_, err := NewBitbucket("https://bitbucket.org/foo/bar", "bar", nil)
assert.NotNil(t, err)
}

func FuzzBitbucket(f *testing.F) {
f.Add("user:pass", "org/repo", "revision/dsa123a", "info", []byte{}, []byte("{}"))
f.Add("user:pass", "org/repo", "revision/dsa123a", "error", []byte{}, []byte("{}"))

f.Fuzz(func(t *testing.T,
token, urlSuffix, revision, severity string, seed, response []byte) {

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.Copy(io.Discard, r.Body)
w.Write(response)
r.Body.Close()
}))
defer ts.Close()

var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)

bitbucket, err := NewBitbucket(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), token, &cert)
if err != nil {
return
}

apiUrl, err := url.Parse(ts.URL)
if err != nil {
t.Fatalf("cannot parse api base URL: %v", err)
}
// Ensure the call does not go to bitbucket and fuzzes the response.
bitbucket.Client.SetApiBaseURL(*apiUrl)

event := events.Event{}

// Try to fuzz the event object, but if it fails (not enough seed),
// ignore it, as other inputs are also being used in this test.
_ = fuzz.NewConsumer(seed).GenerateStruct(&event)

if event.Metadata == nil && (revision != "") {
event.Metadata = map[string]string{
"revision": revision,
}
}
event.Severity = severity

_ = bitbucket.Post(context.TODO(), event)
})
}
39 changes: 39 additions & 0 deletions internal/notifier/discord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ package notifier
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/fluxcd/pkg/runtime/events"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand All @@ -49,3 +52,39 @@ func TestDiscord_Post(t *testing.T) {
err = discord.Post(context.TODO(), testEvent())
require.NoError(t, err)
}

func FuzzDiscord(f *testing.F) {
f.Add("username", "channel", "/slack", "info", "update", []byte{}, []byte("{}"))
f.Add("", "channel", "", "error", "", []byte{}, []byte(""))

f.Fuzz(func(t *testing.T,
username, channel, urlSuffix, severity, commitStatus string, seed, response []byte) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.Copy(io.Discard, r.Body)
w.Write(response)
r.Body.Close()
}))
defer ts.Close()

targetURL := fmt.Sprintf("%s/%s", ts.URL, urlSuffix)
discord, err := NewDiscord(targetURL, targetURL, username, channel)
if err != nil {
return
}

event := events.Event{}

// Try to fuzz the event object, but if it fails (not enough seed),
// ignore it, as other inputs are also being used in this test.
_ = fuzz.NewConsumer(seed).GenerateStruct(&event)

if event.Metadata == nil {
event.Metadata = map[string]string{}
}

event.Metadata["commit_status"] = commitStatus
event.Severity = severity

_ = discord.Post(context.TODO(), event)
})
}
33 changes: 33 additions & 0 deletions internal/notifier/forwarder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ package notifier

import (
"context"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"

fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/fluxcd/pkg/runtime/events"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -52,3 +55,33 @@ func TestForwarder_Post(t *testing.T) {
err = forwarder.Post(context.TODO(), testEvent())
require.NoError(t, err)
}

func FuzzForwarder(f *testing.F) {
f.Add("", []byte{}, []byte{})

f.Fuzz(func(t *testing.T,
urlSuffix string, seed, response []byte) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(response)
io.Copy(io.Discard, r.Body)
r.Body.Close()
}))
defer ts.Close()

var cert x509.CertPool
_ = fuzz.NewConsumer(seed).GenerateStruct(&cert)

header := make(map[string]string)
_ = fuzz.NewConsumer(seed).FuzzMap(&header)

forwarder, err := NewForwarder(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", header, &cert)
if err != nil {
return
}

event := events.Event{}
_ = fuzz.NewConsumer(seed).GenerateStruct(&event)

_ = forwarder.Post(context.TODO(), event)
})
}
Loading

0 comments on commit c2978c4

Please sign in to comment.