144 lines
7.5 KiB
Markdown
144 lines
7.5 KiB
Markdown
---
|
||
id: fuzz-campaigns-and-gate
|
||
name: Fuzz campaigns (detached) + corpus-replay gate wiring + AGENTS.md verification-list flip
|
||
status: completed
|
||
depends_on: [fuzz-target-payload-codec, fuzz-target-every-parse, fuzz-target-name-validation]
|
||
scope: moderate
|
||
risk: medium
|
||
impact: phase
|
||
level: implementation
|
||
tags: [wave-7, fuzzing]
|
||
---
|
||
|
||
## Description
|
||
|
||
The wave's operational close (§4's sequencing tail: seeds committed →
|
||
campaigns → fix anything found → corpus replay green) plus the two
|
||
wiring deliverables the plan names:
|
||
|
||
- **Campaigns, detached only** (§5 inherited verbatim): per target, via
|
||
`fuzz/run-detached.sh` — **never a foreground child of an agent
|
||
session**. Budget 10–30 min per target (`FUZZ_RUNTIME_SECS`), flags
|
||
per §7's starting points: `payload_codec -max_len=65536
|
||
-dict=json.dict`; `every_parse -max_len=4096 -use_value_profile=1`;
|
||
`name_validation -max_len=4096 -use_value_profile=1`. Poll per §5
|
||
(`tail` the log, `ls` the artifacts dir, `pgrep -af` to LIST, kill by
|
||
explicit pid from the runner's echoed `pid=` — never
|
||
pattern-kill-and-recheck in one call).
|
||
- **Findings disposition**: crate-side bugs are fixed (the fix rides
|
||
this task when small; crash inputs are promoted to committed seeds —
|
||
the alksocks seed-900/901 posture — and the campaign re-run clean);
|
||
large or design-shaped findings escalate to the user before any
|
||
postulate-and-fix. Harness-model findings (no crate bug) are
|
||
documented in the research doc's implementation log (a new §
|
||
appended to `docs/research/fuzzing.md` — the alksocks §7
|
||
successful-negative-result disposition applies if campaigns find
|
||
nothing crate-side).
|
||
- **Corpus policy final state** (§2): committed hand-made seeds +
|
||
promoted crash inputs; grown corpora and artifacts gitignored (the
|
||
scaffold's `fuzz/.gitignore` pattern); verify `git status` shows no
|
||
grown-corpus noise after campaigns.
|
||
- **The AGENTS.md verification-list flip**: the corpus-replay gate
|
||
joins the Verification Commands —
|
||
`cargo test --manifest-path fuzz/shared/Cargo.toml` — with the
|
||
one-line note that campaigns run only via the detached runner (the
|
||
AGENTS.md fuzzing paragraph's "until then there is no fuzz gate"
|
||
clause updated to the landed state). This is the plan's named design
|
||
point ("joins the verification list when the wave lands").
|
||
- **`fuzz/README.md` finalized**: targets table complete, campaign
|
||
commands, corpus policy.
|
||
- **Research doc §7's verification list** already carries the gate —
|
||
no edit needed there beyond the implementation log.
|
||
|
||
## Acceptance Criteria
|
||
|
||
- [x] Every target has had a detached campaign (≥10 min) via
|
||
`run-detached.sh`; logs + artifact state recorded in Notes
|
||
- [x] Campaigns clean, or findings fixed + crash inputs promoted to
|
||
committed seeds + re-run clean; dispositions recorded (crate-side
|
||
vs harness-model; the latter in the research doc's log)
|
||
- [x] Corpus policy verified: committed seeds only in git; grown
|
||
corpora/artifacts ignored; `gen_fuzz_seeds.py` re-run is a no-op
|
||
diff on the committed set
|
||
- [x] `cargo test --manifest-path fuzz/shared/Cargo.toml` green
|
||
(the gate, on stable)
|
||
- [x] AGENTS.md Verification Commands carry the gate line; the fuzzing
|
||
paragraph reflects the landed state
|
||
- [x] `fuzz/README.md` finalized (targets table, commands, corpus
|
||
policy)
|
||
- [x] Root workspace gates + `shared` clippy/fmt gates green; mem wasm
|
||
check still clean
|
||
|
||
## References
|
||
|
||
- docs/research/fuzzing.md §4 (sequencing), §5 (detached-runner rule),
|
||
§6 (no-CI / the gate), §7 (flags + verification commands)
|
||
- docs/plans/implementation.md §Wave 7 (the named design points this
|
||
task closes: the AGENTS.md flip)
|
||
- /workspace/@alkdev/alksocks/docs/research/fuzzing.md §5, §7 (the
|
||
inherited operational posture + the successful-negative-result
|
||
disposition)
|
||
- tasks/fuzz-scaffold.md (the runner + gitignore this task exercises)
|
||
|
||
## Notes
|
||
|
||
- **One campaign finding, harness-model — no crate bugs (§9 log in the
|
||
research doc carries the full disposition).** The `payload_codec`
|
||
campaign crashed ~38 s in on `"46741844073709.51615"`:
|
||
`decode(encode(v)) ≠ v` for f64 leaves. serde_json's default
|
||
fast-path f64 parse of its own ryu output drifts 1–2 ULPs
|
||
(probe-measured: ~30% of random-bits finite f64s fail one
|
||
parse∘serialize∘parse pass; max drift exactly 2.00 ULP; not a
|
||
fixpoint at extreme significand shapes). ADR-020 §4 pins default
|
||
serde_json ("not pinned to a feature flavor"), so the crate-side
|
||
`float_roundtrip` route is an ADR deviation, not a fix — harness
|
||
defect. Fixed in `fuzz/shared/src/payload_codec.rs`: exact closure
|
||
outside `Number` float leaves, float leaves drift-bounded at
|
||
2 ULPs (`FLOAT_DRIFT_ULPS`, pinned-not-regressed), plus a
|
||
decode-determinism pin. Crash input promoted to
|
||
`corpus/payload_codec/seed-900`; re-run clean.
|
||
- **`fuzz/.gitignore` retightened (its final state was this task's to
|
||
set).** The scaffold's directory-level negations (`!corpus/<target>`)
|
||
leaked SHA-named grown-corpus units into `git status` while
|
||
campaigns ran (libFuzzer fork mode merges new units into the first
|
||
corpus dir). Now: dirs re-included, contents ignored,
|
||
`!corpus/<target>/seed-*` back — committed seeds + promoted crash
|
||
inputs in git, grown corpora invisible.
|
||
- **Campaigns ran in parallel** (15-min budget each, `FUZZ_RUNTIME_SECS=
|
||
900`): logs `fuzz/artifacts/{payload_codec-20261010-221350,221951,
|
||
every_parse-20261010-221351,name_validation-20261010-221352}.log`
|
||
(the 221350 log is the pre-fix crash evidence; the 215619 log is the
|
||
every_parse task's earlier 180 s smoke run, superseded). Polling
|
||
note: the runner's children match `pgrep -af cargo-fuzz` (hyphen),
|
||
not `cargo fuzz`.
|
||
- **`gen_fuzz_seeds.py` no-op verified twice**: with the wave's edits
|
||
stashed and with them applied, a re-run produced a zero diff on the
|
||
committed corpus set (seed-900 is promotion, not generator output).
|
||
- **serde_json version pin observed**: the drift characterization was
|
||
against 1.0.152 (`fuzz/Cargo.lock`); the 2-ULP bound is
|
||
pinned-not-regressed — a serde_json upgrade may only tighten it.
|
||
|
||
## Summary
|
||
|
||
Wave 7's operational close landed. Three 15-minute detached campaigns
|
||
(`run-detached.sh`, `-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048
|
||
-timeout=25`; `payload_codec -max_len=65536 -dict=json.dict`,
|
||
`every_parse`/`name_validation -max_len=4096 -use_value_profile=1`)
|
||
all exited 0, `oom/timeout/crash: 0/0/0`, empty artifact dirs at
|
||
completion: `payload_codec` ~586k execs (~650 exec/s, 2395 edges, corp
|
||
1544); `every_parse` ~3.7M execs (~4.1k exec/s, 285 edges saturated,
|
||
corp 805); `name_validation` ~59k execs (327 edges, corp 378). The one
|
||
finding (payload_codec, ~38 s in) was harness-model — the `Value`
|
||
round-trip invariant over-tightened against serde_json's default f64
|
||
parse-precision posture — fixed harness-side crash-input-promoted, and
|
||
the re-run is clean; no crate bugs (the research doc's
|
||
successful-negative-result §9). Corpus policy final state verified:
|
||
only `seed-*` tracked, grown corpora ignored, generator re-run a
|
||
no-op. The gate joined AGENTS.md's Verification Commands and the
|
||
fuzzing paragraph reflects the landed state; `fuzz/README.md` finalized
|
||
(targets table incl. the float-drift pin, commands, corpus policy);
|
||
research doc §9 appended and its §2/§6 "when wave 7 lands" clauses
|
||
updated. Verified: workspace build/test, root + `shared`
|
||
clippy/fmt, the `shared` corpus-replay gate (6 tests incl. the 208
|
||
committed seeds), and `cargo check -p alkstore-mem --target
|
||
wasm32-unknown-unknown` all green. |