Fuzz #45
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
| # Fuzzing workflow for WRAITH Protocol | |
| # Runs cargo-fuzz on nightly Rust to find crashes and undefined behavior | |
| name: Fuzz | |
| on: | |
| # Run on manual trigger | |
| workflow_dispatch: | |
| inputs: | |
| duration: | |
| description: 'Fuzz duration in seconds per target' | |
| required: false | |
| default: '60' | |
| type: string | |
| target: | |
| description: 'Specific fuzz target (leave empty for all)' | |
| required: false | |
| default: '' | |
| type: string | |
| # Run weekly on Sunday at midnight UTC | |
| schedule: | |
| - cron: '0 0 * * 0' | |
| # Run on PRs that modify fuzz targets or core crypto code | |
| pull_request: | |
| paths: | |
| - 'fuzz/**' | |
| - 'crates/wraith-crypto/**' | |
| - 'crates/wraith-core/src/frame.rs' | |
| - 'crates/wraith-discovery/src/dht/**' | |
| - 'crates/wraith-obfuscation/src/padding.rs' | |
| - 'crates/wraith-files/src/tree_hash.rs' | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUSTFLAGS: -D warnings | |
| jobs: | |
| fuzz: | |
| name: Fuzz ${{ matrix.target }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: | |
| - fuzz_frame_parser | |
| - fuzz_dht_message | |
| - fuzz_crypto | |
| - fuzz_padding | |
| - fuzz_tree_hash | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| components: llvm-tools-preview | |
| - name: Install cargo-fuzz | |
| run: cargo install cargo-fuzz | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-fuzz-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-fuzz- | |
| - name: Create corpus directory | |
| run: mkdir -p fuzz/corpus/${{ matrix.target }} | |
| # Use cache instead of artifacts for corpus persistence across workflow runs | |
| # actions/cache persists across runs; actions/download-artifact@v7 only works within the same run | |
| - name: Restore corpus from cache | |
| id: corpus-cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: fuzz/corpus/${{ matrix.target }} | |
| key: fuzz-corpus-${{ matrix.target }}-${{ github.sha }} | |
| restore-keys: | | |
| fuzz-corpus-${{ matrix.target }}- | |
| - name: Run fuzzer | |
| env: | |
| FUZZ_DURATION: ${{ github.event.inputs.duration || '60' }} | |
| FUZZ_TARGET_INPUT: ${{ github.event.inputs.target }} | |
| FUZZ_TARGET: ${{ matrix.target }} | |
| RUST_BACKTRACE: '1' | |
| run: | | |
| # If specific target requested and doesn't match, skip | |
| if [ -n "$FUZZ_TARGET_INPUT" ] && [ "$FUZZ_TARGET_INPUT" != "$FUZZ_TARGET" ]; then | |
| echo "Skipping $FUZZ_TARGET (requested: $FUZZ_TARGET_INPUT)" | |
| exit 0 | |
| fi | |
| echo "Fuzzing $FUZZ_TARGET for ${FUZZ_DURATION} seconds..." | |
| cd fuzz | |
| cargo +nightly fuzz run "$FUZZ_TARGET" -- \ | |
| -max_total_time="${FUZZ_DURATION}" \ | |
| -max_len=16384 \ | |
| -print_final_stats=1 | |
| # Note: Corpus is automatically saved to cache at job end (actions/cache post-job behavior) | |
| # We only upload artifacts for crashes which need longer retention and explicit review | |
| - name: Upload crash artifacts | |
| if: failure() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: crashes-${{ matrix.target }} | |
| path: | | |
| fuzz/artifacts/${{ matrix.target }} | |
| fuzz/corpus/${{ matrix.target }}/crash-* | |
| retention-days: 90 | |
| # Summary job that fails if any fuzzer found a crash | |
| fuzz-summary: | |
| name: Fuzz Summary | |
| runs-on: ubuntu-latest | |
| needs: fuzz | |
| if: always() | |
| steps: | |
| - name: Check fuzz results | |
| env: | |
| FUZZ_RESULT: ${{ needs.fuzz.result }} | |
| run: | | |
| if [ "$FUZZ_RESULT" != "success" ]; then | |
| echo "One or more fuzzers found issues!" | |
| exit 1 | |
| fi | |
| echo "All fuzzers completed successfully" | |
| # Coverage-guided fuzzing with longer duration (weekly only) | |
| extended-fuzz: | |
| name: Extended Fuzz (Coverage) | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'schedule' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| components: llvm-tools-preview | |
| - name: Install cargo-fuzz | |
| run: cargo install cargo-fuzz | |
| - name: Create corpus directories | |
| run: | | |
| mkdir -p fuzz/corpus/fuzz_frame_parser | |
| mkdir -p fuzz/corpus/fuzz_dht_message | |
| mkdir -p fuzz/corpus/fuzz_crypto | |
| mkdir -p fuzz/corpus/fuzz_padding | |
| mkdir -p fuzz/corpus/fuzz_tree_hash | |
| # Restore all corpus caches for extended fuzzing | |
| - name: Restore corpus caches | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| fuzz/corpus/fuzz_frame_parser | |
| fuzz/corpus/fuzz_dht_message | |
| fuzz/corpus/fuzz_crypto | |
| fuzz/corpus/fuzz_padding | |
| fuzz/corpus/fuzz_tree_hash | |
| key: fuzz-corpus-extended-${{ github.sha }} | |
| restore-keys: | | |
| fuzz-corpus-extended- | |
| fuzz-corpus-fuzz_frame_parser- | |
| fuzz-corpus-fuzz_dht_message- | |
| fuzz-corpus-fuzz_crypto- | |
| fuzz-corpus-fuzz_padding- | |
| fuzz-corpus-fuzz_tree_hash- | |
| - name: Extended fuzzing (all targets) | |
| run: | | |
| cd fuzz | |
| for target in fuzz_frame_parser fuzz_dht_message fuzz_crypto fuzz_padding fuzz_tree_hash; do | |
| echo "Extended fuzzing $target for 300 seconds..." | |
| cargo +nightly fuzz run "$target" -- \ | |
| -max_total_time=300 \ | |
| -max_len=65536 \ | |
| -print_final_stats=1 || true | |
| done | |
| - name: Upload all artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: extended-fuzz-results | |
| path: | | |
| fuzz/artifacts/ | |
| fuzz/corpus/ | |
| retention-days: 90 |