-
Notifications
You must be signed in to change notification settings - Fork 0
139 lines (122 loc) · 5.72 KB
/
Copy pathfuzz.yml
File metadata and controls
139 lines (122 loc) · 5.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
name: Fuzz
# Corpus replay runs on every push inside the regular CI job (ci.yml).
# This job runs the fuzzer itself: a longer, coverage-guided run that adds to
# the corpus. It is on a schedule rather than on push so that it never runs
# alongside the timing-sensitive scaling and LSP cost tests in CI.
#
# A crash input is pushed as a branch and reported as an issue. Opening a pull
# request from that branch makes the regression test permanent, because the
# next regular CI run and every one after it replays the corpus.
on:
schedule:
# Sundays at 03:00 UTC. A weekday run would compete with review activity;
# a late-night Sunday run is quiet, and any crash shows up first thing
# Monday.
- cron: '0 3 * * 0'
workflow_dispatch:
permissions:
contents: write
issues: write
env:
CARGO_TERM_COLOR: always
jobs:
fuzz:
name: fuzz the check pipeline
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# cargo-fuzz requires the nightly toolchain for sanitiser support.
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
with:
# The fuzz crate is its own workspace, so cache it separately.
workspaces: crates/deed-driver/fuzz
- name: install cargo-fuzz
run: cargo install cargo-fuzz
# Thirty minutes. Long enough to find shallow bugs without making the
# scheduled slot a sink for machine time.
- name: cargo fuzz run check
id: fuzz
working-directory: crates/deed-driver
run: |
cargo fuzz run check -- -max_total_time=1800
continue-on-error: true
# Crash and timeout inputs become corpus entries. The replay test in
# fuzz_corpus.rs will exercise them on every future build, so the fix
# for a crash is a permanent regression test, not just a CI artifact.
#
# Upload the full artifacts directory first so a developer can reproduce
# any crash locally with `cargo fuzz run check <input-file>`.
- name: upload crash reproducers
uses: actions/upload-artifact@v7
if: always()
with:
name: fuzz-artifacts
path: crates/deed-driver/fuzz/artifacts/
retention-days: 90
if-no-files-found: ignore
# Only what crashed is committed. libFuzzer also writes new coverage
# inputs into the corpus directory, and those are its working set rather
# than regression tests: fuzz_corpus.rs replays every entry on every
# build, so keeping them would charge each future build for inputs that
# assert nothing. If cumulative coverage turns out to be worth carrying
# between runs, the answer is a cache, not the corpus.
#
# This pushes a branch and opens an issue rather than opening a pull
# request. It used to open the pull request, and on the first run that
# found anything `gh pr create` answered "GitHub Actions is not
# permitted to create or approve pull requests": a repository setting
# that also lets workflows approve pull requests, which is not a thing to
# turn on so that a fuzzer can leave a note. The branch is pushed either
# way, so the issue names it and a person turns it into a pull request.
# The ruleset on the default branch requires one and exempts nobody, so
# a direct push was never an option.
- name: report any crash inputs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
artifacts=crates/deed-driver/fuzz/artifacts/check
corpus=crates/deed-driver/fuzz/corpus/check
copied=()
for f in "$artifacts"/crash-* "$artifacts"/timeout-* "$artifacts"/oom-*; do
[ -f "$f" ] || continue
cp "$f" "$corpus/"
copied+=("$corpus/$(basename "$f")")
done
if [ "${#copied[@]}" -eq 0 ]; then
echo "the fuzzer found nothing that the check pipeline did not survive"
exit 0
fi
count="${#copied[@]}"
branch="fuzz/crash-${GITHUB_RUN_ID}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$branch"
git add "${copied[@]}"
git commit -m "fuzz: add $count crash input(s) from the scheduled run"
git push origin "$branch"
body="$(mktemp)"
{
echo "The scheduled fuzz run found $count input(s) the check pipeline did not survive."
echo
echo "They are on \`$branch\` as corpus entries, so \`fuzz_corpus.rs\` replays them"
echo "on every build once that branch lands."
echo
echo "Open a pull request from \`$branch\` and expect it red: the replay test"
echo "reproduces the crash, which is what makes this a regression test rather than a"
echo "CI artifact. Push the fix onto the same branch and it goes green."
echo
echo "Reproduce locally with \`cargo fuzz run check <input>\`, or with no fuzzer at"
echo "all through \`cargo test -p deed-driver --test fuzz_corpus\`. The same files"
echo "are in the \`fuzz-artifacts\` artifact on run $GITHUB_RUN_ID."
} > "$body"
gh issue create \
--title "fuzz: $count crash input(s) from the scheduled run" \
--body-file "$body"
# Re-surface the fuzz failure after saving artifacts and corpus entries.
# Without this the job always shows green, which would hide a finding.
- name: fail if the fuzzer found a crash
if: steps.fuzz.outcome == 'failure'
run: |
echo "cargo fuzz found a crash; the reproducer is in the fuzz-artifacts artifact above" >&2
exit 1