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

Add nightlyfuzz CI #11934

Merged
merged 26 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b3b509c
Adds nightlyfuzz CI
vyzaldysanchez Feb 5, 2024
47e6a66
Runs nightly fuzz on the root directory
vyzaldysanchez Feb 5, 2024
c6a8a64
Gives proper permissions to fuzz script
vyzaldysanchez Feb 5, 2024
1889db5
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 5, 2024
cc624d3
Changes go_core_fuzz command
vyzaldysanchez Feb 5, 2024
b44f197
WIP
vyzaldysanchez Feb 6, 2024
15f3d88
WIP
vyzaldysanchez Feb 6, 2024
231f921
Fixes py script to point to chainlink files
vyzaldysanchez Feb 6, 2024
a2f34c8
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 6, 2024
1a53de8
Update fuzz/fuzz_all_native.py
vyzaldysanchez Feb 6, 2024
34c2cb2
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 6, 2024
9bdf380
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 7, 2024
8fb7094
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 7, 2024
f666158
Logs failing fuzz inputs
vyzaldysanchez Feb 7, 2024
1273be6
Bumps actions versions on nightlyfuzz action
vyzaldysanchez Feb 7, 2024
715f9cb
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 7, 2024
774a80c
Sets fix secs amount on CI fuzz run
vyzaldysanchez Feb 7, 2024
11042a6
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 7, 2024
f97ad72
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 8, 2024
5d11ad6
Adds comment on `--seconds` usage
vyzaldysanchez Feb 8, 2024
2872e94
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 8, 2024
81555cc
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 8, 2024
471b058
Merge branch 'develop' into chore/BCF-2571/nightly-fuzz-ci
vyzaldysanchez Feb 9, 2024
97a647d
Fixes sonar exclusion on fuzz
vyzaldysanchez Feb 9, 2024
436cd4a
Fixes sonar exclusion on fuzz
vyzaldysanchez Feb 9, 2024
2613326
Fixes sonar exclusion on fuzz
vyzaldysanchez Feb 9, 2024
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
53 changes: 53 additions & 0 deletions .github/workflows/nightlyfuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: 'nightly/tag fuzz'
on:
schedule:
# Note: The schedule event can be delayed during periods of high
# loads of GitHub Actions workflow runs. High load times include
# the start of every hour. To decrease the chance of delay,
# schedule your workflow to run at a different time of the hour.
- cron: "25 0 * * *" # at 25 past midnight every day
push:
tags:
- '*'
workflow_dispatch: null
jobs:
fuzzrun:
name: "run native fuzzers"
runs-on: "ubuntu20.04-4cores-16GB"
steps:
- name: "Checkout"
uses: "actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11" # v4.1.1
- name: "Setup go"
uses: "actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491" # v5.0.0
with:
go-version-file: 'go.mod'
cache: true
cache-dependency-path: 'go.sum'
- name: "Get corpus directory"
id: "get-corpus-dir"
run: echo "corpus_dir=$(go env GOCACHE)/fuzz" >> $GITHUB_OUTPUT
shell: "bash"
- name: "Restore corpus"
uses: "actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2" # v4.0.0
id: "restore-corpus"
with:
path: "${{ steps.get-corpus-dir.outputs.corpus_dir }}"
# We need to ensure uniqueness of the key, as saving to a key more than once will fail (see Save corpus step).
# We never expect a cache hit with the key but we do expect a hit with the restore-keys prefix that is going
# to match the latest cache that has that prefix.
key: "nightlyfuzz-corpus-${{ github.run_id }}-${{ github.run_attempt }}"
restore-keys: "nightlyfuzz-corpus-"
- name: "Run native fuzzers"
# Fuzz for 1 hour
run: "cd fuzz && ./fuzz_all_native.py --seconds 3600"
- name: "Print failing testcases"
if: failure()
run: find . -type f|fgrep '/testdata/fuzz/'|while read f; do echo $f; cat $f; done
- name: "Save corpus"
uses: "actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2" # v4.0.0
# We save also on failure, so that we can keep the valuable corpus generated that led to finding a crash.
# If the corpus gets clobbered for any reason, we can remove the offending cache from the Github UI.
if: always()
with:
path: "${{ steps.get-corpus-dir.outputs.corpus_dir }}"
key: "${{ steps.restore-corpus.outputs.cache-primary-key }}"
1 change: 1 addition & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*/fuzzer
78 changes: 78 additions & 0 deletions fuzz/fuzz_all_native.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3

import argparse
import itertools
import os
import re
import subprocess
import sys

LIBROOT = "../"

def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="\n".join([
"Fuzz helper to run all native go fuzzers in chainlink",
"",
]),
)
parser.add_argument("--ci", required=False, help="In CI mode we run each parser only briefly once", action="store_true")
parser.add_argument("--seconds", required=False, help="Run for this many seconds of total fuzz time before exiting")
args = parser.parse_args()

# use float for remaining_seconds so we can represent infinity
if args.seconds:
remaining_seconds = float(args.seconds)
else:
remaining_seconds = float("inf")

fuzzers = discover_fuzzers()
print(f"🐝 Discovered fuzzers:", file=sys.stderr)
for fuzzfn, path in fuzzers.items():
print(f"{fuzzfn} in {path}", file=sys.stderr)

if args.ci:
# only run each fuzzer once for 60 seconds in CI
durations_seconds = [60]
else:
# run forever or until --seconds, with increasingly longer durations per fuzz run
durations_seconds = itertools.chain([5, 10, 30, 90, 270], itertools.repeat(600))

