Skip to content

Fuzz (nightly)

Fuzz (nightly) #48

Workflow file for this run

name: Fuzz (nightly)
# Extended, coverage-guided fuzzing that would be too slow for the per-PR gate. Each target
# runs far longer than the 60s CI smoke run, seeded from the committed corpus plus a corpus
# that persists (and grows) across nights via the cache, and uses a libFuzzer dictionary where
# one helps. A crash fails that target's job and uploads the reproducer as an artifact.
#
# This is a separate workflow from ci.yml so it never blocks pull requests.
on:
schedule:
- cron: '17 7 * * *' # daily at 07:17 UTC
workflow_dispatch:
inputs:
max_total_time:
description: 'Seconds to fuzz each target'
required: false
default: '900'
permissions:
contents: read
jobs:
fuzz:
name: nightly fuzz (${{ matrix.target }})
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
target:
- fuzz_reliable
- fuzz_netcode
- fuzz_netcode_connect_token
- fuzz_connection
- fuzz_connection_structured
steps:
- uses: actions/checkout@v4
# Persist the discovered corpus across runs: restore the newest prior cache for this
# target, and save a fresh entry keyed by run id (cache entries are immutable, so a
# unique key each run + a prefix restore-key gives a growing corpus).
- name: Restore corpus
uses: actions/cache@v4
with:
path: corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
restore-keys: |
fuzz-corpus-${{ matrix.target }}-
- name: Build ${{ matrix.target }}
run: |
set -euo pipefail
SAN="-fsanitize=fuzzer,address,undefined -fno-sanitize=nonnull-attribute -fno-sanitize-recover=all -fno-omit-frame-pointer -g"
case "${{ matrix.target }}" in
fuzz_reliable)
clang $SAN -DRELIABLE_DEBUG -Ireliable -Ifuzz \
fuzz/fuzz_reliable.c reliable/reliable.c -o "${{ matrix.target }}" ;;
fuzz_netcode)
clang $SAN -DNETCODE_DEBUG -Inetcode -Isodium -Ifuzz \
fuzz/fuzz_netcode.c sodium/sodium.c -o "${{ matrix.target }}" ;;
fuzz_netcode_connect_token)
clang $SAN -DNETCODE_DEBUG -Inetcode -Isodium -Ifuzz \
fuzz/fuzz_netcode_connect_token.c sodium/sodium.c -o "${{ matrix.target }}" ;;
*)
clang++ -std=c++11 $SAN -DYOJIMBO_DEBUG -DNETCODE_DEBUG -DRELIABLE_DEBUG -DSERIALIZE_DEBUG \
-I. -Iinclude -Isodium -Itlsf -Inetcode -Ireliable -Iserialize -Ifuzz \
fuzz/${{ matrix.target }}.cpp source/*.cpp netcode/netcode.c reliable/reliable.c tlsf/tlsf.c sodium/sodium.c \
-o "${{ matrix.target }}" ;;
esac
- name: Fuzz ${{ matrix.target }}
run: |
set -euo pipefail
mkdir -p "corpus/${{ matrix.target }}"
DICT=""
if [ -f "fuzz/dict/${{ matrix.target }}.dict" ]; then
DICT="-dict=fuzz/dict/${{ matrix.target }}.dict"
fi
T="${{ github.event.inputs.max_total_time || '900' }}"
echo "fuzzing ${{ matrix.target }} for ${T}s ${DICT}"
UBSAN_OPTIONS=halt_on_error=1 ./"${{ matrix.target }}" \
"corpus/${{ matrix.target }}" "fuzz/corpus/${{ matrix.target }}" \
$DICT -max_total_time="$T" -print_final_stats=1 -rss_limit_mb=4096
# On a crash libFuzzer writes the reproducer to the working dir; keep it.
- name: Upload crash reproducers
if: failure()
uses: actions/upload-artifact@v4
with:
name: crashes-${{ matrix.target }}
path: |
crash-*
oom-*
timeout-*
leak-*
if-no-files-found: ignore