-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Adds nightlyfuzz CI * Runs nightly fuzz on the root directory * Gives proper permissions to fuzz script * Changes go_core_fuzz command * WIP * WIP * Fixes py script to point to chainlink files * Update fuzz/fuzz_all_native.py Co-authored-by: Jordan Krage <[email protected]> * Logs failing fuzz inputs * Bumps actions versions on nightlyfuzz action * Sets fix secs amount on CI fuzz run * Adds comment on `--seconds` usage * Fixes sonar exclusion on fuzz * Fixes sonar exclusion on fuzz * Fixes sonar exclusion on fuzz --------- Co-authored-by: Jordan Krage <[email protected]>
- Loading branch information
1 parent
556a4f3
commit 0e564cd
Showing
5 changed files
with
141 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*/fuzzer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters