Fuzz (nightly) #37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |