-
Notifications
You must be signed in to change notification settings - Fork 2
259 lines (221 loc) · 7.92 KB
/
fuzz.yml
File metadata and controls
259 lines (221 loc) · 7.92 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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
name: Continuous Fuzzing
on:
# Run on main branch pushes
push:
branches: [main]
# Run nightly
schedule:
- cron: '0 2 * * *' # 2 AM UTC daily
# Allow manual trigger
workflow_dispatch:
inputs:
duration:
description: 'Fuzz duration per target (seconds)'
required: false
default: '3600'
env:
CARGO_TERM_COLOR: always
permissions:
contents: read
issues: write # To create issues on fuzzing failures
jobs:
# Discover all fuzz targets dynamically
discover-targets:
name: Discover Fuzz Targets
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.list-targets.outputs.targets }}
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: List fuzz targets
id: list-targets
run: |
cd fuzz
# Get all fuzz targets and format as JSON array
TARGETS=$(cargo fuzz list | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "targets=$TARGETS" >> $GITHUB_OUTPUT
echo "Found targets:"
echo "$TARGETS" | jq -r '.[]'
# Long-running fuzz tests
fuzz-continuous:
name: Fuzz ${{ matrix.target }}
runs-on: ubuntu-latest
needs: discover-targets
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.discover-targets.outputs.targets) }}
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
- uses: Swatinem/rust-cache@v2
with:
workspaces: "fuzz -> target"
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: Restore corpus
uses: actions/cache@v5
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
fuzz-corpus-${{ matrix.target }}-
- name: Run fuzz target
id: fuzz
continue-on-error: true
run: |
cd fuzz
DURATION=${{ github.event.inputs.duration || '3600' }}
echo "Running ${{ matrix.target }} for ${DURATION} seconds"
cargo fuzz run ${{ matrix.target }} -- \
-max_total_time=${DURATION} \
-print_final_stats=1 \
-print_corpus_stats=1 \
-rss_limit_mb=4096
env:
RUST_BACKTRACE: full
- name: Minimize corpus
if: always()
run: |
cd fuzz
cargo fuzz cmin ${{ matrix.target }}
- name: Save corpus
if: always()
uses: actions/cache/save@v5
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.sha }}
- name: Check for crashes
id: check_crashes
if: always()
run: |
if [ -d "fuzz/artifacts/${{ matrix.target }}" ] && [ "$(ls -A fuzz/artifacts/${{ matrix.target }})" ]; then
echo "crashes_found=true" >> $GITHUB_OUTPUT
echo "## Crashes found in ${{ matrix.target }}" >> $GITHUB_STEP_SUMMARY
ls -lh fuzz/artifacts/${{ matrix.target }}/ >> $GITHUB_STEP_SUMMARY
else
echo "crashes_found=false" >> $GITHUB_OUTPUT
echo "✅ No crashes found in ${{ matrix.target }}" >> $GITHUB_STEP_SUMMARY
fi
- name: Upload crash artifacts
if: steps.check_crashes.outputs.crashes_found == 'true'
uses: actions/upload-artifact@v7
with:
name: fuzz-crashes-${{ matrix.target }}-${{ github.sha }}
path: fuzz/artifacts/${{ matrix.target }}/
retention-days: 90
- name: Create issue for crashes
if: steps.check_crashes.outputs.crashes_found == 'true' && github.event_name == 'schedule'
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
const target = '${{ matrix.target }}';
const artifactsDir = `fuzz/artifacts/${target}`;
const files = fs.readdirSync(artifactsDir);
const body = `
## 🐛 Fuzzing Found Crashes in \`${target}\`
The continuous fuzzing workflow found ${files.length} crash(es) in the \`${target}\` fuzz target.
### Crash Files
${files.map(f => `- \`${f}\``).join('\n')}
### How to Reproduce
\`\`\`bash
cd fuzz
cargo fuzz run ${target} fuzz/artifacts/${target}/${files[0]}
\`\`\`
### Artifacts
Download crash artifacts from: [Actions Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
### Next Steps
1. Reproduce locally using the command above
2. Fix the underlying issue
3. Add a regression test
4. Re-run fuzzing to verify the fix
---
*Automatically created by continuous fuzzing workflow*
`;
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[FUZZ] Crashes found in ${target}`,
body: body,
labels: ['bug', 'fuzzing', 'security']
});
- name: Fail if crashes found
if: steps.check_crashes.outputs.crashes_found == 'true'
run: |
echo "::error::Fuzzing found crashes in ${{ matrix.target }}"
exit 1
# Corpus statistics and coverage
fuzz-coverage:
name: Fuzz Coverage Report
runs-on: ubuntu-latest
needs: fuzz-continuous
if: always() && github.event_name == 'schedule'
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
- uses: Swatinem/rust-cache@v2
with:
workspaces: "fuzz -> target"
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: Restore all corpora
uses: actions/cache@v5
with:
path: fuzz/corpus/
key: fuzz-corpus-all-${{ github.sha }}
restore-keys: |
fuzz-corpus-
- name: Generate coverage report
run: |
cd fuzz
# Sample arbitrary_ops targets for coverage (representative subset)
# This avoids running coverage for all targets which would be too slow
TARGETS=$(cargo fuzz list | grep '_arbitrary_ops$' || true)
if [ -z "$TARGETS" ]; then
echo "No arbitrary_ops targets found, using all targets" >> $GITHUB_STEP_SUMMARY
TARGETS=$(cargo fuzz list)
fi
echo "## Coverage Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
for target in $TARGETS; do
echo "### Coverage for $target" >> $GITHUB_STEP_SUMMARY
cargo fuzz coverage $target || true
if [ -f "coverage/$target/coverage.json" ]; then
echo "\`\`\`json" >> $GITHUB_STEP_SUMMARY
cat "coverage/$target/coverage.json" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
done
- name: Upload coverage artifacts
uses: actions/upload-artifact@v7
with:
name: fuzz-coverage-${{ github.sha }}
path: fuzz/coverage/
retention-days: 30
# Summary job
fuzz-summary:
name: Fuzzing Summary
runs-on: ubuntu-latest
needs: fuzz-continuous
if: always()
steps:
- name: Check fuzzing results
run: |
if [ "${{ needs.fuzz-continuous.result }}" != "success" ]; then
echo "⚠️ Some fuzz targets found issues or failed" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "✅ All fuzz targets passed" >> $GITHUB_STEP_SUMMARY
fi