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

pkg/util: Add ClusterfuzzLite #263

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM gcr.io/oss-fuzz-base/base-builder-go:v1
RUN git clone --depth 1 https://github.com/containrrr/shoutrrr
COPY . $SRC/shoutrrr
WORKDIR $SRC/shoutrrr
COPY ./.clusterfuzzlite/build.sh $SRC/
3 changes: 3 additions & 0 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/binsh

compile_go_fuzzer github.com/containrrr/shoutrrr/fuzz FuzzPartitionMessage fuzz_partition_message
1 change: 1 addition & 0 deletions .clusterfuzzlite/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: go
3 changes: 3 additions & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
exclude_paths:
- "fuzz/*"
29 changes: 29 additions & 0 deletions .github/workflows/cflite_pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: ClusterFuzzLite PR fuzzing
on:
workflow_dispatch:
pull_request:
paths:
- '**'
permissions: read-all
jobs:
PR:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sanitizer: [address]
steps:
- name: Build Fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
with:
sanitizer: ${{ matrix.sanitizer }}
language: go
- name: Run Fuzzers (${{ matrix.sanitizer }})
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 400
mode: 'code-change'
sanitizer: ${{ matrix.sanitizer }}
33 changes: 33 additions & 0 deletions fuzz/partition_message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build gofuzz
// +build gofuzz

package fuzz

import (
fuzz "github.com/AdaLogics/go-fuzz-headers"
t "github.com/containrrr/shoutrrr/pkg/types"
"github.com/containrrr/shoutrrr/pkg/util"
)

// FuzzPartitionMessage fuzzes the util.PartitionMessage function
func FuzzPartitionMessage(data []byte) int {
f := fuzz.NewConsumer(data)

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

limits := t.MessageLimit{}
err = f.GenerateStruct(&limits)
if err != nil {
return 0
}

distance, err := f.GetInt()
if err != nil {
return 0
}
_, _ = util.PartitionMessage(input, limits, distance)
return 1
}
6 changes: 3 additions & 3 deletions pkg/types/message_limit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package types

// MessageLimit is used for declaring the payload limits for services upstream APIs
type MessageLimit struct {
ChunkSize int
TotalChunkSize int
ChunkSize uint
TotalChunkSize uint

// Maximum number of chunks (including the last chunk for meta data)
ChunkCount int
ChunkCount uint
}
12 changes: 6 additions & 6 deletions pkg/util/partition_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const ellipsis = " [...]"
func PartitionMessage(input string, limits t.MessageLimit, distance int) (items []t.MessageItem, omitted int) {
runes := []rune(input)
chunkOffset := 0
maxTotal := Min(len(runes), limits.TotalChunkSize)
maxCount := limits.ChunkCount - 1
maxTotal := Min(len(runes), int(limits.TotalChunkSize))
maxCount := int(limits.ChunkCount) - 1

if len(input) == 0 {
// If the message is empty, return an empty array
Expand All @@ -26,7 +26,7 @@ func PartitionMessage(input string, limits t.MessageLimit, distance int) (items

for i := 0; i < maxCount; i++ {
// If no suitable split point is found, use the chunkSize
chunkEnd := chunkOffset + limits.ChunkSize
chunkEnd := chunkOffset + int(limits.ChunkSize)
// ... and start next chunk directly after this one
nextChunkStart := chunkEnd
if chunkEnd >= maxTotal {
Expand Down Expand Up @@ -69,7 +69,7 @@ func Ellipsis(text string, maxLength int) string {

// MessageItemsFromLines creates a set of MessageItems that is compatible with the supplied limits
func MessageItemsFromLines(plain string, limits t.MessageLimit) (batches [][]t.MessageItem) {
maxCount := limits.ChunkCount
maxCount := int(limits.ChunkCount)

lines := strings.Split(plain, "\n")
batches = make([][]t.MessageItem, 0)
Expand All @@ -78,9 +78,9 @@ func MessageItemsFromLines(plain string, limits t.MessageLimit) (batches [][]t.M
totalLength := 0
for _, line := range lines {

maxLen := limits.ChunkSize
maxLen := int(limits.ChunkSize)

if len(items) == maxCount || totalLength+maxLen > limits.TotalChunkSize {
if len(items) == maxCount || totalLength+maxLen > int(limits.TotalChunkSize) {
batches = append(batches, items)
items = items[:0]
}
Expand Down
22 changes: 11 additions & 11 deletions pkg/util/partition_message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ var _ = Describe("Partition Message", func() {
items, omitted := PartitionMessage(testString, unalignedLimits, 7)
included := 0
for ii, item := range items {
expectedSize := unalignedLimits.ChunkSize
expectedSize := int(unalignedLimits.ChunkSize)

// The last chunk might be smaller than the preceeding chunks
if ii == len(items)-1 {
// the chunk size is the remainder of, the total size,
// or the max size, whatever is smallest,
// and the previous chunk sizes
chunkSize := Min(inputLen, unalignedLimits.TotalChunkSize) % unalignedLimits.ChunkSize
chunkSize := Min(inputLen, int(unalignedLimits.TotalChunkSize)) % int(unalignedLimits.ChunkSize)
// if the "rest" of the runes needs another chunk
if chunkSize > 0 {
// expect the chunk to contain the "rest" of the runes
Expand All @@ -87,19 +87,19 @@ var _ = Describe("Partition Message", func() {
if ii == len(items)-1 {
for ri, r := range item.Text {
runeOffset := (len(item.Text) - ri) - 1
runeVal, err := strconv.ParseInt(string(r), 16, 64)
expectedLen := Min(inputLen, unalignedLimits.TotalChunkSize)
runeVal, err := strconv.ParseUint(string(r), 16, 64)
expectedLen := Min(inputLen, int(unalignedLimits.TotalChunkSize))
expectedVal := (expectedLen - runeOffset) % 16

Expect(err).ToNot(HaveOccurred())
Expect(runeVal).To(Equal(int64(expectedVal)))
Expect(runeVal).To(BeEquivalentTo(expectedVal))
}
}

included += len(item.Text)
Expect(item.Text).To(HaveLen(expectedSize))
Expect(item.Text).To(HaveLen(int(expectedSize)))
}
Expect(omitted + included).To(Equal(inputLen))
Expect(omitted + included).To(BeEquivalentTo(inputLen))

}
})
Expand Down Expand Up @@ -133,8 +133,8 @@ var _ = Describe("Partition Message", func() {
batches := testMessageItemsFromLines(hundreds, limits, repeat)
items := batches[0]

Expect(len(items[0].Text)).To(Equal(limits.ChunkSize))
Expect(len(items[1].Text)).To(Equal(limits.ChunkSize))
Expect(len(items[0].Text)).To(BeEquivalentTo(limits.ChunkSize))
Expect(len(items[1].Text)).To(BeEquivalentTo(limits.ChunkSize))
})
})
})
Expand Down Expand Up @@ -170,10 +170,10 @@ func testPartitionMessage(hundreds int, limits types.MessageLimit, distance int)

items, omitted = PartitionMessage(builder.String(), limits, distance)

contentSize := Min(hundreds*100, limits.TotalChunkSize)
contentSize := Min(hundreds*100, int(limits.TotalChunkSize))
expectedOmitted := Max(0, (hundreds*100)-contentSize)

ExpectWithOffset(0, omitted).To(Equal(expectedOmitted))
ExpectWithOffset(0, omitted).To(BeEquivalentTo(expectedOmitted))

return
}