Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/bench.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ jobs:

steps:
- name: download baseline results
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: baseline-results
path: ./baseline

- name: download head results
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: head-results
path: ./head
Expand Down
103 changes: 103 additions & 0 deletions .github/workflows/fuzz.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: fuzz

on:
schedule:
- cron: '13 0 * * *' # every day at 00:13 UTC

workflow_dispatch:
inputs:
fuzz_time:
description: '-fuzztime argument'
required: false
default: '5m'
fuzz_minimize_time:
description: '-fuzzminimizetime argument'
required: false
default: '60s'

env:
# Default values that can be overridden
FUZZ_TIME: ${{ github.event.inputs.fuzz_time || '1h' }}
FUZZ_MINIMIZE_TIME: ${{ github.event.inputs.fuzz_minimize_time || '5m' }}
FUZZ_OUTPUT: ./fuzz.log

permissions:
contents: write
pull-requests: write

jobs:
fuzz_test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: stable

- name: fuzz
id: fuzz
run: |
set -euo pipefail
mkdir -p /tmp/fuzz

set +e
make fuzz | tee "${FUZZ_OUTPUT}"
FUZZ_EXIT_CODE=$?

cat <<EOF >> $GITHUB_STEP_SUMMARY
### fuzz results
\`\`\`
$(cat "${FUZZ_OUTPUT}")
\`\`\`
EOF

exit $FUZZ_EXIT_CODE

- name: report failure
if: failure()
run: |
set -euo pipefail

# make sure something actually changed before we open a PR
git add testdata/
if git diff --staged --quiet; then
echo "No changes to testdata/ directory were found, assuming failure has already been reported."
exit 0
fi

git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"

BRANCH_NAME="fuzzer-failure/$(date +%Y%m%d-%H%M%S)"
git checkout -b $BRANCH_NAME
git commit -m "fuzz: add new findings"
git push origin $BRANCH_NAME

cat <<EOF > pr.txt
Scheduled fuzz testing found new failures:

$(cat "${FUZZ_OUTPUT}")

Next steps:
1. Close and re-open this PR to trigger GitHub Actions (see [explanation here][1])
2. Examine the crash inputs in the `testdata/fuzz/` directory
3. Run the fuzzer locally to reproduce: `make fuzz`
4. ???
5. Profit!

---
<small>*This PR was automatically created by the scheduled daily [fuzz action][2].*</small>

[1]: https://github.com/peter-evans/create-pull-request/blob/9b309f7eaa24cdc404c6e9e169d35ac06ca3671e/docs/concepts-guidelines.md#triggering-further-workflow-runs
[2]: ./.github/workflows/fuzz.yaml
EOF

gh pr create \
--base main \
--head "${BRANCH_NAME}" \
--title "fuzz: new failure on $(date +%Y/%m/%d)" \
--body-file pr.txt \
--label automated-pr \
--label bug \
--label fuzzing
6 changes: 3 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ jobs:
fetch-depth: 0 # Full history for commit timestamps

- name: setup pages
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0

- name: download latest autobahn report
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: autobahn-report
path: "${{ env.OUT_DIR }}/latest-report"
Expand All @@ -115,7 +115,7 @@ jobs:
run: ./ci/build-github-pages

- name: upload github pages artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: "${{ env.OUT_DIR }}/reports"

Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ BENCH_COUNT ?= 10
BENCH_ARGS ?= -bench=. -benchmem -count=$(BENCH_COUNT) -run=^$$
DOCS_PORT ?= :8080

FUZZ_TIME ?= 60s
FUZZ_MINIMIZE_TIME ?= 60s
FUZZ_ARGS ?= -fuzz=Fuzz -run=^Fuzz -fuzztime $(FUZZ_TIME) -fuzzminimizetime $(FUZZ_MINIMIZE_TIME)

# 3rd party tools
CMD_GOFUMPT := go run mvdan.cc/gofumpt@v0.8.0
CMD_PKGSITE := go run golang.org/x/pkgsite/cmd/pkgsite@latest
Expand All @@ -29,6 +33,11 @@ test:
go test $(TEST_ARGS) ./...
.PHONY: test

fuzz:
go test $(FUZZ_ARGS) .
.PHONY: fuzz


# Test command to run for continuous integration, which includes code coverage
# based on codecov.io's documentation:
# https://github.com/codecov/example-go/blob/b85638743b972bd0bd2af63421fe513c6f968930/README.md
Expand Down
44 changes: 44 additions & 0 deletions proto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,50 @@ func TestIncompleteFrames(t *testing.T) {
}
}

// ============================================================================
// Fuzzers
// ============================================================================
func FuzzReadFrame(f *testing.F) {
// Set up seed corpus
var testCases [][]byte
// Example frames from RFC 6455 section 5.7
// https://datatracker.ietf.org/doc/html/rfc6455#section-5.7
testCases = append(testCases, [][]byte{
// single-frame unmasked text
{0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f},
// single-frame masked text
{0x81, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58},
// fragmented unmasked text part 1
{0x01, 0x03, 0x48, 0x65, 0x6c},
// fragmented unmasked text part 2
{0x80, 0x02, 0x6c, 0x6f},
// unmasked ping
{0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f},
// masked ping response
{0x8a, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58},
// 256 bytes binary message
append([]byte{0x82, 0x7E, 0x01, 0x00}, make([]byte, 256)...),
// 64KiB binary message
append([]byte{0x82, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, make([]byte, 65536)...),
}...)

for _, tc := range testCases {
f.Add(tc)
}

f.Fuzz(func(t *testing.T, input []byte) {
modes := []websocket.Mode{websocket.ClientMode, websocket.ServerMode}
for _, mode := range modes {
frame, err := websocket.ReadFrame(bytes.NewReader(input), mode, 1<<20)
if err != nil {
t.Skipf("skipping eror: %s", err)
return
}
t.Logf("frame: %s", frame)
}
})
}

// ============================================================================
// Benchmarks
// ============================================================================
Expand Down
Loading