Files
alktty/fuzz/README.md
T
glm-5.3-flash fb7cfe3851 feat(fuzz): session_opseq target - stateful op-sequence campaign against the session pump
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.
2026-09-28 10:06:00 +00:00

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.