Skip to content

Fuzz Testing

Fuzz Testing #368

Workflow file for this run

name: Fuzz Testing
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
# Run daily at 2 AM UTC
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
duration:
description: 'Fuzzing duration in seconds'
required: false
default: '600'
target:
description: 'Fuzz target to run (all_protocols or validate_with_python)'
required: false
default: 'all_protocols'
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
fuzz-fast:
name: Fast Fuzzing (all_protocols)
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@0f1b44df7e9cbb178d781a242338dfa5e243ad7f
with:
toolchain: nightly-2026-04-16
- name: Install cargo-fuzz
run: cargo install cargo-fuzz --locked --version 0.13.1
- name: Cache fuzz corpus
uses: actions/cache@v4
with:
path: fuzz/corpus/all_protocols
key: fuzz-corpus-all-protocols-${{ github.sha }}
restore-keys: |
fuzz-corpus-all-protocols-
- name: Run fast fuzzing (5 minutes)
run: |
cargo fuzz run all_protocols -- \
-max_total_time=300 \
-print_final_stats=1 \
-verbosity=1
continue-on-error: true
- name: Check for crashes
if: always()
run: |
if [ -d "fuzz/artifacts/all_protocols" ]; then
# Only fail on actual crashes, not memory leaks (often false positives)
if ls fuzz/artifacts/all_protocols/crash-* 2>/dev/null || \
ls fuzz/artifacts/all_protocols/timeout-* 2>/dev/null || \
ls fuzz/artifacts/all_protocols/oom-* 2>/dev/null; then
echo "::error::Fuzzing found crashes!"
ls -la fuzz/artifacts/all_protocols/
exit 1
fi
# Log leaks but don't fail
if ls fuzz/artifacts/all_protocols/leak-* 2>/dev/null; then
echo "::warning::Memory leaks detected (not failing build)"
ls -la fuzz/artifacts/all_protocols/leak-*
fi
else
echo "::error::Fuzzing did not create fuzz/artifacts/all_protocols. cargo fuzz likely failed before execution."
exit 1
fi
- name: Upload crash artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-crashes-all-protocols
path: fuzz/artifacts/all_protocols/
if-no-files-found: ignore
fuzz-thorough:
name: Thorough Fuzzing (validate_with_python)
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@0f1b44df7e9cbb178d781a242338dfa5e243ad7f
with:
toolchain: nightly-2026-04-16
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install cargo-fuzz
run: cargo install cargo-fuzz --locked --version 0.13.1
- name: Cache fuzz corpus
uses: actions/cache@v4
with:
path: fuzz/corpus/validate_with_python
key: fuzz-corpus-validate-python-${{ github.sha }}
restore-keys: |
fuzz-corpus-validate-python-
- name: Determine fuzzing duration
id: duration
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_DURATION: ${{ github.event.inputs.duration }}
run: |
duration=1800
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
case "$INPUT_DURATION" in
''|*[!0-9]*)
echo "::error::workflow_dispatch input 'duration' must be a whole number of seconds"
exit 1
;;
esac
duration="$INPUT_DURATION"
fi
printf 'duration=%s\n' "$duration" >> "$GITHUB_OUTPUT"
- name: Run thorough fuzzing with Python validation
env:
FUZZ_DURATION: ${{ steps.duration.outputs.duration }}
PICKLE_FUZZ_PYTHON_ENV_POLICY: strip_setup_python_and_ld_library_path
run: |
cargo fuzz run validate_with_python -- \
-max_total_time="$FUZZ_DURATION" \
-print_final_stats=1 \
-verbosity=1
continue-on-error: true
- name: Check for crashes
if: always()
run: |
if [ -d "fuzz/artifacts/validate_with_python" ]; then
# Only fail on actual crashes, not memory leaks (often false positives)
if ls fuzz/artifacts/validate_with_python/crash-* 2>/dev/null || \
ls fuzz/artifacts/validate_with_python/timeout-* 2>/dev/null || \
ls fuzz/artifacts/validate_with_python/oom-* 2>/dev/null; then
echo "::error::Fuzzing found crashes!"
ls -la fuzz/artifacts/validate_with_python/
exit 1
fi
# Log leaks but don't fail
if ls fuzz/artifacts/validate_with_python/leak-* 2>/dev/null; then
echo "::warning::Memory leaks detected (not failing build)"
ls -la fuzz/artifacts/validate_with_python/leak-*
fi
else
echo "::error::Fuzzing did not create fuzz/artifacts/validate_with_python. cargo fuzz likely failed before execution."
exit 1
fi
- name: Upload crash artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-crashes-validate-python
path: fuzz/artifacts/validate_with_python/
if-no-files-found: ignore
fuzz-custom:
name: Custom Fuzzing Run
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@0f1b44df7e9cbb178d781a242338dfa5e243ad7f
with:
toolchain: nightly-2026-04-16
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install cargo-fuzz
run: cargo install cargo-fuzz --locked --version 0.13.1
- name: Validate custom fuzz target
id: target
env:
INPUT_TARGET: ${{ github.event.inputs.target }}
run: |
case "$INPUT_TARGET" in
all_protocols|validate_with_python)
printf 'target=%s\n' "$INPUT_TARGET" >> "$GITHUB_OUTPUT"
;;
*)
echo "::error::workflow_dispatch input 'target' must be one of: all_protocols, validate_with_python"
exit 1
;;
esac
- name: Cache fuzz corpus
uses: actions/cache@v4
with:
path: fuzz/corpus/${{ steps.target.outputs.target }}
key: fuzz-corpus-${{ steps.target.outputs.target }}-${{ github.sha }}
restore-keys: |
fuzz-corpus-${{ steps.target.outputs.target }}-
- name: Validate custom fuzz duration
id: duration
env:
INPUT_DURATION: ${{ github.event.inputs.duration }}
run: |
case "$INPUT_DURATION" in
''|*[!0-9]*)
echo "::error::workflow_dispatch input 'duration' must be a whole number of seconds"
exit 1
;;
esac
printf 'duration=%s\n' "$INPUT_DURATION" >> "$GITHUB_OUTPUT"
- name: Run custom fuzzing
env:
FUZZ_TARGET: ${{ steps.target.outputs.target }}
FUZZ_DURATION: ${{ steps.duration.outputs.duration }}
PICKLE_FUZZ_PYTHON_ENV_POLICY: strip_setup_python_and_ld_library_path
run: |
cargo fuzz run "$FUZZ_TARGET" -- \
-max_total_time="$FUZZ_DURATION" \
-print_final_stats=1 \
-verbosity=1
continue-on-error: true
- name: Check for crashes
if: always() && steps.target.outputs.target != ''
env:
FUZZ_TARGET: ${{ steps.target.outputs.target }}
run: |
if [ -d "fuzz/artifacts/$FUZZ_TARGET" ]; then
# Only fail on actual crashes, not memory leaks (often false positives)
if ls "fuzz/artifacts/$FUZZ_TARGET"/crash-* 2>/dev/null || \
ls "fuzz/artifacts/$FUZZ_TARGET"/timeout-* 2>/dev/null || \
ls "fuzz/artifacts/$FUZZ_TARGET"/oom-* 2>/dev/null; then
echo "::error::Fuzzing found crashes!"
ls -la "fuzz/artifacts/$FUZZ_TARGET"/
exit 1
fi
# Log leaks but don't fail
if ls "fuzz/artifacts/$FUZZ_TARGET"/leak-* 2>/dev/null; then
echo "::warning::Memory leaks detected (not failing build)"
ls -la "fuzz/artifacts/$FUZZ_TARGET"/leak-*
fi
else
echo "::error::Fuzzing did not create fuzz/artifacts/$FUZZ_TARGET. cargo fuzz likely failed before execution."
exit 1
fi
- name: Upload crash artifacts
if: failure() && steps.target.outputs.target != ''
uses: actions/upload-artifact@v4
with:
name: fuzz-crashes-${{ steps.target.outputs.target }}
path: fuzz/artifacts/${{ steps.target.outputs.target }}/
if-no-files-found: ignore
minimize-corpus:
name: Minimize Corpus
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
needs: [fuzz-thorough]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@0f1b44df7e9cbb178d781a242338dfa5e243ad7f
with:
toolchain: nightly-2026-04-16
- name: Install cargo-fuzz
run: cargo install cargo-fuzz --locked --version 0.13.1
- name: Restore corpus
uses: actions/cache@v4
with:
path: |
fuzz/corpus/all_protocols
fuzz/corpus/validate_with_python
key: fuzz-corpus-all-${{ github.sha }}
restore-keys: |
fuzz-corpus-all-
- name: Minimize all_protocols corpus
run: cargo fuzz cmin all_protocols
continue-on-error: true
- name: Minimize validate_with_python corpus
run: cargo fuzz cmin validate_with_python
continue-on-error: true
- name: Report corpus stats
run: |
echo "## Corpus Statistics" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### all_protocols" >> $GITHUB_STEP_SUMMARY
echo "Files: $(find fuzz/corpus/all_protocols -type f | wc -l)" >> $GITHUB_STEP_SUMMARY
echo "Size: $(du -sh fuzz/corpus/all_protocols | cut -f1)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### validate_with_python" >> $GITHUB_STEP_SUMMARY
echo "Files: $(find fuzz/corpus/validate_with_python -type f | wc -l)" >> $GITHUB_STEP_SUMMARY
echo "Size: $(du -sh fuzz/corpus/validate_with_python | cut -f1)" >> $GITHUB_STEP_SUMMARY