@@ -28,7 +28,7 @@ concurrency:
2828jobs :
2929 fuzzing-smoke :
3030 name : AFL++ fuzzing smoke test
31- runs-on : ubuntu-24.04
31+ runs-on : ubuntu-latest
3232 timeout-minutes : 60
3333
3434 env :
@@ -68,213 +68,79 @@ jobs:
6868 | awk '{print $2}'
6969 )"
7070
71- if [ -z "$BEST_PKG" ]; then
72- echo "Failed to determine Lua dev package"
73- printf '%s\n' "$CANDIDATES"
74- exit 1
75- fi
76-
7771 BEST_VER="$(printf '%s\n' "$BEST_PKG" | sed -E 's/^liblua([0-9]+\.[0-9]+)-dev$/\1/')"
7872 LUA_PKG="lua$BEST_VER"
7973
8074 echo "lua_dev_pkg=$BEST_PKG" >> "$GITHUB_OUTPUT"
8175 echo "lua_pkg=$LUA_PKG" >> "$GITHUB_OUTPUT"
8276
83- echo "Using Lua dev package: $BEST_PKG"
84- echo "Using Lua interpreter: $LUA_PKG"
85-
8677 - name : Install dependencies
87- shell : bash
8878 run : |
89- set -euo pipefail
90-
9179 sudo apt-get install -y \
92- autoconf \
93- automake \
94- build-essential \
95- afl++ \
96- clang \
97- libtool \
98- pkg-config \
99- libyajl-dev \
100- libcurl4-openssl-dev \
101- liblmdb-dev \
80+ autoconf automake build-essential afl++ clang libtool pkg-config \
81+ libyajl-dev libcurl4-openssl-dev liblmdb-dev \
10282 ${{ steps.detect_lua.outputs.lua_dev_pkg }} \
10383 ${{ steps.detect_lua.outputs.lua_pkg }} \
104- libmaxminddb-dev \
105- libpcre2-dev \
106- libxml2-dev \
107- libfuzzy-dev \
108- pcre2-utils \
109- libpcre3-dev \
110- bison \
111- flex \
112- python3 \
113- python3-venv
84+ libmaxminddb-dev libpcre2-dev libxml2-dev libfuzzy-dev \
85+ pcre2-utils libpcre3-dev bison flex python3 python3-venv
11486
115- - name : Show Lua installation
116- shell : bash
117- run : |
118- which lua || true
119- lua -v || true
120- dpkg -l | grep lua || true
121-
122- - name : Build ModSecurity with AFL++ instrumentation
123- shell : bash
87+ - name : Build ModSecurity with AFL++
12488 env :
12589 CC : afl-clang-fast
12690 CXX : afl-clang-fast++
12791 run : |
128- set -euo pipefail
12992 ./build.sh
130- ./configure \
131- --enable-afl-fuzz \
132- --enable-parser-generation \
133- --enable-assertions=yes
93+ ./configure --enable-afl-fuzz --enable-parser-generation --enable-assertions=yes
13494 make -j"$(nproc)"
13595
136- - name : Locate AFL fuzzer target
96+ - name : Locate AFL target
13797 id : target
138- shell : bash
13998 run : |
140- set -euo pipefail
141-
142- CANDIDATES=(
143- "./test/fuzzer/afl_fuzzer"
144- "./test/fuzzer/.libs/afl_fuzzer"
145- )
146-
147- TARGET=""
148- for candidate in "${CANDIDATES[@]}"; do
149- if [ -x "$candidate" ]; then
150- TARGET="$candidate"
151- break
152- fi
99+ for f in ./test/fuzzer/afl_fuzzer ./test/fuzzer/.libs/afl_fuzzer; do
100+ [ -x "$f" ] && echo "target=$f" >> $GITHUB_OUTPUT && exit 0
153101 done
154-
155- if [ -z "$TARGET" ]; then
156- echo "Could not find test/fuzzer/afl_fuzzer"
157- find . -path "*afl_fuzzer*" -maxdepth 6 -print || true
158- exit 1
159- fi
160-
161- echo "target=$TARGET" >> "$GITHUB_OUTPUT"
162- echo "Using AFL target: $TARGET"
102+ echo "Fuzzer not found" && exit 1
163103
164104 - name : Create seed corpus
165- shell : bash
166105 run : |
167- set -euo pipefail
168106 rm -rf fuzz-in fuzz-out
169107 mkdir -p fuzz-in fuzz-out
170-
171- # Keep seeds small because the existing ModSecurity AFL harness reads 128 bytes.
172108 printf '' > fuzz-in/empty
173109 printf 'abc' > fuzz-in/plain
174- printf '../../etc/passwd' > fuzz-in/path-traversal
175- printf '%s' '<script>alert(1)</script>' > fuzz-in/xss
176- printf "%s" "' OR '1'='1" > fuzz-in/sqli
177- printf '%s' 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' > fuzz-in/max-128-a
178- printf '%s' '%u0101%u4141%2f%2e%2e%2f' > fuzz-in/url-encoded
179- printf '%s' '\x00\xff\xfe\xfd{{{{[[[[' > fuzz-in/binary-ish
180- printf '%s' 'SecRule ARGS "@rx ^(a+)+$" "id:1,phase:2,deny"' > fuzz-in/rule-ish
181- printf '%s' '../../../../../../../../../../tmp/a' > fuzz-in/deep-path
182- printf '%s' '() { :;}; echo vulnerable' > fuzz-in/shellshock-ish
183110
184- ls -la fuzz-in
185-
186- - name : Dry-run fuzzer target
187- shell : bash
188- run : |
189- set -euo pipefail
190- timeout 10s "${{ steps.target.outputs.target }}" < fuzz-in/plain
111+ - name : Dry-run
112+ run : timeout 10s "${{ steps.target.outputs.target }}" < fuzz-in/plain
191113
192- - name : Run AFL++ smoke fuzzing
193- shell : bash
114+ - name : Run AFL++
194115 run : |
195- set -euo pipefail
196-
197- RUN_MINUTES="${{ github.event.inputs.run_minutes }}"
198- if [ -z "$RUN_MINUTES" ]; then
199- RUN_MINUTES="10"
200- fi
201-
202- if ! [[ "$RUN_MINUTES" =~ ^[0-9]+$ ]]; then
203- echo "run_minutes must be a positive integer"
204- exit 1
205- fi
206-
207- if [ "$RUN_MINUTES" -lt 1 ]; then
208- echo "run_minutes must be >= 1"
209- exit 1
210- fi
211-
212- echo "Running AFL++ for ${RUN_MINUTES} minute(s)"
213-
214- # timeout returns 124 when it stops AFL after the requested smoke window.
215- # That is expected and should not fail the workflow.
216- set +e
217- timeout "${RUN_MINUTES}m" \
218- afl-fuzz \
219- -i fuzz-in \
220- -o fuzz-out \
221- -m none \
222- -t 1000+ \
223- -- "${{ steps.target.outputs.target }}"
224- AFL_EXIT=$?
225- set -e
116+ timeout "${{ github.event.inputs.run_minutes || '10' }}m" \
117+ afl-fuzz -i fuzz-in -o fuzz-out -m none -t 1000+ \
118+ -- "${{ steps.target.outputs.target }}" || true
226119
227- if [ "$AFL_EXIT" -ne 0 ] && [ "$AFL_EXIT" -ne 124 ]; then
228- echo "afl-fuzz exited with unexpected code: $AFL_EXIT"
229- exit "$AFL_EXIT"
230- fi
231-
232- - name : Summarize AFL++ results
120+ - name : Summarize
233121 id : summary
234- shell : bash
235122 run : |
236- set -euo pipefail
237-
238- echo "AFL++ output tree:"
239- find fuzz-out -maxdepth 4 -type f | sort || true
240-
241- CRASH_COUNT="$(find fuzz-out -path '*/crashes/id:*' -type f | wc -l | tr -d ' ')"
242- HANG_COUNT="$(find fuzz-out -path '*/hangs/id:*' -type f | wc -l | tr -d ' ')"
243-
244- echo "crash_count=$CRASH_COUNT" >> "$GITHUB_OUTPUT"
245- echo "hang_count=$HANG_COUNT" >> "$GITHUB_OUTPUT"
123+ CRASH=$(find fuzz-out -path '*/crashes/id:*' -type f | wc -l)
124+ HANG=$(find fuzz-out -path '*/hangs/id:*' -type f | wc -l)
125+ echo "crash_count=$CRASH" >> $GITHUB_OUTPUT
126+ echo "hang_count=$HANG" >> $GITHUB_OUTPUT
246127
247- {
248- echo "## AFL++ fuzzing smoke result"
249- echo
250- echo "- Crashes: \`$CRASH_COUNT\`"
251- echo "- Hangs: \`$HANG_COUNT\`"
252- echo "- Target: \`${{ steps.target.outputs.target }}\`"
253- echo "- Lua dev package: \`${{ steps.detect_lua.outputs.lua_dev_pkg }}\`"
254- echo "- Lua interpreter: \`${{ steps.detect_lua.outputs.lua_pkg }}\`"
255- } >> "$GITHUB_STEP_SUMMARY"
128+ - name : Package results
129+ if : always()
130+ run : |
131+ tar -czf afl-fuzz-results.tar.gz fuzz-in fuzz-out
256132
257- - name : Upload AFL++ corpus and findings
133+ - name : Upload results
258134 if : always()
259- uses : actions/upload-artifact@v4
135+ uses : actions/upload-artifact@v7
260136 with :
261137 name : afl-fuzz-results-${{ github.run_id }}
262- path : |
263- fuzz-in
264- fuzz-out
265- if-no-files-found : warn
266- retention-days : 14
138+ path : afl-fuzz-results.tar.gz
267139
268- - name : Fail on AFL++ crashes
140+ - name : Fail on crashes
269141 if : steps.summary.outputs.crash_count != '0'
270- shell : bash
271- run : |
272- echo "AFL++ found ${{ steps.summary.outputs.crash_count }} crash(es). Download the afl-fuzz-results artifact."
273- exit 1
142+ run : exit 1
274143
275- - name : Fail on AFL++ hangs when enabled
144+ - name : Fail on hangs
276145 if : github.event.inputs.fail_on_hangs == 'true' && steps.summary.outputs.hang_count != '0'
277- shell : bash
278- run : |
279- echo "AFL++ found ${{ steps.summary.outputs.hang_count }} hang(s). Download the afl-fuzz-results artifact."
280- exit 1
146+ run : exit 1
0 commit comments