mirror of
https://github.com/logos-blockchain/lez-fuzzing.git
synced 2026-06-14 06:59:29 +00:00
fix: workflow files update
- polish documentation
This commit is contained in:
parent
0710dbfc2b
commit
cfc415d214
22
.github/workflows/fuzz-afl.yml
vendored
22
.github/workflows/fuzz-afl.yml
vendored
@ -41,6 +41,11 @@ jobs:
|
||||
- fuzz_transaction_decoding
|
||||
- fuzz_validate_execute_consistency
|
||||
- fuzz_witness_set_verification
|
||||
- fuzz_merkle_tree
|
||||
- fuzz_transaction_properties
|
||||
- fuzz_privacy_preserving_witness
|
||||
- fuzz_encoding_privacy_preserving
|
||||
- fuzz_nullifier_set_roundtrip
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@ -239,7 +244,7 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# afl-coverage-aggregate — single HTML report merging all 15 targets
|
||||
# afl-coverage-aggregate — single HTML report merging all 20 targets
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
afl-coverage-aggregate:
|
||||
name: "AFL++ coverage — aggregated"
|
||||
@ -302,6 +307,11 @@ jobs:
|
||||
fuzz_transaction_decoding
|
||||
fuzz_validate_execute_consistency
|
||||
fuzz_witness_set_verification
|
||||
fuzz_merkle_tree
|
||||
fuzz_transaction_properties
|
||||
fuzz_privacy_preserving_witness
|
||||
fuzz_encoding_privacy_preserving
|
||||
fuzz_nullifier_set_roundtrip
|
||||
)
|
||||
for TARGET in "${TARGETS[@]}"; do
|
||||
cargo build \
|
||||
@ -330,6 +340,11 @@ jobs:
|
||||
fuzz_transaction_decoding
|
||||
fuzz_validate_execute_consistency
|
||||
fuzz_witness_set_verification
|
||||
fuzz_merkle_tree
|
||||
fuzz_transaction_properties
|
||||
fuzz_privacy_preserving_witness
|
||||
fuzz_encoding_privacy_preserving
|
||||
fuzz_nullifier_set_roundtrip
|
||||
)
|
||||
PROFRAW_DIR="coverage/afl/aggregated/profraw"
|
||||
mkdir -p "$PROFRAW_DIR"
|
||||
@ -409,6 +424,11 @@ jobs:
|
||||
fuzz_transaction_decoding
|
||||
fuzz_validate_execute_consistency
|
||||
fuzz_witness_set_verification
|
||||
fuzz_merkle_tree
|
||||
fuzz_transaction_properties
|
||||
fuzz_privacy_preserving_witness
|
||||
fuzz_encoding_privacy_preserving
|
||||
fuzz_nullifier_set_roundtrip
|
||||
)
|
||||
# llvm-cov show: first binary is a positional arg; the rest use --object
|
||||
first=1
|
||||
|
||||
17
.github/workflows/fuzz.yml
vendored
17
.github/workflows/fuzz.yml
vendored
@ -35,6 +35,11 @@ jobs:
|
||||
- fuzz_apply_state_diff_split_path
|
||||
- fuzz_multi_block_state_sequence
|
||||
- fuzz_sequencer_vs_replayer
|
||||
- fuzz_merkle_tree
|
||||
- fuzz_transaction_properties
|
||||
- fuzz_privacy_preserving_witness
|
||||
- fuzz_encoding_privacy_preserving
|
||||
- fuzz_nullifier_set_roundtrip
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -218,6 +223,11 @@ jobs:
|
||||
- fuzz_apply_state_diff_split_path
|
||||
- fuzz_multi_block_state_sequence
|
||||
- fuzz_sequencer_vs_replayer
|
||||
- fuzz_merkle_tree
|
||||
- fuzz_transaction_properties
|
||||
- fuzz_privacy_preserving_witness
|
||||
- fuzz_encoding_privacy_preserving
|
||||
- fuzz_nullifier_set_roundtrip
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout logos-execution-zone
|
||||
@ -285,7 +295,12 @@ jobs:
|
||||
fuzz_program_deployment_lifecycle \
|
||||
fuzz_apply_state_diff_split_path \
|
||||
fuzz_multi_block_state_sequence \
|
||||
fuzz_sequencer_vs_replayer; do
|
||||
fuzz_sequencer_vs_replayer \
|
||||
fuzz_merkle_tree \
|
||||
fuzz_transaction_properties \
|
||||
fuzz_privacy_preserving_witness \
|
||||
fuzz_encoding_privacy_preserving \
|
||||
fuzz_nullifier_set_roundtrip; do
|
||||
echo "=== $target ===" | tee -a perf_baseline.txt
|
||||
cargo fuzz run "$target" -- -max_total_time=30 2>&1 \
|
||||
| grep -E "exec/s|execs_per_sec" | tail -1 | tee -a perf_baseline.txt
|
||||
|
||||
168
README.md
168
README.md
@ -1,11 +1,20 @@
|
||||
# Lez-fuzzing
|
||||
<div align="center">
|
||||
|
||||
Coverage-guided fuzzing and adversarial testing infrastructure for the
|
||||
**Logos Execution Zone (LEZ)** protocol.
|
||||
# 🦀 Lez-fuzzing
|
||||
|
||||
**Coverage-guided fuzzing & adversarial testing infrastructure for the
|
||||
[Logos Execution Zone (LEZ)](https://github.com/) protocol.**
|
||||
|
||||
[](rust-toolchain.toml)
|
||||
[](#-fuzz-targets)
|
||||
[](.github/workflows/mutants.yml)
|
||||
[](LICENSE-MIT)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Repository Layout
|
||||
## 📂 Repository Layout
|
||||
|
||||
```
|
||||
lez-fuzzing/
|
||||
@ -22,28 +31,9 @@ lez-fuzzing/
|
||||
│ └── generators.rs # Arbitrary / proptest strategies
|
||||
├── fuzz/ # cargo-fuzz crate (own [workspace] sentinel)
|
||||
│ ├── Cargo.toml
|
||||
│ ├── fuzz_targets/
|
||||
│ │ ├── _template.rs # Template for just new-target
|
||||
│ │ ├── fuzz_transaction_decoding.rs
|
||||
│ │ ├── fuzz_stateless_verification.rs
|
||||
│ │ ├── fuzz_state_transition.rs
|
||||
│ │ ├── fuzz_block_verification.rs
|
||||
│ │ ├── fuzz_encoding_roundtrip.rs
|
||||
│ │ ├── fuzz_signature_verification.rs
|
||||
│ │ ├── fuzz_replay_prevention.rs
|
||||
│ │ ├── fuzz_state_diff_computation.rs
|
||||
│ │ ├── fuzz_validate_execute_consistency.rs
|
||||
│ │ ├── fuzz_state_serialization.rs
|
||||
│ │ ├── fuzz_witness_set_verification.rs
|
||||
│ │ ├── fuzz_program_deployment_lifecycle.rs
|
||||
│ │ ├── fuzz_apply_state_diff_split_path.rs
|
||||
│ │ ├── fuzz_multi_block_state_sequence.rs
|
||||
│ │ ├── fuzz_sequencer_vs_replayer.rs
|
||||
│ │ ├── fuzz_merkle_tree.rs
|
||||
│ │ ├── fuzz_transaction_properties.rs
|
||||
│ │ ├── fuzz_privacy_preserving_witness.rs
|
||||
│ │ ├── fuzz_encoding_privacy_preserving.rs
|
||||
│ │ └── fuzz_nullifier_set_roundtrip.rs # 20 targets total — see table below
|
||||
│ ├── fuzz_targets/ # 20 targets total — see table below
|
||||
│ │ ├── _template.rs # Template for `just new-target`
|
||||
│ │ └── fuzz_*.rs
|
||||
│ └── corpus/ # Curated seed inputs (one dir per target)
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
@ -54,11 +44,12 @@ lez-fuzzing/
|
||||
├── scripts/
|
||||
│ └── add_fuzz_target.py # Automates new-target scaffolding (called by just new-target)
|
||||
└── docs/
|
||||
└── fuzzing.md # Full developer guide
|
||||
├── fuzzing.md # Full developer guide
|
||||
└── mutants-not-fuzzable.md # Policy + mutant→test mapping
|
||||
```
|
||||
|
||||
The LEZ codebase is consumed as a **sibling directory** — clone
|
||||
`logos-execution-zone` next to this repository:
|
||||
`logos-execution-zone` next to this repository so the `../` path deps resolve:
|
||||
|
||||
```
|
||||
parent/
|
||||
@ -68,25 +59,25 @@ parent/
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
### 1. Prerequisites
|
||||
|
||||
```bash
|
||||
rustup install nightly
|
||||
rustup component add llvm-tools-preview --toolchain nightly
|
||||
cargo install cargo-fuzz
|
||||
# Optional but recommended:
|
||||
cargo install just
|
||||
cargo install just # optional but recommended
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> **Why nightly?** `cargo-fuzz` passes `-Zsanitizer=address` and
|
||||
> `-Zinstrument-coverage` (unstable flags) to `rustc`, and depends on the
|
||||
> `llvm-tools-preview` nightly component for coverage reporting. The
|
||||
> `rust-toolchain.toml` pins the whole repository to nightly so you never
|
||||
> `llvm-tools-preview` nightly component for coverage reporting.
|
||||
> `rust-toolchain.toml` pins the whole repository to nightly, so you never
|
||||
> need an explicit `+nightly` flag.
|
||||
|
||||
### Setup
|
||||
### 2. Setup
|
||||
|
||||
```bash
|
||||
# Clone both repositories side by side
|
||||
@ -95,7 +86,7 @@ git clone <LEZ_FUZZING_REPO_URL> lez-fuzzing
|
||||
cd lez-fuzzing
|
||||
```
|
||||
|
||||
### Run the fuzz targets
|
||||
### 3. Run the fuzz targets
|
||||
|
||||
```bash
|
||||
# All targets for 30 s each (RISC0_DEV_MODE=1 is set automatically)
|
||||
@ -114,38 +105,42 @@ just fuzz-regression
|
||||
just fuzz-props
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **ZK-proof cost:** `RISC0_DEV_MODE=1` is exported at the top of the
|
||||
> `Justfile` and must be set in every fuzz run to stub out ZK proof
|
||||
> generation. Without it each execution takes seconds instead of
|
||||
> microseconds.
|
||||
> generation. Without it, each execution takes **seconds** instead of
|
||||
> **microseconds**.
|
||||
|
||||
---
|
||||
|
||||
## Fuzz Targets
|
||||
## 🎯 Fuzz Targets
|
||||
|
||||
| Target | Protocol layer | Entry point |
|
||||
|--------|---------------|-------------|
|
||||
| `fuzz_transaction_decoding` | Borsh decoding of all tx/block types (`LeeTransaction`, `Block`, `HashableBlockData`) with roundtrip re-encoding | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` |
|
||||
| `fuzz_stateless_verification` | `transaction_stateless_check()` no-panic + idempotency | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` |
|
||||
| `fuzz_state_transition` | `V03State` transition: StateIsolationOnFailure + BalanceConservation + ReplayRejection invariants across up to 8 txs with fuzz-driven state | `fuzz/fuzz_targets/fuzz_state_transition.rs` |
|
||||
| `fuzz_block_verification` | Block hash integrity: HashRoundTrip · HashPreimage completeness (block_id/prev_hash/timestamp) · TxOrderCommitment | `fuzz/fuzz_targets/fuzz_block_verification.rs` |
|
||||
| `fuzz_encoding_roundtrip` | Borsh encode→decode→encode round-trip identity + canonical encoding for `PublicTransaction` and `ProgramDeploymentTransaction` | `fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs` |
|
||||
| `fuzz_signature_verification` | Signature correctness (sign→verify), no-panic on random bytes, cross-key soundness | `fuzz/fuzz_targets/fuzz_signature_verification.rs` |
|
||||
| `fuzz_replay_prevention` | Transaction nonce replay rejection with fuzz-driven initial state | `fuzz/fuzz_targets/fuzz_replay_prevention.rs` |
|
||||
| `fuzz_state_diff_computation` | `ValidatedStateDiff` forward containment + reverse completeness (bidirectional isolation check) | `fuzz/fuzz_targets/fuzz_state_diff_computation.rs` |
|
||||
| `fuzz_validate_execute_consistency` | `validate_on_state` / `execute_check_on_state` agreement + diff accuracy + BalanceConservation | `fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs` |
|
||||
| `fuzz_state_serialization` | `V03State` Borsh decode no-panic + StateSerializationRoundtrip idempotency + NullifierDeduplication (`NullifierSet` hand-written impl) | `fuzz/fuzz_targets/fuzz_state_serialization.rs` |
|
||||
| `fuzz_witness_set_verification` | `WitnessSet::is_valid_for` no-panic + CorrectVerification (sign→verify) + MessageIsolation (witness set for msg A rejected on msg B) | `fuzz/fuzz_targets/fuzz_witness_set_verification.rs` |
|
||||
| `fuzz_program_deployment_lifecycle` | `V03State::transition_from_program_deployment_transaction` no-panic + BalanceIsolation (deployment must not move tokens) + StateIsolationOnFailure | `fuzz/fuzz_targets/fuzz_program_deployment_lifecycle.rs` |
|
||||
| `fuzz_apply_state_diff_split_path` | SplitPathEquivalence: `validate_on_state + apply_state_diff` == `execute_check_on_state` for all known accounts (balance, nonce, data, program_owner); NonceIncrementCorrectness | `fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs` |
|
||||
| `fuzz_multi_block_state_sequence` | LongRangeBalanceConservation across up to 16 blocks + FailedTxNonceStability (nonce must not change on rejection) + PerBlockReplayRejection | `fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs` |
|
||||
| `fuzz_sequencer_vs_replayer` | Differential: sequencer path (`validate_on_state` → `apply_state_diff`) vs replayer path (`execute_check_on_state`) — SequencerReplayerEquivalence + ReplayerAcceptsAllSequencerTxs + ClockConsistency | `fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs` |
|
||||
| `fuzz_merkle_tree` | Commitment Merkle tree via the commitment set: ProofSome · ProofValid (leaf + auth path recomputes the root) · NonMembershipNone · IndicesSequential | `fuzz/fuzz_targets/fuzz_merkle_tree.rs` |
|
||||
| `fuzz_transaction_properties` | Transaction property invariants: HashDeterministic/HashNonDefault, SignerIds derived from witness keys & non-empty, AffectedAccountsContainSigners, PublicDiffNonEmptyOnSuccess | `fuzz/fuzz_targets/fuzz_transaction_properties.rs` |
|
||||
| `fuzz_privacy_preserving_witness` | `privacy_preserving_transaction::WitnessSet`: CorrectVerification (witness for msg A passes `signatures_are_valid_for(A)`) + MessageIsolation + SignerIdsMatchWitnessKeys | `fuzz/fuzz_targets/fuzz_privacy_preserving_witness.rs` |
|
||||
| `fuzz_encoding_privacy_preserving` | Privacy-preserving encoding: MessageEncodingRoundtrip + TxEncodingDeterministic/NonEmpty | `fuzz/fuzz_targets/fuzz_encoding_privacy_preserving.rs` |
|
||||
| `fuzz_nullifier_set_roundtrip` | `NullifierSet` Borsh serialisation: NullifierSetRoundtrip (decode→encode identity for the hand-written impl) | `fuzz/fuzz_targets/fuzz_nullifier_set_roundtrip.rs` |
|
||||
| # | Target | Protocol layer |
|
||||
|---|--------|----------------|
|
||||
| 1 | `fuzz_transaction_decoding` | Borsh decoding of all tx/block types (`LeeTransaction`, `Block`, `HashableBlockData`) with roundtrip re-encoding |
|
||||
| 2 | `fuzz_stateless_verification` | `transaction_stateless_check()` no-panic + idempotency |
|
||||
| 3 | `fuzz_state_transition` | `V03State` transition: StateIsolationOnFailure + BalanceConservation + ReplayRejection invariants across up to 8 txs with fuzz-driven state |
|
||||
| 4 | `fuzz_block_verification` | Block hash integrity: HashRoundTrip · HashPreimage completeness (block_id/prev_hash/timestamp) · TxOrderCommitment |
|
||||
| 5 | `fuzz_encoding_roundtrip` | Borsh encode→decode→encode round-trip identity + canonical encoding for `PublicTransaction` and `ProgramDeploymentTransaction` |
|
||||
| 6 | `fuzz_signature_verification` | Signature correctness (sign→verify), no-panic on random bytes, cross-key soundness |
|
||||
| 7 | `fuzz_replay_prevention` | Transaction nonce replay rejection with fuzz-driven initial state |
|
||||
| 8 | `fuzz_state_diff_computation` | `ValidatedStateDiff` forward containment + reverse completeness (bidirectional isolation check) |
|
||||
| 9 | `fuzz_validate_execute_consistency` | `validate_on_state` / `execute_check_on_state` agreement + diff accuracy + BalanceConservation |
|
||||
| 10 | `fuzz_state_serialization` | `V03State` Borsh decode no-panic + StateSerializationRoundtrip idempotency + NullifierDeduplication (`NullifierSet` hand-written impl) |
|
||||
| 11 | `fuzz_witness_set_verification` | `WitnessSet::is_valid_for` no-panic + CorrectVerification (sign→verify) + MessageIsolation (witness set for msg A rejected on msg B) |
|
||||
| 12 | `fuzz_program_deployment_lifecycle` | `V03State::transition_from_program_deployment_transaction` no-panic + BalanceIsolation (deployment must not move tokens) + StateIsolationOnFailure |
|
||||
| 13 | `fuzz_apply_state_diff_split_path` | SplitPathEquivalence: `validate_on_state + apply_state_diff` == `execute_check_on_state` for all known accounts (balance, nonce, data, program_owner); NonceIncrementCorrectness |
|
||||
| 14 | `fuzz_multi_block_state_sequence` | LongRangeBalanceConservation across up to 16 blocks + FailedTxNonceStability (nonce must not change on rejection) + PerBlockReplayRejection |
|
||||
| 15 | `fuzz_sequencer_vs_replayer` | Differential: sequencer path (`validate_on_state` → `apply_state_diff`) vs replayer path (`execute_check_on_state`) — SequencerReplayerEquivalence + ReplayerAcceptsAllSequencerTxs + ClockConsistency |
|
||||
| 16 | `fuzz_merkle_tree` | Commitment Merkle tree via the commitment set: ProofSome · ProofValid (leaf + auth path recomputes the root) · NonMembershipNone · IndicesSequential |
|
||||
| 17 | `fuzz_transaction_properties` | Transaction property invariants: HashDeterministic/HashNonDefault, SignerIds derived from witness keys & non-empty, AffectedAccountsContainSigners, PublicDiffNonEmptyOnSuccess |
|
||||
| 18 | `fuzz_privacy_preserving_witness` | `privacy_preserving_transaction::WitnessSet`: CorrectVerification (witness for msg A passes `signatures_are_valid_for(A)`) + MessageIsolation + SignerIdsMatchWitnessKeys |
|
||||
| 19 | `fuzz_encoding_privacy_preserving` | Privacy-preserving encoding: MessageEncodingRoundtrip + TxEncodingDeterministic/NonEmpty |
|
||||
| 20 | `fuzz_nullifier_set_roundtrip` | `NullifierSet` Borsh serialisation: NullifierSetRoundtrip (decode→encode identity for the hand-written impl) |
|
||||
|
||||
Each target lives at `fuzz/fuzz_targets/<name>.rs`.
|
||||
|
||||
> [!NOTE]
|
||||
> **Input-independent checks are not fuzz targets here.** Deterministic invariants
|
||||
> that ignore their input (e.g. genesis-account contents, getter/round-trip
|
||||
> identities, the system-account-modification guard) belong in `logos-execution-zone`
|
||||
@ -155,7 +150,7 @@ just fuzz-props
|
||||
|
||||
---
|
||||
|
||||
## Corpus Management
|
||||
## 🧬 Corpus Management
|
||||
|
||||
```bash
|
||||
# Minimise all corpora (removes dominated inputs, keeps coverage-equivalent set)
|
||||
@ -167,52 +162,52 @@ just corpus-cmin-target fuzz_state_transition
|
||||
|
||||
---
|
||||
|
||||
## Crash / Failure Workflow
|
||||
## 💥 Crash / Failure Workflow
|
||||
|
||||
```bash
|
||||
# Minimise a crash artifact
|
||||
# 1. Minimise a crash artifact
|
||||
just fuzz-tmin fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123
|
||||
|
||||
# Print the bytes as a Rust literal (for a regression #[test])
|
||||
# 2. Print the bytes as a Rust literal (for a regression #[test])
|
||||
cargo fuzz fmt fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123
|
||||
|
||||
# Promote the minimised input to the corpus so CI catches regressions
|
||||
# 3. Promote the minimised input to the corpus so CI catches regressions
|
||||
cp fuzz/artifacts/fuzz_state_transition/crash-abc123-minimised \
|
||||
fuzz/corpus/fuzz_state_transition/regression_001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Target
|
||||
## ➕ Adding a New Target
|
||||
|
||||
```bash
|
||||
# Scaffold everything automatically (corpus dir, .rs file, Cargo.toml entry, CI matrix entry)
|
||||
just new-target my_feature # creates fuzz_my_feature
|
||||
```
|
||||
|
||||
`just new-target` calls [`scripts/add_fuzz_target.py`](scripts/add_fuzz_target.py) which
|
||||
`just new-target` calls [`scripts/add_fuzz_target.py`](scripts/add_fuzz_target.py), which
|
||||
appends the `[[bin]]` entry to [`fuzz/Cargo.toml`](fuzz/Cargo.toml) and inserts the target
|
||||
into every strategy matrix in [`.github/workflows/fuzz.yml`](.github/workflows/fuzz.yml).
|
||||
|
||||
---
|
||||
|
||||
## Housekeeping
|
||||
## 🧹 Housekeeping
|
||||
|
||||
```bash
|
||||
just clean # Remove Cargo build artefacts (target/ and fuzz/target/)
|
||||
just clean-artifacts # Remove fuzz/artifacts/ (crash/timeout inputs)
|
||||
just clean-coverage # Remove fuzz/coverage/ (LLVM coverage reports)
|
||||
just clean-all # All of the above
|
||||
```
|
||||
| Command | Removes |
|
||||
|---------|---------|
|
||||
| `just clean` | Cargo build artefacts (`target/` and `fuzz/target/`) |
|
||||
| `just clean-artifacts` | `fuzz/artifacts/` (crash/timeout inputs) |
|
||||
| `just clean-coverage` | `fuzz/coverage/` (LLVM coverage reports) |
|
||||
| `just clean-all` | All of the above |
|
||||
|
||||
---
|
||||
|
||||
## CI
|
||||
## ⚙️ CI
|
||||
|
||||
GitHub Actions runs these workflows on every push/PR and nightly:
|
||||
|
||||
| Workflow | What it does |
|
||||
|----------|-------------|
|
||||
|----------|--------------|
|
||||
| `fuzz.yml` — `smoke-fuzz` (matrix) | Builds + runs each libFuzzer target for 60 s |
|
||||
| `fuzz.yml` — `regression` (matrix) | Replays the saved corpus (`-runs=0`) |
|
||||
| `fuzz.yml` — `proptest` | `cargo test -p fuzz_props --release` |
|
||||
@ -221,22 +216,23 @@ GitHub Actions runs these workflows on every push/PR and nightly:
|
||||
| `mutants.yml` | Mutation testing (`cargo-mutants`) |
|
||||
| `lint.yml` | Formatting + Clippy |
|
||||
|
||||
> **Note:** The `fuzz.yml` matrix currently lists 15 of the 20 libFuzzer targets.
|
||||
> Still missing: `fuzz_merkle_tree`, `fuzz_transaction_properties`,
|
||||
> `fuzz_privacy_preserving_witness`, `fuzz_encoding_privacy_preserving`, and
|
||||
> `fuzz_nullifier_set_roundtrip` — add them to `.github/workflows/fuzz.yml`. See
|
||||
> [`docs/fuzzing.md`](docs/fuzzing.md) for the manual fallback instructions.
|
||||
> [!NOTE]
|
||||
> All **20** libFuzzer targets are wired into every `fuzz.yml` matrix
|
||||
> (smoke-fuzz · regression · perf-baseline), the `fuzz-afl.yml` AFL++ lane, and
|
||||
> the `mutants.yml` corpus-replay job. New targets are added automatically by
|
||||
> `just new-target`; see [`docs/fuzzing.md`](docs/fuzzing.md) for the manual
|
||||
> fallback instructions.
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
## 📖 Documentation
|
||||
|
||||
Full developer guide — how to add new targets, interpret crashes, update
|
||||
the LEZ sibling clone, and tune performance — is in
|
||||
The full developer guide — how to add new targets, interpret crashes, update
|
||||
the LEZ sibling clone, and tune performance — lives in
|
||||
[`docs/fuzzing.md`](docs/fuzzing.md).
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
## 📜 License
|
||||
|
||||
Licensed under the [MIT License](LICENSE-MIT).
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Alternative Approaches vs. Current Implementation
|
||||
|
||||
## What the Current Project Does
|
||||
## 🧩 What the Current Project Does
|
||||
|
||||
The `lez-fuzzing` repository is a **coverage-guided, structured mutation fuzzing system** built on **cargo-fuzz / libFuzzer**, operating as a standalone companion to the Logos Execution Zone (LEZ) codebase. Its key design pillars:
|
||||
|
||||
@ -17,7 +17,7 @@ The `lez-fuzzing` repository is a **coverage-guided, structured mutation fuzzing
|
||||
|
||||
---
|
||||
|
||||
## Alternative Approaches
|
||||
## 🔬 Alternative Approaches
|
||||
|
||||
### 1. AFL++ (American Fuzzy Lop++)
|
||||
|
||||
@ -127,7 +127,7 @@ fuzzing replacement.
|
||||
|
||||
---
|
||||
|
||||
## Summary Comparison Matrix
|
||||
## 📊 Summary Comparison Matrix
|
||||
|
||||
| Approach | Bug-finding depth | CI cost | Impl. cost | Complements current? | Recommended action |
|
||||
|---|---|---|---|---|---|
|
||||
@ -141,7 +141,7 @@ fuzzing replacement.
|
||||
|
||||
---
|
||||
|
||||
## Decision-maker Recommendations
|
||||
## 🧭 Decision-maker Recommendations
|
||||
|
||||
**Already done** (was previously recommended here):
|
||||
|
||||
@ -150,16 +150,16 @@ fuzzing replacement.
|
||||
with the Plane A / Plane B framework documented in
|
||||
[`docs/mutants-not-fuzzable.md`](docs/mutants-not-fuzzable.md).
|
||||
- ✅ **Differential testing** — `fuzz_sequencer_vs_replayer`.
|
||||
- ✅ **Complete `fuzz.yml` CI matrix** — all **20** libFuzzer targets now run in
|
||||
every `fuzz.yml` matrix (smoke-fuzz · regression · perf-baseline) and the
|
||||
`fuzz-afl.yml` AFL++ lane, including `fuzz_merkle_tree`,
|
||||
`fuzz_transaction_properties`, `fuzz_privacy_preserving_witness`,
|
||||
`fuzz_encoding_privacy_preserving`, and `fuzz_nullifier_set_roundtrip`.
|
||||
|
||||
**Remaining higher-ROI next steps, in priority order:**
|
||||
|
||||
1. **Finish the `fuzz.yml` CI matrix** — it lists 15 of the 20 libFuzzer targets;
|
||||
add `fuzz_merkle_tree`, `fuzz_transaction_properties`,
|
||||
`fuzz_privacy_preserving_witness`, `fuzz_encoding_privacy_preserving`, and
|
||||
`fuzz_nullifier_set_roundtrip`.
|
||||
|
||||
2. **Honggfuzz on Linux CI only** — hardware-counter coverage finds different paths;
|
||||
1. **Honggfuzz on Linux CI only** — hardware-counter coverage finds different paths;
|
||||
gated to Linux since Apple Silicon has no HW counters.
|
||||
|
||||
3. **Formal verification of core invariants** (balance conservation, replay
|
||||
2. **Formal verification of core invariants** (balance conservation, replay
|
||||
prevention) — a long-term supplement, not a replacement.
|
||||
@ -1,4 +1,10 @@
|
||||
# Fuzzing Guide
|
||||
<div align="center">
|
||||
|
||||
# 🔬 Fuzzing Guide
|
||||
|
||||
**The full developer guide to running, extending, and triaging the LEZ fuzzing infrastructure.**
|
||||
|
||||
</div>
|
||||
|
||||
This document covers how to run fuzz targets, add new targets, minimise failures,
|
||||
and convert findings into regression tests.
|
||||
@ -9,7 +15,7 @@ directory that must be cloned separately).
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
## 🏗️ Architecture
|
||||
|
||||
The fuzz workspace (`fuzz/`) is a single Cargo workspace that covers **both** fuzzing
|
||||
engines via Cargo features. No separate Cargo manifest is needed.
|
||||
@ -38,7 +44,7 @@ The `cfg` attributes in the macro expansion resolve against the **calling crate'
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
## 🧰 Prerequisites
|
||||
|
||||
```bash
|
||||
# libFuzzer lane
|
||||
@ -61,7 +67,7 @@ cargo install cargo-afl
|
||||
|
||||
---
|
||||
|
||||
## Repository Setup
|
||||
## 📁 Repository Setup
|
||||
|
||||
`lez-fuzzing` is a **standalone repository** — it does **not** use git submodules.
|
||||
It expects the LEZ codebase to be cloned at `../logos-execution-zone` relative to itself.
|
||||
@ -79,7 +85,7 @@ git clone <LEZ_FUZZING_REPO_URL> lez-fuzzing
|
||||
|
||||
---
|
||||
|
||||
## How to Run
|
||||
## ▶️ How to Run
|
||||
|
||||
All fuzz targets must be run with `RISC0_DEV_MODE=1` to disable expensive ZK
|
||||
proof generation. The `just` recipes handle this automatically.
|
||||
@ -99,7 +105,7 @@ just fuzz-regression
|
||||
|
||||
---
|
||||
|
||||
## Available Fuzz Targets
|
||||
## 🎯 Available Fuzz Targets
|
||||
|
||||
| Target | What it fuzzes | Entry point |
|
||||
|--------|---------------|-------------|
|
||||
@ -118,10 +124,15 @@ just fuzz-regression
|
||||
| `fuzz_apply_state_diff_split_path` | **SplitPathEquivalence**: for every known account, `validate_on_state` + `apply_state_diff` must produce exactly the same balance, nonce, data, and program_owner as `execute_check_on_state`; **NonceIncrementCorrectness**: nonce after the split path equals nonce after the direct path for all signer accounts (catches bugs in the two-step `apply_state_diff` nonce-increment logic) | `fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs` |
|
||||
| `fuzz_multi_block_state_sequence` | **LongRangeBalanceConservation**: total genesis-account balance identical before and after all N (≤ 16) blocks; **FailedTxNonceStability**: every genesis-account nonce unchanged after a rejected transaction; **PerBlockReplayRejection**: every transaction accepted in block B is rejected in block B+1 (cumulative nonce-interaction coverage) | `fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs` |
|
||||
| `fuzz_sequencer_vs_replayer` | **SequencerReplayerEquivalence**: for every known account (genesis ∪ diff-declared), the sequencer path (`validate_on_state` → `apply_state_diff`) and the replayer path (`execute_check_on_state`) must produce identical balance, nonce, data, and program_owner after applying a full block of up to 8 transactions plus the mandatory clock invocation; **ReplayerAcceptsAllSequencerTxs**: every transaction accepted by `validate_on_state` must also be accepted by `execute_check_on_state`; **ClockConsistency**: the mandatory clock invocation must succeed on both paths and leave both states identical | `fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs` |
|
||||
| `fuzz_merkle_tree` | Commitment Merkle tree via the commitment set: **ProofSome**, **ProofValid** (leaf + auth path recomputes the root), **NonMembershipNone**, **IndicesSequential** | `fuzz/fuzz_targets/fuzz_merkle_tree.rs` |
|
||||
| `fuzz_transaction_properties` | Transaction property invariants: **HashDeterministic** / **HashNonDefault**, **SignerIds** derived from witness keys & non-empty, **AffectedAccountsContainSigners**, **PublicDiffNonEmptyOnSuccess** | `fuzz/fuzz_targets/fuzz_transaction_properties.rs` |
|
||||
| `fuzz_privacy_preserving_witness` | `privacy_preserving_transaction::WitnessSet`: **CorrectVerification** (witness for message A passes `signatures_are_valid_for(A)`), **MessageIsolation**, **SignerIdsMatchWitnessKeys** | `fuzz/fuzz_targets/fuzz_privacy_preserving_witness.rs` |
|
||||
| `fuzz_encoding_privacy_preserving` | Privacy-preserving encoding: **MessageEncodingRoundtrip**, **TxEncodingDeterministic** / **NonEmpty** | `fuzz/fuzz_targets/fuzz_encoding_privacy_preserving.rs` |
|
||||
| `fuzz_nullifier_set_roundtrip` | `NullifierSet` Borsh serialisation: **NullifierSetRoundtrip** (decode→encode identity for the hand-written impl) | `fuzz/fuzz_targets/fuzz_nullifier_set_roundtrip.rs` |
|
||||
|
||||
---
|
||||
|
||||
## How to Add a New Fuzz Target
|
||||
## ➕ How to Add a New Fuzz Target
|
||||
|
||||
### Step 1 — Scaffold with `just new-target`
|
||||
|
||||
@ -159,6 +170,7 @@ which:
|
||||
- Inserts the target name into every strategy matrix and the perf-baseline shell
|
||||
loop in [`.github/workflows/fuzz.yml`](../.github/workflows/fuzz.yml).
|
||||
|
||||
> [!TIP]
|
||||
> **Manual fallback:** if you create a target without `just new-target`, add the
|
||||
> entry yourself:
|
||||
>
|
||||
@ -197,12 +209,13 @@ cd fuzz && cargo afl build \
|
||||
|
||||
---
|
||||
|
||||
## AFL++ Parallel Fuzzing Lane
|
||||
## 🔀 AFL++ Parallel Fuzzing Lane
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install AFL++ natively on your machine.
|
||||
|
||||
> [!NOTE]
|
||||
> **Note on Linux package versions**: The `afl++` package in Debian stable (Bookworm)
|
||||
> and Ubuntu LTS is several major versions behind the current AFL++ 4.x series and may
|
||||
> be incompatible with `cargo-afl`. **Build from source** for a current version.
|
||||
@ -222,6 +235,7 @@ cd ..
|
||||
cargo install cargo-afl
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **macOS: run `afl-system-config` once before fuzzing** — AFL++ uses System V shared
|
||||
> memory (`shmget`) to pass coverage bitmaps between the fuzzer and the target. macOS
|
||||
> ships with very small defaults (`kern.sysv.shmmax = 4 MB`, `kern.sysv.shmmni = 32`)
|
||||
@ -245,6 +259,7 @@ cargo install cargo-afl
|
||||
> each restart. The `just fuzz-afl` and `just fuzz-afl-parallel` recipes **do not**
|
||||
> call this automatically because it requires `sudo`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **macOS: crash reporter must be disabled** — AFL++ detects the macOS `ReportCrash`
|
||||
> daemon and aborts if it is active (it delays crash notifications and causes AFL++ to
|
||||
> mis-classify crashes as timeouts). The `just fuzz-afl` and `just fuzz-afl-parallel`
|
||||
@ -361,24 +376,24 @@ The nightly AFL++ CI workflow has two jobs:
|
||||
|
||||
| Job | Triggers | Matrix |
|
||||
|-----|----------|--------|
|
||||
| `afl-smoke` | nightly + `workflow_dispatch` | 7 priority targets, 120 s each |
|
||||
| `afl-coverage` | nightly, `needs: afl-smoke` | 3 key targets; LLVM HTML report |
|
||||
| `afl-smoke` | nightly + `workflow_dispatch` | all 20 targets, 60 s each |
|
||||
| `afl-coverage-aggregate` | nightly, `needs: afl-smoke` | all 20 targets merged into one LLVM HTML report |
|
||||
|
||||
The smoke job:
|
||||
1. Builds the target with `cargo afl build --no-default-features --features fuzzer-afl`
|
||||
2. Runs `afl-fuzz` for 120 s in `aflplusplus/aflplusplus:v4.40c` container
|
||||
3. Syncs new queue entries into `fuzz/corpus/<target>/` and opens a corpus PR
|
||||
4. Uploads crashes/hangs as a workflow artifact
|
||||
The smoke job (one matrix leg per target, on `ubuntu-latest`):
|
||||
1. Builds AFL++ from source, then builds the target with `cargo afl build --no-default-features --features fuzzer-afl`
|
||||
2. Runs `afl-fuzz` for 60 s (`timeout 60`)
|
||||
3. Reports edge-bitmap coverage to the job step summary
|
||||
4. Uploads the queue/crashes/hangs as a workflow artifact
|
||||
|
||||
The coverage job:
|
||||
1. Downloads the smoke findings
|
||||
2. Rebuilds with `RUSTFLAGS="-C instrument-coverage"`
|
||||
3. Runs all corpus + queue inputs through the binary
|
||||
4. Merges `.profraw` → `.profdata` → HTML report via `llvm-cov show`
|
||||
The coverage-aggregate job:
|
||||
1. Downloads every smoke leg's findings
|
||||
2. Rebuilds all 20 targets with `RUSTFLAGS="-C instrument-coverage"`
|
||||
3. Runs all checked-in corpus + AFL queue inputs through each binary
|
||||
4. Merges every `.profraw` → one `.profdata` → a single combined HTML report via `llvm-cov show`
|
||||
|
||||
---
|
||||
|
||||
## Updating the LEZ Dependency
|
||||
## 🔄 Updating the LEZ Dependency
|
||||
|
||||
`lez-fuzzing` reads LEZ source directly from `../logos-execution-zone`. To pick up LEZ
|
||||
changes, simply update that repo:
|
||||
@ -401,7 +416,7 @@ just update-lez
|
||||
|
||||
---
|
||||
|
||||
## Minimising & Reproducing Failures
|
||||
## 🐛 Minimising & Reproducing Failures
|
||||
|
||||
When `cargo fuzz` finds a crash it writes an artifact to
|
||||
`fuzz/artifacts/fuzz_<target>/crash-<hash>`.
|
||||
@ -440,7 +455,7 @@ Open a PR. The `regression` CI job will permanently block re-introduction of thi
|
||||
|
||||
---
|
||||
|
||||
## Coverage Reports
|
||||
## 📊 Coverage Reports
|
||||
|
||||
### Step 1 — libFuzzer coverage (via `cargo fuzz coverage`)
|
||||
|
||||
@ -476,7 +491,7 @@ automates steps 2–5 and uploads the report as a workflow artifact.
|
||||
|
||||
---
|
||||
|
||||
## Invariant Framework
|
||||
## 🛡️ Invariant Framework
|
||||
|
||||
Shared invariants live in `fuzz_props/src/invariants.rs`. There are two layers:
|
||||
|
||||
@ -538,7 +553,7 @@ To add a new invariant:
|
||||
|
||||
---
|
||||
|
||||
## Input Generators
|
||||
## 🎲 Input Generators
|
||||
|
||||
The `fuzz_props` crate provides two layers of input generation:
|
||||
|
||||
@ -579,7 +594,7 @@ fuzz target parameters for zero-boilerplate structured fuzzing.
|
||||
|
||||
---
|
||||
|
||||
## Performance Baseline
|
||||
## ⚡ Performance Baseline
|
||||
|
||||
Measured on a 4-core x86_64 Linux runner with `RISC0_DEV_MODE=1`:
|
||||
|
||||
@ -600,7 +615,13 @@ Measured on a 4-core x86_64 Linux runner with `RISC0_DEV_MODE=1`:
|
||||
| `fuzz_apply_state_diff_split_path` | ~5 000 exec/sec *(estimate)* |
|
||||
| `fuzz_multi_block_state_sequence` | ~1 000 exec/sec *(estimate)* |
|
||||
| `fuzz_sequencer_vs_replayer` | ~2 000 exec/sec *(estimate)* |
|
||||
| `fuzz_merkle_tree` | ~20 000 exec/sec *(estimate)* |
|
||||
| `fuzz_transaction_properties` | ~15 000 exec/sec *(estimate)* |
|
||||
| `fuzz_privacy_preserving_witness` | ~15 000 exec/sec *(estimate)* |
|
||||
| `fuzz_encoding_privacy_preserving` | ~50 000 exec/sec *(estimate)* |
|
||||
| `fuzz_nullifier_set_roundtrip` | ~100 000 exec/sec *(estimate)* |
|
||||
|
||||
> [!NOTE]
|
||||
> Throughput figures for the five new targets are rough estimates; run `just perf-baseline`
|
||||
> locally or check the `perf-baseline` CI artifact for up-to-date measurements.
|
||||
|
||||
@ -617,7 +638,7 @@ just fuzz-afl-parallel fuzz_state_transition $(nproc) 3600
|
||||
|
||||
---
|
||||
|
||||
## ZK-Proof Cost Warning
|
||||
## ⚠️ ZK-Proof Cost Warning
|
||||
|
||||
`PrivacyPreservingTransaction` uses `risc0-zkvm` (seconds per proof).
|
||||
All fuzz targets **must** set `RISC0_DEV_MODE=1` in the environment and the `just`
|
||||
@ -632,7 +653,7 @@ flag stubs out ZK proof generation and replaces it with a fast mock implementati
|
||||
|
||||
---
|
||||
|
||||
## Mutation testing — the two planes
|
||||
## 🧬 Mutation testing — the two planes
|
||||
|
||||
Mutation testing here runs in two distinct planes, answering two different questions:
|
||||
|
||||
@ -666,7 +687,7 @@ from `data`; if a check doesn't depend on the input, write it as a unit test in
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations & Future Work
|
||||
## 🚧 Known Limitations & Future Work
|
||||
|
||||
| Item | Notes |
|
||||
|------|-------|
|
||||
|
||||
@ -35,7 +35,7 @@ mutant on **neither** list warrants a new corpus input.
|
||||
|
||||
---
|
||||
|
||||
## Why fuzzing is the wrong tool for these
|
||||
## 🧭 Why fuzzing is the wrong tool for these
|
||||
|
||||
Fuzzing earns its keep by exploring a large, *unknown* input space to find inputs
|
||||
a human wouldn't think of — malformed transactions, adversarial byte sequences,
|
||||
@ -74,7 +74,7 @@ to new unit tests — see the companion doc) were all removed.
|
||||
|
||||
---
|
||||
|
||||
## Catalogue (Group 1 — structurally unreachable by fuzzing)
|
||||
## 📋 Catalogue (Group 1 — structurally unreachable by fuzzing)
|
||||
|
||||
The nine mutations reported as MISSED by the `mutants-protocol` run for which
|
||||
fuzzing is structurally the wrong tool, with their true coverage. Verified by
|
||||
@ -143,7 +143,8 @@ fuzz input can reach this code. The `lee` crate exercises them directly.
|
||||
`state::tests::public_changer_claimer_data_change_no_claim_fails` and
|
||||
`public_changer_claimer_no_data_change_no_claim_succeeds`.
|
||||
|
||||
> Note: an earlier analysis guessed 6 and 7 were *equivalent mutants*. They are
|
||||
> [!NOTE]
|
||||
> an earlier analysis guessed 6 and 7 were *equivalent mutants*. They are
|
||||
> not — they are caught by Plane A, just not reachable by Plane B. They appear
|
||||
> "equivalent" only if you restrict yourself to the deployed `authenticated_transfer`
|
||||
> program, which is exactly the restriction fuzzing operates under.
|
||||
@ -165,7 +166,7 @@ fuzz input can reach this code. The `lee` crate exercises them directly.
|
||||
|
||||
---
|
||||
|
||||
## Group 2 — migrated input-independent targets
|
||||
## 🔁 Group 2 — migrated input-independent targets
|
||||
|
||||
These mutants used to be caught by Plane B via input-independent fuzz targets.
|
||||
Those targets were removed and their invariants ported to LEZ unit tests, so the
|
||||
@ -207,7 +208,7 @@ port *added* coverage rather than merely relocating it; those are marked **(new)
|
||||
|
||||
---
|
||||
|
||||
## Re-verifying
|
||||
## ✅ Re-verifying
|
||||
|
||||
From `logos-execution-zone/` with the fuzzing repo checked out as a sibling:
|
||||
|
||||
@ -226,6 +227,7 @@ registry; a mutation that the corpus replay (`just mutants-protocol`) catches
|
||||
belongs in the corpus instead. Across both groups, mutation #4 (the near-equivalent
|
||||
cycle-limit weak mutant) is the only one caught by **neither** plane.
|
||||
|
||||
> Tip: when reverting, prefer reverse-editing only the mutated line rather than
|
||||
> [!TIP]
|
||||
> when reverting, prefer reverse-editing only the mutated line rather than
|
||||
> `git checkout -- <file>` if you have uncommitted unit tests in the same file —
|
||||
> a whole-file checkout would discard them too.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user