Files
alktype/fuzz
glm-5.3-flash a0dd3d2de4 fix: W3-3 — read_field/write_field misread aligned maxLength reservations
The running validate_pair campaign found a third crash: in aligned
mode a maxLength reservation (ADR-003 strategy 2, VARCHAR(N)) stores
RAW zero-padded data with no length prefix — materialize and
validate_bytes implement exactly that — but read_field read the entry
through data_access::read_string, i.e. parsed the window's first four
bytes as a u32 length prefix. Raw reservation bytes that look like a
large prefix then fail bounds with Access while validate_bytes says
Ok: the validate⇒read agreement lattice breaks on every aligned
maxLength string/bytes field (any schema declaring maxLength in
aligned mode). write_field had the same mismatch (prefix+data into a
raw window).

Engine fix:
- VariableEncoding gains MaxLengthReserved (additive variant, ADR-003
  strategy 2). OffsetMap::compute records it for maxLength fields with
  the default encoding; maxLength+offset-indirect stays OffsetIndirect
  (the pair read is intentional, the window reserves max_len bytes),
  preserving the W3-1 combination semantics.
- read_field String/Bytes arms dispatch on MaxLengthReserved → new
  data_access::read_reservation_string / read_reservation (raw window
  inside-buffer check + NUL trim — the materializer's exact semantics).
- write_field dispatches → new data_access::write_reservation (zero-
  pads the window, rejects oversized values with Access).
- materialize_aligned reads MaxLengthReserved through the same new
  read_reservation paths (single source of truth; replaces the inline
  trim logic with an identical implementation).
- offset_map compute rejects a MaxLengthReserved encoding reaching the
  walk with a clean Offset error (recorded, never declared).
- builder round-trips: MaxLengthReserved serializes via maxLength (the
  document form), never as an encoding value.
- three engine regression tests: raw-not-prefixed read, zero-pad
  write + oversize rejection, validate⇒read_field agreement.
- fuzz/shared validate_pair invariant updated: the W3-1
  shorter-than-reservation exemption now applies to offset-indirect
  only; reservations assert the full window in-bounds (fixed engine).
- corpus regenerated for generator-consistent numbering (seeds 037-044
  relabeled; W3-1/W3-2 artifacts remain 044/045-047 → now 044, 048-050
  region) — 48 seeds, replay 30/30 green.

Verification: main crate 573 tests pass; clippy -D warnings clean
(crate + shared); wasm clean; cargo fuzz build clean.
2026-09-30 08:20:13 +00:00
..

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