Compare commits
1 Commits
main
...
raggi/gofu
Author | SHA1 | Date |
---|---|---|
![]() |
6c992d3e60 |
|
@ -2,20 +2,6 @@
|
|||
# both PRs and merged commits, and for the latter reports failures to slack.
|
||||
name: CI
|
||||
|
||||
env:
|
||||
# Our fuzz job, powered by OSS-Fuzz, fails periodically because we upgrade to
|
||||
# new Go versions very eagerly. OSS-Fuzz is a little more conservative, and
|
||||
# ends up being unable to compile our code.
|
||||
#
|
||||
# When this happens, we want to disable the fuzz target until OSS-Fuzz catches
|
||||
# up. However, we also don't want to forget to turn it back on when OSS-Fuzz
|
||||
# can once again build our code.
|
||||
#
|
||||
# This variable toggles the fuzz job between two modes:
|
||||
# - false: we expect fuzzing to be happy, and should report failure if it's not.
|
||||
# - true: we expect fuzzing is broken, and should report failure if it start working.
|
||||
TS_FUZZ_CURRENTLY_BROKEN: false
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
@ -296,63 +282,61 @@ jobs:
|
|||
|
||||
|
||||
fuzz:
|
||||
# This target periodically breaks (see TS_FUZZ_CURRENTLY_BROKEN at the top
|
||||
# of the file), so it's more complex than usual: the 'build fuzzers' step
|
||||
# might fail, and depending on the value of 'TS_FUZZ_CURRENTLY_BROKEN', that
|
||||
# might or might not be fine. The steps after the build figure out whether
|
||||
# the success/failure is expected, and appropriately pass/fail the job
|
||||
# overall accordingly.
|
||||
#
|
||||
# Practically, this means that all steps after 'build fuzzers' must have an
|
||||
# explicit 'if' condition, because the default condition for steps is
|
||||
# 'success()', meaning "only run this if no previous steps failed".
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- pkg: ./disco
|
||||
test: FuzzDisco
|
||||
- pkg: ./net/dns/resolver
|
||||
test: FuzzClampEDNSSize
|
||||
- pkg: ./net/stun
|
||||
test: FuzzStun
|
||||
- pkg: ./util/deephash
|
||||
test: FuzzTime
|
||||
- pkg: ./util/deephash
|
||||
test: FuzzAddr
|
||||
- pkg: ./util/hashx
|
||||
test: Fuzz
|
||||
|
||||
steps:
|
||||
- name: build fuzzers
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
# continue-on-error makes steps.build.conclusion be 'success' even if
|
||||
# steps.build.outcome is 'failure'. This means this step does not
|
||||
# contribute to the job's overall pass/fail evaluation.
|
||||
continue-on-error: true
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Restore Build Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
oss-fuzz-project-name: 'tailscale'
|
||||
dry-run: false
|
||||
language: go
|
||||
- name: report unexpectedly broken fuzz build
|
||||
if: steps.build.outcome == 'failure' && env.TS_FUZZ_CURRENTLY_BROKEN != 'true'
|
||||
run: |
|
||||
echo "fuzzer build failed, see above for why"
|
||||
echo "if the failure is due to OSS-Fuzz not being on the latest Go yet,"
|
||||
echo "set TS_FUZZ_CURRENTLY_BROKEN=true in .github/workflows/test.yml"
|
||||
echo "to temporarily disable fuzzing until OSS-Fuzz works again."
|
||||
exit 1
|
||||
- name: report unexpectedly working fuzz build
|
||||
if: steps.build.outcome == 'success' && env.TS_FUZZ_CURRENTLY_BROKEN == 'true'
|
||||
run: |
|
||||
echo "fuzzer build succeeded, but we expect it to be broken"
|
||||
echo "please set TS_FUZZ_CURRENTLY_BROKEN=false in .github/workflows/test.yml"
|
||||
echo "to reenable fuzz testing"
|
||||
exit 1
|
||||
- name: run fuzzers
|
||||
# Note: unlike the other setups, this is only grabbing the mod download
|
||||
# cache, rather than the whole mod directory, as the download cache
|
||||
# contains zips that can be unpacked in parallel faster than they can be
|
||||
# fetched and extracted by tar
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod/cache
|
||||
~\AppData\Local\go-build
|
||||
# The -2- here should be incremented when the scheme of data to be
|
||||
# cached changes (e.g. path above changes).
|
||||
key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ github.job }}-${{ runner.os }}-go-2-
|
||||
- name: Restore fuzz corpus cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build/fuzz
|
||||
# Uses an incrementing key to constantly collide and upload, but this
|
||||
# cache action is kept separate from any build cache action.
|
||||
key: fuzz-${{ matrix.pkg }}-${{ matrix.test }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
fuzz-${{ matrix.pkg }}-${{ matrix.test }}-
|
||||
- name: fuzz ${{matrix.pkg}} ${{matrix.test}}
|
||||
id: run
|
||||
# Run the fuzzers whenever they're able to build, even if we're going to
|
||||
# report a failure because TS_FUZZ_CURRENTLY_BROKEN is set to the wrong
|
||||
# value.
|
||||
if: steps.build.outcome == 'success'
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'tailscale'
|
||||
fuzz-seconds: 300
|
||||
dry-run: false
|
||||
language: go
|
||||
run: ./tool/go test -fuzz=${{ matrix.test }} -fuzztime=60s ${{ matrix.pkg }}
|
||||
- name: upload crash
|
||||
uses: actions/upload-artifact@v3
|
||||
if: steps.run.outcome != 'success' && steps.build.outcome == 'success'
|
||||
if: steps.run.outcome != 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
name: testdata
|
||||
path: ./**/testdata
|
||||
|
||||
depaware:
|
||||
runs-on: ubuntu-22.04
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package disco
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func FuzzDisco(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data1 []byte) {
|
||||
if data1 == nil {
|
||||
return
|
||||
}
|
||||
data2 := make([]byte, 0, len(data1))
|
||||
|
||||
m1, e1 := Parse(data1)
|
||||
if m1 == nil || reflect.ValueOf(m1).IsNil() {
|
||||
if e1 == nil {
|
||||
t.Fatal("nil message and nil error!")
|
||||
}
|
||||
t.Logf("message result is actually nil, can't be serialized again")
|
||||
return
|
||||
}
|
||||
|
||||
data2 = m1.AppendMarshal(data2)
|
||||
m2, e2 := Parse(data2)
|
||||
if m2 == nil || reflect.ValueOf(m2).IsNil() {
|
||||
if e2 == nil {
|
||||
t.Fatal("nil message and nil error!")
|
||||
}
|
||||
t.Errorf("second message result is actually nil!")
|
||||
}
|
||||
|
||||
t.Logf("m1: %#v", m1)
|
||||
t.Logf("m2: %#v", m1)
|
||||
t.Logf("data1:\n%x", data1)
|
||||
t.Logf("data2:\n%x", data2)
|
||||
|
||||
if e1 != nil && e2 != nil {
|
||||
if e1.Error() != e2.Error() {
|
||||
t.Errorf("error mismatch: %v != %v", e1, e2)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Explicitly ignore the case where the fuzzer made a different version
|
||||
// byte, it's not interesting.
|
||||
data1[1] = v0
|
||||
// The protocol doesn't have a length at this layer, and so it will
|
||||
// ignore meaningless trailing data such as a key that is more than 0
|
||||
// bytes, but less than keylen bytes.
|
||||
if len(data2) < len(data1) {
|
||||
data1 = data1[:len(data2)]
|
||||
}
|
||||
|
||||
if !bytes.Equal(data1, data2) {
|
||||
t.Errorf("data mismatch:\n%x\n%x", data1, data2)
|
||||
}
|
||||
|
||||
switch t1 := m1.(type) {
|
||||
case *Ping:
|
||||
t2, ok := m2.(*Ping)
|
||||
if !ok {
|
||||
t.Errorf("m1 and m2 are not the same type")
|
||||
}
|
||||
if *t1 != *t2 {
|
||||
t.Errorf("m1 and m2 are not the same")
|
||||
}
|
||||
case *Pong:
|
||||
t2, ok := m2.(*Pong)
|
||||
if !ok {
|
||||
t.Errorf("m1 and m2 are not the same type")
|
||||
}
|
||||
if *t1 != *t2 {
|
||||
t.Errorf("m1 and m2 are not the same")
|
||||
}
|
||||
case *CallMeMaybe:
|
||||
t2, ok := m2.(*CallMeMaybe)
|
||||
if !ok {
|
||||
t.Errorf("m1 and m2 are not the same type")
|
||||
}
|
||||
|
||||
if !slices.Equal(t1.MyNumber, t2.MyNumber) {
|
||||
t.Errorf("m1 and m2 are not the same")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unknown message type %T", m1)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build gofuzz
|
||||
|
||||
package disco
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
m, _ := Parse(data)
|
||||
|
||||
newBytes := m.AppendMarshal(data)
|
||||
parsedMarshall, _ := Parse(newBytes)
|
||||
|
||||
if m != parsedMarshall {
|
||||
panic("Parsing error")
|
||||
}
|
||||
return 1
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package stun
|
||||
|
||||
import "testing"
|
||||
|
||||
func FuzzStun(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
_, _, _ = ParseResponse(data)
|
||||
|
||||
_, _ = ParseBindingRequest(data)
|
||||
})
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build gofuzz
|
||||
|
||||
package stun
|
||||
|
||||
func FuzzStunParser(data []byte) int {
|
||||
_, _, _ = ParseResponse(data)
|
||||
|
||||
_, _ = ParseBindingRequest(data)
|
||||
return 1
|
||||
}
|
Loading…
Reference in New Issue