diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml new file mode 100644 index 0000000..5d3d28e --- /dev/null +++ b/.github/workflows/coverage.yaml @@ -0,0 +1,85 @@ +name: Fuzz Coverage + +on: + push: + branches: + - main + paths: + - "corpus/**" + + schedule: + - cron: "0 3 * * *" # Daily at 03:00 UTC + workflow_dispatch: + +permissions: + contents: write + pages: write + id-token: write + +jobs: + coverage: + runs-on: ubuntu-latest + + steps: + - name: Checkout corpus repo + uses: actions/checkout@v4 + + - name: Checkout fuzz targets repo + uses: actions/checkout@v4 + with: + repository: stratum-mining/stratum + path: stratum + + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly + with: + profile: minimal + toolchain: nightly + override: true + components: llvm-tools-preview + + - name: Add Rust LLVM tools to PATH + run: | + LLVM_TOOLS=$(rustc +nightly --print sysroot)/lib/rustlib/$(rustc --print host-tuple)/bin + echo "$LLVM_TOOLS" >> $GITHUB_PATH + ls -al "$LLVM_TOOLS" + + - name: Install cargo-fuzz + run: cargo install cargo-fuzz + + - name: Copy corpus into fuzz targets repo + run: | + mkdir -p stratum/fuzz/corpus + mv corpus/* stratum/fuzz/corpus + + - name: Run fuzz coverage + run: | + chmod +x ./scripts/fuzz_coverage.sh + cp ./scripts/fuzz_coverage.sh stratum/fuzz_coverage.sh + cd stratum/ + ls fuzz/ + ./fuzz_coverage.sh + + - name: Move coverage results into this repo + run: | + rm -rf coverage + mkdir -p coverage + cp -r stratum/coverage_html coverage/ + + - name: Upload pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: coverage/coverage_html + + deploy: + needs: coverage + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deploy + uses: actions/deploy-pages@v4 + diff --git a/scripts/fuzz_coverage.sh b/scripts/fuzz_coverage.sh new file mode 100755 index 0000000..e1d2a1c --- /dev/null +++ b/scripts/fuzz_coverage.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail + +############################################ +# Tools must be available directly in PATH: +# llvm-profdata +# llvm-cov +############################################ + +PROJECT_ROOT="$(pwd)" +HOST_TARGET=$(rustc +nightly --print host-tuple) + +echo "[+] Project root: $PROJECT_ROOT" +echo "[+] Host target: $HOST_TARGET" + +############################################ +# 1. Run coverage for all fuzz targets +############################################ + +echo "[+] Running fuzz coverage" +for target in $(cargo +nightly fuzz list); do + echo " → $target" + cargo +nightly fuzz coverage "$target" +done + +############################################ +# 2. Merge all .profdata +############################################ + +echo "[+] Merging coverage profiles" +llvm-profdata merge -sparse \ + fuzz/coverage/*/coverage.profdata \ + -o fuzz/coverage/merged.profdata + +############################################ +# 3. Collect fuzz target coverage binaries +############################################ + +echo "[+] Collecting fuzz binaries" + +OBJECTS="" +FIRST_TARGET="" + +for target in $(cargo fuzz list); do + BIN="target/$HOST_TARGET/coverage/$HOST_TARGET/release/$target" + + if [ ! -f "$BIN" ]; then + echo "ERROR: expected binary not found: $BIN" + exit 1 + fi + + if [ -z "$FIRST_TARGET" ]; then + FIRST_TARGET="$BIN" + else + OBJECTS="$OBJECTS -object $BIN" + fi +done + +############################################ +# 4. Filtering rules: +# - ignore stdlib (/rustc/…) +# - ignore crates.io (~/.cargo/registry/…) +# - ignore everything outside the project dir +############################################ + +IGNORE_FLAGS=( + "-ignore-filename-regex=/.*/rustc/.*" + "-ignore-filename-regex=/.cargo/registry/.*" + "-ignore-filename-regex=^/(?!${PROJECT_ROOT}).*$" +) + +############################################ +# 5. Generate text summary +############################################ + +echo "[+] Generating text summary" +llvm-cov report \ + "${IGNORE_FLAGS[@]}" \ + -instr-profile=fuzz/coverage/merged.profdata \ + $FIRST_TARGET $OBJECTS \ + > coverage_summary.txt + +############################################ +# 6. Generate HTML report +############################################ + +echo "[+] Generating HTML report" +llvm-cov show \ + "${IGNORE_FLAGS[@]}" \ + -instr-profile=fuzz/coverage/merged.profdata \ + $FIRST_TARGET $OBJECTS \ + -format=html \ + -output-dir=coverage_html + +echo "[✓] Done!" +echo "HTML → coverage_html/index.html" +echo "TXT → coverage_summary.txt" + diff --git a/scripts/readme.md b/scripts/readme.md new file mode 100644 index 0000000..368452e --- /dev/null +++ b/scripts/readme.md @@ -0,0 +1,75 @@ +# Fuzz Coverage Script + +This repository includes a helper script, `fuzz_coverage.sh`, that lets you generate local coverage reports for our fuzz targets. + +The script is meant to be copied into the **root of the [stratum](https://github.com/stratum-mining/stratum) repository**. + +It also expects that the **corpus directory** of this repository to be present in `fuzz/corpus` on the [stratum](https://github.com/stratum-mining/stratum) repository. + +--- + +## Requirements + +Before running the script, the following must be available directly in your `$PATH`: + +* `llvm-profdata` +* `llvm-cov` +* A nightly Rust toolchain +* `cargo-fuzz` + +For details on installing LLVM coverage tools, see: +[https://doc.rust-lang.org/rustc/instrument-coverage.html#installing-llvm-coverage-tools](https://doc.rust-lang.org/rustc/instrument-coverage.html#installing-llvm-coverage-tools) + +--- + +## Expected Project Layout + +After copying `fuzz_coverage.sh` into the root of [stratum](https://github.com/stratum-mining/stratum) repository, the structure should look like: + +``` +stratum/ +├── coverage_html (generated by the fuzz_coverage.sh after a successfull run) +├── fuzz +│ ├── artifacts +│ ├── corpus (the one copied from this repo) +│ ├── coverage (generated by cargo-fuzz) +│ ├── fuzz_targets +└── fuzz_coverage.sh (copied from this repo to the root of the stratum repo) +``` + +--- + +## Usage + +Run the script from the project root: + +```bash +./fuzz_coverage.sh +``` + +The script does the following: + +1. Runs `cargo +nightly fuzz coverage` for every fuzz target. +2. Merges all generated `.profdata` files. +3. Collects the built coverage binaries. +5. Generates: + * `coverage_summary.txt` (text report) + * `coverage_html/` (HTML report) + +--- + +## Output + +After running: + +* Open the HTML report: + + ``` + coverage_html/index.html + ``` + +* View the text summary: + + ``` + coverage_summary.txt + ```