-
Notifications
You must be signed in to change notification settings - Fork 1
157 lines (134 loc) · 5.07 KB
/
fuzz.yml
File metadata and controls
157 lines (134 loc) · 5.07 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
name: Fuzzing
on:
schedule:
# Run daily at 2 AM UTC
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
duration:
description: 'Fuzzing duration in seconds per target'
required: false
default: '300'
env:
CARGO_TERM_COLOR: always
jobs:
fuzz:
name: Fuzz Testing
runs-on: ubuntu-latest
permissions:
contents: read # to checkout the repo
issues: write # to create/comment on issues
# Only run fuzzing for non-fork repositories to save folks CI credits.
if: github.event.repository.fork == false
strategy:
fail-fast: false
matrix:
target: ['eval_sexpr', 'eval_jsonlogic']
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@nightly
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
# Corpus caching:
# - Save key uses run_id so each run creates a new cache (caches are immutable)
# - Restore key uses prefix match to find the most recent cache from any prior run
# - Daily runs ensure we always have a recent corpus to build on
- name: Restore corpus cache
uses: actions/cache@v5
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
restore-keys: |
fuzz-corpus-${{ matrix.target }}-
- name: Run fuzzer
run: |
DURATION=${{ github.event.inputs.duration || '300' }}
cargo fuzz run ${{ matrix.target }} -- -max_total_time=$DURATION
continue-on-error: true
- name: Check for crashes
id: check_crashes
run: |
if [ -d "fuzz/artifacts/${{ matrix.target }}" ] && [ "$(ls -A fuzz/artifacts/${{ matrix.target }})" ]; then
echo "crashes_found=true" >> $GITHUB_OUTPUT
echo "::warning::Crashes found in ${{ matrix.target }}"
else
echo "crashes_found=false" >> $GITHUB_OUTPUT
fi
- name: Upload crash artifacts
if: steps.check_crashes.outputs.crashes_found == 'true'
uses: actions/upload-artifact@v7
with:
name: fuzz-crashes-${{ matrix.target }}
path: fuzz/artifacts/${{ matrix.target }}/
- name: Upload corpus
if: always()
uses: actions/upload-artifact@v7
with:
name: fuzz-corpus-${{ matrix.target }}
path: fuzz/corpus/${{ matrix.target }}/
retention-days: 7
- name: Create issue for crashes
if: steps.check_crashes.outputs.crashes_found == 'true'
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
const target = '${{ matrix.target }}';
const artifactsPath = `fuzz/artifacts/${target}`;
// List crash files
let crashFiles = [];
try {
crashFiles = fs.readdirSync(artifactsPath);
} catch (e) {
crashFiles = ['(unable to read crash files)'];
}
const body = `## Fuzzing Crash Detected
**Target:** \`${target}\`
**Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
**Branch:** \`${{ github.ref_name }}\`
**Commit:** ${{ github.sha }}
### Crash Files
${crashFiles.map(f => `- \`${f}\``).join('\n')}
### Next Steps
1. Download the crash artifacts from the workflow run
2. Reproduce locally: \`cargo +nightly fuzz run ${target} fuzz/artifacts/${target}/<crash-file>\`
3. Fix the underlying issue
4. Re-run fuzzing to verify the fix
Crash artifacts are available in the workflow artifacts for 90 days.`;
// Check if issue already exists for this target
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'fuzzing',
state: 'open'
});
const existingIssue = issues.data.find(issue =>
issue.title.includes(target) && issue.labels.some(l => l.name === 'fuzzing')
);
if (existingIssue) {
// Add comment to existing issue
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssue.number,
body: `### New crash detected\n\n${body}`
});
} else {
// Create new issue
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Fuzzing crash in ${target}`,
body: body,
labels: ['bug', 'fuzzing']
});
}
- name: Fail if crashes found
if: steps.check_crashes.outputs.crashes_found == 'true'
run: |
echo "::error::Fuzzing discovered crashes in ${{ matrix.target }}"
exit 1