Target 4 from the fuzzing plan (the alkcall-bug-class hunt): a #[derive(Arbitrary)] op sequence drives the producer session pump (drive_session_pre_negotiated) over a duplex pair against a fuzz-local mock backend. - fuzz/shared/src/session_opseq.rs: op vocabulary (client writes incl. invalid-type/oversize-length barrier probes, backend production/EOF, exit resolve/fail/drop, control messages, bounded reads, yields), kill-guard exit future (ADR-005 observability), delivery model, and the invariants: kill-on-Drop (kill_fired == !exit_resolved), exit chunk is last with first-issued code -1 on wait-failure (ADR-004), lossless per-stream FIFO + single stdout sentinel, stdin prefix-losslessness (post-shutdown chunks never delivered), control dispatch bounds, take-once allocation, teardown termination. - Harness-model correction caught by a 60s smoke run before the campaign (the alkcall §7.7 pattern, third time): a failed client write can mean the session completed and dropped the server duplex half while the write was in flight - the harness now drains and requires the exit chunk on any write/shutdown error (premature close without it is a finding). No adapter code changed. - 12 committed seeds via the deterministic generator, hand-encoded against the arbitrary 1.4.x derive layout; the encoding is pinned by a seed-decode test. Existing chunk_frame/negotiation_frame/ control_json seed corpora are byte-identical to before. - fuzz/.gitignore: the per-target seed negations never matched (corpus/* excludes the parent dir; git cannot re-include beneath an excluded dir). Fixed with the !corpus/*/ + corpus/*/* + !corpus/*/seed-* recipe - new seed files were silently ignored until now. - Campaign: 10 min detached via run-detached.sh, exited 0, artifact dir empty - 42.3k execs across 33 fork jobs, 0 crash/oom/timeout, cov 3934 -> 4075 edges. Grown corpus excised per the corpus policy. Verification: corpus replay green on stable (9 tests), cargo test 113 + --all-features 156, clippy stable/wasm/fuzz-shared, fmt, doc, publish dry-run, wasm check - all pass.
73 lines
3.0 KiB
Markdown
73 lines
3.0 KiB
Markdown
# alktty fuzzing
|
|
|
|
cargo-fuzz targets for the wire-facing parse surfaces. The design and
|
|
operating rules live in `docs/plans/fuzzing.md` (adopted from alkcall's
|
|
`docs/research/fuzzing.md`); this README is the operational cheat-sheet.
|
|
|
|
## Layout
|
|
|
|
- `fuzz_targets/` — nightly-only `fuzz_target!` binaries (thin wrappers).
|
|
- `shared/` — stable-toolchain library holding the invariant logic; the
|
|
corpus replay tests run here on plain `cargo test`.
|
|
- `corpus/<target>/` — committed seeds (regenerate with
|
|
`python3 fuzz/gen_fuzz_seeds.py`).
|
|
- `artifacts/` — gitignored crash/oom/timeout artifacts + campaign logs.
|
|
|
|
## Targets
|
|
|
|
| Target | Drives |
|
|
|---|---|
|
|
| `chunk_frame` | `ChunkReader`/`ChunkWriter` (5-byte chunk codec, ADR-001) |
|
|
| `negotiation_frame` | `NegotiationReader`/`NegotiationWriter` + `NegotiateRequest` + `error_response_bytes` + the cross-codec peek-disambiguation seam |
|
|
| `control_json` | `ControlMessage::from_slice`/`to_json` + `signal_from_name` |
|
|
| `session_opseq` | the producer session pump (`drive_session_pre_negotiated`) + a fuzz-local mock backend — the stateful op-sequence target (ADR-004/ADR-005 invariants; the alkcall-bug-class hunt) |
|
|
|
|
## Running a campaign — always detached
|
|
|
|
Agent sessions must never run fuzzing in the foreground (an OOM in a
|
|
target can take down the session host; see docs/plans/fuzzing.md §3).
|
|
Use the detached runner:
|
|
|
|
```bash
|
|
fuzz/run-detached.sh chunk_frame
|
|
# poll:
|
|
tail -n 50 fuzz/artifacts/chunk_frame-*.log
|
|
ls fuzz/artifacts/chunk_frame/
|
|
pgrep -f "cargo fuzz run chunk_frame"
|
|
```
|
|
|
|
`FUZZ_RUNTIME_SECS=1800 fuzz/run-detached.sh negotiation_frame` for a
|
|
longer campaign. The runner pins `-fork=1 -rss_limit_mb=2048
|
|
-malloc_limit_mb=2048 -timeout=25` and detaches via `setsid` + `nohup`.
|
|
|
|
## Corpus replay (the standing fuzz gate)
|
|
|
|
```bash
|
|
cargo test --manifest-path fuzz/shared/Cargo.toml
|
|
```
|
|
|
|
Replays every committed seed through the same invariant functions the
|
|
fuzzer runs, on stable, no nightly needed. Part of the release
|
|
verification checklist (AGENTS.md).
|
|
|
|
## Toolchain notes
|
|
|
|
`rust-toolchain.toml` pins nightly for this subtree only (llvm-tools
|
|
required by cargo-fuzz); the crate itself stays on stable at MSRV 1.88.
|
|
`cargo fuzz build` must be run with the fuzz dir as CWD or trust the
|
|
toolchain file (rustup resolves per directory).
|
|
|
|
Run campaigns before releases and after touching `src/wire.rs`,
|
|
`src/negotiation.rs`, `src/control.rs`, the adapter, or the session pump.
|
|
|
|
## session_opseq notes
|
|
|
|
The op-sequence target is inherently slower than the byte-format targets
|
|
(each input drives a full tokio session with bounded reads and yields —
|
|
tens of exec/s per worker, not millions). That is expected: the value is
|
|
state-space exploration, not throughput. It uses `tokio::time::timeout`
|
|
internally, so the nightly target needs the `--features time` tokio
|
|
surface already present via the shared crate's deps — no extra flags.
|
|
Its invariants (kill-on-Drop, exit-chunk-is-last, sentinel/FIFO
|
|
semantics) are the ones unit tests assert individually; the fuzzer
|
|
explores interleavings no example test can enumerate. |