Skip to content

Fuzz

Fuzz #2

Workflow file for this run

name: Fuzz
on:
schedule:
- cron: "0 0 * * 1"
workflow_dispatch:
permissions: {}
env:
CARGO_TERM_COLOR: always
jobs:
fuzz:
name: Fuzz (${{ matrix.target }})
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
issues: write
strategy:
fail-fast: false
matrix:
include:
- target: skill_frontmatter
budget: 300
- target: skill_extensions
budget: 300
- target: chunk_file
budget: 300
- target: config_toml
budget: 120
- target: plugin_manifest
budget: 300
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@4fd1da8b0805d2d2e936788875a7d65dbd677dc2 # nightly
- uses: taiki-e/install-action@afc483d62172accfff644fa56e82b4dab50727fc # cargo-fuzz
with:
tool: cargo-fuzz
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: fuzz -> target
prefix-key: fuzz-${{ matrix.target }}
- name: Fuzz ${{ matrix.target }}
id: fuzz
working-directory: fuzz
run: cargo +nightly fuzz run ${{ matrix.target }} -- -max_total_time=${{ matrix.budget }}
- name: Minimize crash and file issue
if: failure() && steps.fuzz.outcome == 'failure'
working-directory: fuzz
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FUZZ_TARGET: ${{ matrix.target }}
run: |
crash="$(ls -t "artifacts/${FUZZ_TARGET}"/* 2>/dev/null | head -1)"
if [[ -z "$crash" ]]; then
echo "no crash artifact found under artifacts/${FUZZ_TARGET}/, skipping issue filing"
exit 0
fi
cargo +nightly fuzz tmin "$FUZZ_TARGET" "$crash" || true
title="fuzz: crash in ${FUZZ_TARGET}"
existing="$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "${title} in:title" --json number,title \
--jq "[.[] | select(.title == \"${title}\")] | .[0].number" || true)"
if [[ -n "$existing" ]]; then
echo "open issue #$existing already tracks a crash in ${FUZZ_TARGET}, skipping duplicate"
exit 0
fi
body_file="$(mktemp)"
{
echo "## Fuzz crash"
echo ""
echo "Target: \`${FUZZ_TARGET}\`"
echo "Commit: ${GITHUB_SHA}"
echo "Workflow run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
echo ""
echo "The authoritative reproducer is attached to the workflow run above as the"
echo "\`fuzz-artifacts\` artifact, under \`${FUZZ_TARGET}/\`. A base64 copy of the"
echo "original (non-minimized) crash input is included below for quick inspection."
echo ""
echo '```'
base64 "$crash"
echo '```'
} > "$body_file"
gh issue create --repo "$GITHUB_REPOSITORY" \
--title "fuzz: crash in ${FUZZ_TARGET}" \
--label bug --label P1 \
--body-file "$body_file"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: fuzz-corpus-${{ matrix.target }}
path: fuzz/corpus/${{ matrix.target }}/
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: fuzz-artifacts-${{ matrix.target }}
path: fuzz/artifacts/${{ matrix.target }}/
if-no-files-found: ignore
coverage:
name: Coverage (${{ matrix.target }})
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
strategy:
fail-fast: false
matrix:
target:
- skill_frontmatter
- skill_extensions
- chunk_file
- config_toml
- plugin_manifest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@4fd1da8b0805d2d2e936788875a7d65dbd677dc2 # nightly
with:
components: llvm-tools-preview
- uses: taiki-e/install-action@afc483d62172accfff644fa56e82b4dab50727fc # cargo-fuzz, cargo-binutils
with:
tool: cargo-fuzz, cargo-binutils
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: fuzz -> target
prefix-key: fuzz-coverage-${{ matrix.target }}
# Visibility only: replays the committed seed corpus (no libFuzzer mutation) through an
# instrumented binary to report which lines/branches the corpus reaches. Does not run
# crash-finding — that's the `fuzz` job above — and continue-on-error here means a
# tooling failure in this step must never fail the crash-finding `fuzz` job.
- name: Generate coverage profile
id: coverage
continue-on-error: true
working-directory: fuzz
run: cargo +nightly fuzz coverage ${{ matrix.target }}
# Uses `rust-cov` (the cargo-binutils-installed llvm-cov wrapper) directly rather than
# the `cargo cov --` subcommand: cargo-binutils 0.4.0's `cargo cov`/`cargo profdata`
# subcommand parsing panics unconditionally on a clap `ArgAction` incompatibility
# (`arg 'no-default-features's 'ArgAction' should be one of 'SetTrue', 'SetFalse'`),
# confirmed even on bare `cargo cov -- --help`. `rust-cov` itself is unaffected.
- name: Text coverage report
if: steps.coverage.outcome == 'success'
continue-on-error: true
working-directory: fuzz
run: |
triple="$(rustc -vV | sed -n 's/^host: //p')"
bin="target/${triple}/coverage/${triple}/release/${{ matrix.target }}"
rust-cov report \
--instr-profile="coverage/${{ matrix.target }}/coverage.profdata" \
"$bin" \
| tee "coverage-${{ matrix.target }}.txt"
- name: HTML coverage report
if: steps.coverage.outcome == 'success'
continue-on-error: true
working-directory: fuzz
run: |
triple="$(rustc -vV | sed -n 's/^host: //p')"
bin="target/${triple}/coverage/${triple}/release/${{ matrix.target }}"
rust-cov show \
--instr-profile="coverage/${{ matrix.target }}/coverage.profdata" \
"$bin" \
--format=html --output-dir="coverage-html-${{ matrix.target }}"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: fuzz-coverage-${{ matrix.target }}
path: |
fuzz/coverage-${{ matrix.target }}.txt
fuzz/coverage-html-${{ matrix.target }}/
if-no-files-found: ignore