for duration_seconds in durations_seconds:
print(f"🐝 Running each fuzzer for {duration_seconds}s before switching to next fuzzer", file=sys.stderr)
for fuzzfn, path in fuzzers.items():
if remaining_seconds <= 0:
print(f"🐝 Time budget of {args.seconds}s is exhausted. Exiting.", file=sys.stderr)
return

next_duration_seconds = min(remaining_seconds, duration_seconds)
remaining_seconds -= next_duration_seconds

print(f"🐝 Running {fuzzfn} in {path} for {next_duration_seconds}s before switching to next fuzzer", file=sys.stderr)
run_fuzzer(fuzzfn, path, next_duration_seconds)
print(f"🐝 Completed running {fuzzfn} in {path} for {next_duration_seconds}s. Total remaining time is {remaining_seconds}s", file=sys.stderr)

def discover_fuzzers():
fuzzers = {}
for root, dirs, files in os.walk(LIBROOT):
for file in files:
if not file.endswith("test.go"): continue
with open(os.path.join(root, file), "r") as f:
text = f.read()
# ignore multiline comments
text = re.sub(r"(?s)/[*].*?[*]/", "", text)
# ignore single line comments *except* build tags
text = re.sub(r"//.*", "", text)
# Find every function with a name like FuzzXXX
for fuzzfn in re.findall(r"func\s+(Fuzz\w+)", text):
if fuzzfn in fuzzers:
raise Exception(f"Duplicate fuzz function: {fuzzfn}")
fuzzers[fuzzfn] = os.path.relpath(root, LIBROOT)
return fuzzers

def run_fuzzer(fuzzfn, dir, duration_seconds):
subprocess.check_call(["go", "test", "-run=^$", f"-fuzz=^{fuzzfn}$", f"-fuzztime={duration_seconds}s", f"./{dir}"], cwd=LIBROOT)

if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sonar.python.version=3.8
# Full exclusions from the static analysis
sonar.exclusions=**/node_modules/**/*,**/mocks/**/*, **/testdata/**/*, **/contracts/typechain/**/*, **/contracts/artifacts/**/*, **/contracts/cache/**/*, **/contracts/scripts/**/*, **/generated/**/*, **/fixtures/**/*, **/docs/**/*, **/tools/**/*, **/*.pb.go, **/*report.xml, **/*.config.ts, **/*.txt, **/*.abi, **/*.bin, **/*_codecgen.go, core/services/relay/evm/types/*_gen.go, core/services/relay/evm/types/gen/main.go, core/services/relay/evm/testfiles/*, **/core/web/assets**, core/scripts/chaincli/handler/debug.go
# Coverage exclusions
sonar.coverage.exclusions=**/*.test.ts, **/*_test.go, **/contracts/test/**/*, **/contracts/**/tests/**/*, **/core/**/testutils/**/*, **/core/**/mocks/**/*, **/core/**/cltest/**/*, **/integration-tests/**/*, **/generated/**/*, **/core/scripts**/* , **/*.pb.go, ./plugins/**/*, **/main.go, **/0195_add_not_null_to_evm_chain_id_in_job_specs.go, **/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go
sonar.coverage.exclusions=**/*.test.ts, **/*_test.go, **/contracts/test/**/*, **/contracts/**/tests/**/*, **/core/**/testutils/**/*, **/core/**/mocks/**/*, **/core/**/cltest/**/*, **/integration-tests/**/*, **/generated/**/*, **/core/scripts**/* , **/*.pb.go, ./plugins/**/*, **/main.go, **/0195_add_not_null_to_evm_chain_id_in_job_specs.go, **/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/streams/streams.go, fuzz/**
# Duplication exclusions
sonar.cpd.exclusions=**/contracts/**/*.sol, **/config.go, /core/services/ocr2/plugins/ocr2keeper/evm*/*,**/integration-tests/testconfig/**/*

Expand Down
13 changes: 8 additions & 5 deletions tools/bin/go_core_fuzz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this giant messy script? IMHO we should prefer to use the tooling as directly as possible, and avoid inheriting all this extra noise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might not need it, but I need to figure out how to keep the results visible, and downloadable, after each run. Which is what the script does.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it does a bunch of other junk and prints confusing messages that aren't about fuzzing, because it was just copied from regular tests. I'm not opposed to aligning these scripts, but we need to be deliberate with what we are doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
OUTPUT_FILE=${OUTPUT_FILE:-"./output.txt"}
USE_TEE="${USE_TEE:-true}"

echo "Failed tests and panics: ---------------------"
echo "Failed fuzz tests and panics: ---------------------"
echo ""
GO_LDFLAGS=$(bash tools/bin/ldflags)
use_tee() {
if [ "$USE_TEE" = "true" ]; then
tee "$@"
else
cat > "$@"
fi
}
go test -json -ldflags "$GO_LDFLAGS" github.com/smartcontractkit/chainlink/v2/core/services/relay/evm -fuzz . -fuzztime 12s | use_tee $OUTPUT_FILE

# the amount of --seconds here is subject to change based on how long the CI job takes in the future
# as we add more fuzz tests, we should take into consideration increasing this timelapse, so we can have enough coverage.
cd ./fuzz && ./fuzz_all_native.py --ci --seconds 420 | use_tee $OUTPUT_FILE
EXITCODE=${PIPESTATUS[0]}

# Assert no known sensitive strings present in test logger output
Expand All @@ -29,8 +31,9 @@ fi

echo "Exit code: $EXITCODE"
if [[ $EXITCODE != 0 ]]; then
echo "Encountered test failures."
echo "Encountered fuzz test failures. Logging all failing fuzz inputs:"
find . -type f|fgrep '/testdata/fuzz/'|while read f; do echo $f; cat $f; done
else
echo "All tests passed!"
echo "All fuzz tests passed!"
fi
exit $EXITCODE
Loading