From 4418a341e006d6f44595bb1dfb89c403e80d7b65 Mon Sep 17 00:00:00 2001 From: Alex Garcia Date: Mon, 2 Mar 2026 20:34:30 -0800 Subject: [PATCH] Add GitHub Actions CI workflow for fuzz testing Runs on push to main, nightly at 2am UTC, and manual dispatch. Linux (ubuntu-22.04) and macOS (macos-14) are fully supported. Windows (windows-2022) is best-effort with continue-on-error. Crash artifacts are uploaded on failure. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/fuzz.yaml | 152 ++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 .github/workflows/fuzz.yaml diff --git a/.github/workflows/fuzz.yaml b/.github/workflows/fuzz.yaml new file mode 100644 index 0000000..20d9885 --- /dev/null +++ b/.github/workflows/fuzz.yaml @@ -0,0 +1,152 @@ +name: "Fuzz" +on: + push: + branches: [main] + schedule: + # Nightly at 2am UTC for longer fuzzing sessions + - cron: "0 2 * * *" + workflow_dispatch: + inputs: + duration: + description: "Fuzz duration per target (seconds)" + default: "60" + +permissions: + contents: read + +jobs: + fuzz-linux: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install LLVM 18 + run: | + wget -qO- https://apt.llvm.org/llvm.sh | sudo bash -s -- 18 + echo "FUZZ_CC=clang-18" >> $GITHUB_ENV + - run: ./scripts/vendor.sh + - name: Build fuzz targets + run: make -C tests/fuzz all FUZZ_CC=$FUZZ_CC FUZZ_LDFLAGS= + - name: Run fuzz targets + run: | + DURATION=${{ github.event.inputs.duration || '60' }} + EXIT_CODE=0 + for target in tests/fuzz/targets/*; do + [ -f "$target" ] && [ -x "$target" ] || continue + name=$(basename "$target") + echo "::group::Fuzzing $name ($DURATION seconds)" + corpus="tests/fuzz/corpus/$name" + mkdir -p "$corpus" + dict="tests/fuzz/${name//_/-}.dict" + dict_flag="" + [ -f "$dict" ] && dict_flag="-dict=$dict" + if ! ASAN_OPTIONS=detect_leaks=1 "$target" $dict_flag \ + -max_total_time="$DURATION" "$corpus" 2>&1; then + echo "::error::Fuzz target $name found a crash!" + EXIT_CODE=1 + fi + echo "::endgroup::" + done + exit $EXIT_CODE + - name: Upload crash artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: fuzz-crashes-linux + path: | + tests/fuzz/crash-* + tests/fuzz/leak-* + tests/fuzz/timeout-* + + fuzz-macos: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - name: Install LLVM + run: brew install llvm + - run: ./scripts/vendor.sh + - name: Build fuzz targets + run: make -C tests/fuzz all FUZZ_CC=/opt/homebrew/opt/llvm/bin/clang + - name: Run fuzz targets + env: + DYLD_LIBRARY_PATH: "/opt/homebrew/opt/llvm/lib/c++:${{ env.DYLD_LIBRARY_PATH }}" + run: | + DURATION=${{ github.event.inputs.duration || '60' }} + EXIT_CODE=0 + for target in tests/fuzz/targets/*; do + [ -f "$target" ] && [ -x "$target" ] || continue + name=$(basename "$target") + echo "::group::Fuzzing $name ($DURATION seconds)" + corpus="tests/fuzz/corpus/$name" + mkdir -p "$corpus" + dict="tests/fuzz/${name//_/-}.dict" + dict_flag="" + [ -f "$dict" ] && dict_flag="-dict=$dict" + if ! "$target" $dict_flag \ + -max_total_time="$DURATION" "$corpus" 2>&1; then + echo "::error::Fuzz target $name found a crash!" + EXIT_CODE=1 + fi + echo "::endgroup::" + done + exit $EXIT_CODE + - name: Upload crash artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: fuzz-crashes-macos + path: | + tests/fuzz/crash-* + tests/fuzz/leak-* + tests/fuzz/timeout-* + + fuzz-windows: + # Best-effort: libFuzzer works on Windows via LLVM but ASAN/UBSAN + # support is less reliable. Leak detection is not available. + runs-on: windows-2022 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - name: Install LLVM 18 + run: choco install llvm --version=18.1.8 -y + - run: bash ./scripts/vendor.sh + shell: bash + - name: Build fuzz targets + shell: bash + run: | + export PATH="/c/Program Files/LLVM/bin:$PATH" + cd tests/fuzz + mkdir -p targets + for src in *.c; do + name="${src%.c}" + target_name="${name//-/_}" + echo "Building $target_name from $src" + clang -fsanitize=address,fuzzer \ + -I ../../ -I ../../vendor -DSQLITE_CORE -g \ + ../../vendor/sqlite3.c ../../sqlite-vec.c \ + "$src" -o "targets/${target_name}.exe" || { + echo "Warning: failed to build $target_name (best-effort)" + } + done + - name: Run fuzz targets + shell: bash + run: | + export PATH="/c/Program Files/LLVM/bin:$PATH" + DURATION=${{ github.event.inputs.duration || '60' }} + for target in tests/fuzz/targets/*.exe; do + [ -f "$target" ] || continue + name=$(basename "$target" .exe) + echo "=== Fuzzing $name ($DURATION seconds) ===" + corpus="tests/fuzz/corpus/$name" + mkdir -p "$corpus" + "$target" -max_total_time="$DURATION" "$corpus" 2>&1 || { + echo "Warning: $name found an issue or failed" + } + done + - name: Upload crash artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: fuzz-crashes-windows + path: | + tests/fuzz/crash-* + tests/fuzz/leak-*