Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fuzz: Refactor Fuzzers based on Go native fuzzing #414

Merged
merged 1 commit into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
44 changes: 44 additions & 0 deletions internal/notifier/alertmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ package notifier

import (
"context"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
"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 +49,42 @@ 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, urlSuffix, summary 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)

alertmanager, err := NewAlertmanager(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", &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 && (commitStatus != "" || summary != "") {
event.Metadata = map[string]string{
"commit_status": commitStatus,
"summary": summary,
}
}

_ = alertmanager.Post(context.TODO(), event)
})
}
9 changes: 9 additions & 0 deletions internal/notifier/azure_devops.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,16 @@ func toAzureDevOpsState(severity string) (git.GitStatusState, error) {
// duplicateStatus return true if the latest status
// with a matching context has the same state and description
func duplicateAzureDevOpsStatus(statuses *[]git.GitStatus, status *git.GitStatus) bool {
if status == nil || status.Context == nil || statuses == nil {
return false
}

for _, s := range *statuses {
if s.Context == nil || s.Context.Name == nil || s.Context.Genre == nil ||
s.State == nil || s.Description == nil {
continue
}

if *s.Context.Name == *status.Context.Name && *s.Context.Genre == *status.Context.Genre {
if *s.State == *status.State && *s.Description == *status.Description {
return true
Expand Down
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 Fuzz_AzureDevOps(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 Fuzz_Bitbucket(f *testing.F) {
f.Add("user:pass", "org/repo", "revision/dsa123a", "info", []byte{}, []byte(`{"state":"SUCCESSFUL","description":"","key":"","name":"","url":""}`))
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)
})
}
37 changes: 37 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,37 @@ func TestDiscord_Post(t *testing.T) {
err = discord.Post(context.TODO(), testEvent())
require.NoError(t, err)
}

func Fuzz_Discord(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()

discord, err := NewDiscord(fmt.Sprintf("%s/%s", ts.URL, urlSuffix), "", 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)
})
}
Loading