ZKAC/docs/FUZZING.md
2026-05-06 17:39:11 +02:00

4.6 KiB
Raw Blame History

Fuzzing ZKAC

This repository ships a libFuzzer harness via cargo-fuzz. You can reuse zkac_fuzz from other fuzzers (e.g. AFL++, Honggfuzz) with a small custom binary.

Prerequisites

  • Rust toolchain (rustc, cargo).
  • cargo-fuzz: cargo install cargo-fuzz
  • AddressSanitizer / default cargo fuzz: requires nightly Rust (e.g. rustup toolchain install nightly). If you use a distro Rust without rustup, use sanitizer none (see below).

Quick start

From the repository root:

chmod +x scripts/fuzz-libfuzzer.sh
./scripts/fuzz-libfuzzer.sh

Fuzz a single target for 5 minutes:

FUZZ_TIME=300 ./scripts/fuzz-libfuzzer.sh handshake_respond

Stable Rust (no nightly)

Default cargo fuzz enables AddressSanitizer and needs nightly. To fuzz on stable, build and run with sanitizer none:

cargo fuzz build -s none handshake_respond
cargo fuzz run -s none handshake_respond -- -max_total_time=60

The helper script sets SANITIZER=none by default for stable-toolchain usage. It also prepends ~/.cargo/bin to PATH so cargo-fuzz is found after cargo install cargo-fuzz.

Did anything crash?

After a run:

  1. Exit code0 means every target finished its time/run budget without libFuzzer treating the process as a crash. Non-zero means at least one target failed (crash, abort, or libFuzzer error).
  2. Stdout — On failure, libFuzzer prints ERROR: libFuzzer: deadly signal (or ASan messages if you use a sanitizer), the panic/backtrace, and a reproduce command.
  3. Artifacts — Crashes are written under fuzz/artifacts/<target_name>/, typically crash-<hash> (and sometimes leak-* with leak sanitizer). Re-run with:
    cargo fuzz run -s none <target> fuzz/artifacts/<target>/crash-<hash>
    
  4. Minimize — Shrink a crashing input with cargo fuzz tmin -s none <target> <crash-file>.

If the script stops early, scroll up to the last === cargo-fuzz: <name> === block: the lines after it show which target failed.

Nightly + AddressSanitizer

SANITIZER=address FUZZ_TIME=120 ./scripts/fuzz-libfuzzer.sh session_decrypt

Targets (fuzz/fuzz_targets/)

Target Exercises
handshake_respond X25519 responder (handshake::respond)
handshake_initiator_complete Initiator complete with arbitrary response bytes
session_decrypt ChaCha20-Poly1305 decrypt + replay guard
replay_sequence Sliding-window replay logic
crypto_deserialize Ristretto public key, Schnorr signature, BBS+ issuer key parsing
bbs_verify_presentation BBS+ proof parse + verify (heavy; keep corpora small)

Shared logic lives in fuzz/src/lib.rs (zkac_fuzz).

Crate feature fuzz-expose

Fuzz builds enable zkacs fuzz-expose feature so harnesses can call Session::new_fuzz. Do not enable this in production binaries.

corpora / artifacts

  • Inputs: seed with mkdir -p fuzz/corpus/<target> and pass -artifact_prefix=fuzz/artifacts/ if you want crash dumps.
  • Gitignored: fuzz/corpus/, fuzz/artifacts/, crash files (see .gitignore).

AFL++ (optional)

  1. Install AFL++ and cargo install cargo-afl.
  2. Add a binary in fuzz/ (or a separate crate) that calls into zkac_fuzz::dispatch_all or individual zkac_fuzz::* functions.
  3. Build with the cargo-afl wrapper (not plain cargo build):
cd fuzz
cargo afl build --release
# then point cargo-afl at your harness binary, e.g.:
# cargo afl fuzz -i in -o out target/release/your_afl_bin

(fuzz/Cargo.toml already enables zkacs fuzz-expose for the dependency.)

The afl crates fuzz! macro must be linked with AFLs runtime; use cargo afl build as documented in cargo-afl.

Honggfuzz (optional)

Install the honggfuzz binary (distro package or source). Point it at a binary that exercises zkac_fuzz (similar to AFL). Honggfuzz does not require the cargo-fuzz project layout; you can compile a small main that loops over inputs.

CI smoke

GitHub Actions (.github/workflows/fuzz-smoke.yml) builds all targets with sanitizer none, then runs scripts/fuzz-libfuzzer.sh with FUZZ_RUNS=2000 so every target gets a short fixed-iteration smoke (same idea as below).

Locally you can match that with:

FUZZ_RUNS=2000 ./scripts/fuzz-libfuzzer.sh

Or smoke a single target with a run budget instead of time:

cargo fuzz run -s none handshake_respond -- -runs=2000 -print_final_stats=1