Files
glm-5.3-flash 16b9023f60 fuzz: wave 2 — stateful read_opseq + layout_build targets, seeds, one engine fix
Targets 3-4 of docs/plans/fuzzing.md, per the sibling layout:

- fuzz/shared/src/read_opseq.rs — SequentialReader op sequences
  (Next/NextBorrowed/Field/Reset/End, Arbitrary-derived) over hostile
  buffers under the fixed packed schema menu. Invariants: cursor
  discipline (failed read leaves position untouched, state replay
  deterministic), None sticky at plan end, plan-order full walks with
  a spin bound, read_field leaves a usable reader, ADR-007 reader
  independence (shared Arc, isolated cursors), and the plan §6-1
  record-count ≥4-verified-bytes bound encoded as an explicit End-op
  assertion.
- fuzz/shared/src/layout_build.rs — LayoutBuilder::build with
  adversarial var_sizes over a five-schema menu (string/bytes, nested
  struct, byte-disc union, record+array, fixed control). Invariants:
  Offset-class failures only, position disjointness + total-size
  bounds, variable fields record their 4-byte prefix, failed writes
  leave the buffer byte-identical, write→read pair round trip.
- derive_var_sizes discovers the synthetic keys ('p.__discriminator')
  the builder actually wants by parsing the quoted key from the
  Offset reason.
- 73 committed seeds (58 read_opseq + 15 layout_build) hand-encoded
  against the pinned arbitrary 1.4.2 derive layout (4-byte LE
  multiply-shift variant selectors, keep-going vec elements,
  take-rest last field) and pinned by decode_lands_on_the_intended_variants
  replay tests; gen_fuzz_seeds.py mirrors the encoders.
- Engine fix (finding W2-1): plan_read_array returned Ok for a
  fixed-stride array whose count*stride window extended past the
  buffer — the struct/union arms bounds-check, the array arm did not;
  a truncated array deferred the failure to the next field (wrong
  path) or masked it entirely as an Ok walk. Now an Access error
  naming the array, regression test in sequential_reader.rs.
- Packed-mode 'encoding: offset-indirect' pinned as the documented
  inline-length-prefix no-op (finding W2-2, bast-format.md Default
  strategy selection); open design question recorded as plan §6-7.

Verification: fuzz corpus replay 19/19; main crate 570 tests incl.
the new regression; clippy -D warnings clean (crate + shared); wasm
build clean; cargo fuzz build clean (nightly confined to fuzz/).
Smoke campaigns (10 min detached each): read_opseq 52.1k execs exit 0
empty artifacts, layout_build 42.4k execs exit 0 empty artifacts; no
crash/oom/timeout on any fork job.
2026-09-30 06:26:05 +00:00

2.8 KiB

alktype fuzzing

cargo-fuzz targets for the binary struct engine's untrusted-input surfaces. The design and operating rules live in docs/plans/fuzzing.md (adopted from alkhttp's docs/plans/fuzzing.md; rationale in 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
bast_compile AlkTypeEngine::compile in both layout modes over attacker-shaped BAST JSON (the whole schema side through one choke point) + validate_bast_doc + build_validator
data_access the hand-rolled decode core (src/data_access.rs): fixed-width kinds, bool strictness, length-prefixed and indirect string/bytes, enums — over raw bytes with attacker-chosen offsets and endianness
read_opseq the stateful SequentialReader (packed read side): op sequences (Next / NextBorrowed / Field / Reset / End) over hostile buffers under the compiled plan — cursor discipline, plan-order walks, the record-count spin bound, ADR-007 reader independence
layout_build the packed write side (LayoutBuilder::build) with adversarial var_sizes over a five-schema menu — position disjointness/bounds, failed-write buffer-untouched contracts, write→read pair round trip

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 §2). Use the detached runner:

fuzz/run-detached.sh bast_compile
# poll:
tail -n 50 fuzz/artifacts/bast_compile-*.log
ls fuzz/artifacts/bast_compile/
pgrep -f "cargo fuzz run bast_compile"

FUZZ_RUNTIME_SECS=1800 fuzz/run-detached.sh bast_compile 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)

cargo test --manifest-path fuzz/shared/Cargo.toml

replays every committed seed through the same invariant functions the fuzz targets run — on stable, without nightly, no cargo-fuzz. Part of the release verification checklist (AGENTS.md).

Toolchain

fuzz/rust-toolchain.toml pins nightly (+ llvm-tools-preview) for this subtree only; the main crate stays stable at MSRV 1.85. cargo fuzz build works from any CWD inside fuzz/ (rustup resolves the toolchain per directory). Build:

cd fuzz && cargo fuzz build
# or from the repo root — the toolchain file is picked up by path:
cargo fuzz build -D