-
Notifications
You must be signed in to change notification settings - Fork 1.3k
176 lines (159 loc) · 6.58 KB
/
Copy pathfuzz-pr.yml
File metadata and controls
176 lines (159 loc) · 6.58 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
# Run AFL++ fuzzing on PRs that touch C++ code.
#
# For each PR, an LLM analyzes the diff and generates:
# 1. Targeted seed files — initial inputs crafted to exercise the changed code paths.
# (A "seed" is a RESP-encoded sequence of Redis commands that the fuzzer starts from
# and mutates; see fuzz/seeds/resp/*.resp for the existing seed corpus.)
# 2. Focus command list — commands the mutator should prefer (~70% of the time),
# so mutations concentrate on the affected code instead of spreading randomly.
#
# The fuzzer then runs for 15 minutes in "smoke" mode (stop on first crash).
# Seeds are generated with OpenAI gpt-4.1 by default. When OPENAI_API_KEY is unavailable
# (e.g. fork PRs), seed generation is skipped and the fuzzer uses the existing seed corpus.
#
# Additionally, if the PR touches memcache-related code (memcache_parser, mc_family,
# fuzz/memcache_mutator.py, or fuzz/seeds/memcache/), a focused memcache fuzzing step
# runs automatically after RESP fuzzing passes, reusing the already-built binary.
name: AFL++ PR Fuzzing
on:
pull_request:
branches: [main]
paths:
- 'src/**/*.cc'
- 'src/**/*.h'
- 'helio/**/*.cc'
- 'helio/**/*.h'
- 'fuzz/**'
- '.github/workflows/fuzz-pr.yml'
- '.github/actions/fuzzing/**'
workflow_dispatch:
inputs:
duration:
description: 'Fuzzing duration in minutes'
required: false
default: '15'
type: string
memcache-duration:
description: 'Memcache fuzzing duration in minutes'
required: false
default: '10'
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
fuzz-pr:
runs-on: CI-LARGE-86
timeout-minutes: 60
container:
image: ghcr.io/romange/ubuntu-dev:24-afl
options: --security-opt seccomp=unconfined --sysctl "net.ipv6.conf.all.disable_ipv6=0"
credentials:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
submodules: true
fetch-depth: 0
- name: Generate PR diff
id: diff
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
git config --global --add safe.directory "$GITHUB_WORKSPACE"
BASE=${{ github.event.pull_request.base.sha }}
HEAD_SHA=${{ github.event.pull_request.head.sha }}
MERGE_BASE=$(git merge-base "$BASE" "$HEAD_SHA")
git diff "$MERGE_BASE".."$HEAD_SHA" > /tmp/pr_diff.txt
else
echo "" > /tmp/pr_diff.txt
fi
DIFF_LINES=$(wc -l < /tmp/pr_diff.txt)
echo "diff_lines=${DIFF_LINES}" >> "$GITHUB_OUTPUT"
echo "::group::PR diff summary"
echo "C++ diff lines: ${DIFF_LINES}"
if [ "$DIFF_LINES" -gt 0 ]; then
echo "Changed files:"
grep '^diff --git' /tmp/pr_diff.txt | sed 's|diff --git a/.* b/| |' || true
else
echo "No C++ file changes in this PR — seed generation will be skipped"
fi
echo "::endgroup::"
- name: Generate targeted seeds
id: seeds
run: |
SEEDS_DIR="${GITHUB_WORKSPACE}/fuzz/seeds/pr_targeted"
mkdir -p "$SEEDS_DIR"
python3 fuzz/generate_targeted_seeds.py \
--provider openai \
--output-dir "$SEEDS_DIR" \
< /tmp/pr_diff.txt
FOCUS=""
if [ -f "$SEEDS_DIR/focus_commands.json" ]; then
FOCUS=$(cat "$SEEDS_DIR/focus_commands.json")
fi
echo "focus_commands=${FOCUS}" >> "$GITHUB_OUTPUT"
echo "seeds_dir=${SEEDS_DIR}" >> "$GITHUB_OUTPUT"
SEED_COUNT=$(ls "$SEEDS_DIR"/*.resp 2>/dev/null | wc -l || echo 0)
echo "::group::Seed generation results"
echo "Seeds generated: ${SEED_COUNT}"
echo "Focus commands: ${FOCUS:-none}"
if [ "$SEED_COUNT" -gt 0 ]; then
ls -la "$SEEDS_DIR"/*.resp
fi
echo "::endgroup::"
# Job summary
{
echo "### Fuzzing Seed Generation"
echo ""
if [ "$SEED_COUNT" -gt 0 ]; then
echo "- **Seeds generated:** ${SEED_COUNT}"
echo "- **Focus commands:** \`${FOCUS}\`"
elif [ "$(wc -l < /tmp/pr_diff.txt)" -eq 0 ]; then
echo "- No C++ changes in PR — using default seed corpus"
elif [ -z "$OPENAI_API_KEY" ]; then
echo "- No API key — using default seed corpus"
else
echo "- LLM did not produce usable seeds — using default seed corpus"
fi
} >> "$GITHUB_STEP_SUMMARY"
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Run AFL++ PR fuzzing
uses: ./.github/actions/fuzzing
with:
mode: smoke
duration-minutes: ${{ github.event.inputs.duration || '15' }}
run-number: ${{ github.run_number }}
extra-seeds-dir: ${{ steps.seeds.outputs.seeds_dir }}
focus-commands: ${{ steps.seeds.outputs.focus_commands }}
# Reuses the binary built by the RESP step above (build: false).
# Only runs when RESP fuzzing passed (default success() condition) and memcache
# code was actually touched in this PR.
- name: Check if memcache-related files changed
id: memcache-check
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
CHANGED=$(grep -E '^diff --git a/(src/(facade/memcache|server/mc_family)|fuzz/(memcache_mutator|seeds/memcache))' /tmp/pr_diff.txt || true)
if [ -n "$CHANGED" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "Memcache-related files changed — will run memcache fuzzing:"
echo "$CHANGED" | sed 's|diff --git a/.* b/| |'
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "No memcache-related files changed — skipping memcache fuzzing"
fi
else
echo "run=true" >> "$GITHUB_OUTPUT"
echo "Manual trigger — running memcache fuzzing"
fi
- name: Run AFL++ memcache fuzzing
if: success() && steps.memcache-check.outputs.run == 'true'
uses: ./.github/actions/fuzzing
with:
mode: smoke
target: memcache
build: 'false'
duration-minutes: ${{ github.event.inputs['memcache-duration'] || '10' }}
run-number: ${{ github.run_number }}