fuzz campaigns + gate: wave 7 close — three 15-min detached campaigns clean (payload_codegen finding harness-model: serde_json default f64 parse drifts 1-2 ULPs; invariant pinned to 2-ULP float-leaf drift + decode determinism, crash input promoted as payload_codec/seed-900), fuzz/.gitignore retightened to seed-* tracking, corpus-replay gate joins AGENTS.md verification list, research doc §9 implementation log, README finalized
This commit is contained in:
1 parent
5043f7bcd8
commit
f7de30e57e
7 files changed
+237
-25
No files matched your search
@@ -1,8 +1,10 @@
|
||||
# Fuzzing alkstore: Research and Plan
|
||||
|
||||
**Status:** decided — adopted (2026-10-10), inserted as wave 7 in
|
||||
`docs/plans/implementation.md` (release readiness renumbered to 8).
|
||||
Targets not yet implemented; decomposition pending. Campaigns will run
|
||||
**Status:** landed — adopted (2026-10-10), inserted as wave 7 in
|
||||
`docs/plans/implementation.md` (release readiness renumbered to 8);
|
||||
wave 1's three targets implemented, seeds committed, detached campaigns
|
||||
run (§9 log), the corpus-replay gate on AGENTS.md's verification list.
|
||||
Wave 2 (`mem_opseq`, §3.4) stays deferred post-release. Campaigns run
|
||||
via the detached runner (§5) — **never as a foreground child of an
|
||||
agent session** (the family's §5 rule, inherited verbatim).
|
||||
**Date:** 2026-10-10
|
||||
@@ -114,7 +116,7 @@ Upstream layout rules that carry over, in alkstore terms:
|
||||
binaries — the stable-toolchain corpus replay
|
||||
(`cargo test --manifest-path fuzz/shared/Cargo.toml`) runs the SAME
|
||||
invariant functions the fuzzer runs. This is the fuzz gate (§6);
|
||||
it joins AGENTS.md's verification commands when wave 7 lands.
|
||||
it is on AGENTS.md's verification commands (landed with wave 7, §9).
|
||||
- **Workspace/publish exclusion:** this repo's root `Cargo.toml` is a
|
||||
workspace-only manifest (no root package) with an explicit members
|
||||
list, so cargo will not auto-discover `fuzz/shared/` — but the
|
||||
@@ -318,8 +320,8 @@ The standing policy applies identically (per all four upstream docs):
|
||||
no hosted CI; no Actions/Gitea Actions workflow files. **Corpus replay
|
||||
is the fuzz gate as plain `cargo test`**:
|
||||
`cargo test --manifest-path fuzz/shared/Cargo.toml` replays every
|
||||
committed seed through the invariant functions. It joins
|
||||
AGENTS.md's verification commands when wave 7 lands. Campaigns run
|
||||
committed seed through the invariant functions. It is on
|
||||
AGENTS.md's verification commands (landed with wave 7, §9). Campaigns run
|
||||
manually via the detached runner — e.g. before the release, or after
|
||||
touching `payload.rs`, the three parsers, or `validation.rs`.
|
||||
|
||||
@@ -355,4 +357,80 @@ Campaign flag starting points (per target, tuned at wave 7):
|
||||
ADR-020 §4 (payload encoding posture), ADR-023 §1 (encode
|
||||
fallibility), ADR-024 §5 (mem wasm acceptance); the wave-6 review
|
||||
gate's overflow findings (`tasks/review-wave-6.md`);
|
||||
`docs/plans/implementation.md` §Decided points (the adoption record).
|
||||
`docs/plans/implementation.md` §Decided points (the adoption record).
|
||||
|
||||
---
|
||||
|
||||
## 9. Implementation log — wave 7 (2026-10-10)
|
||||
|
||||
- **2026-10-10 — wave 1's three targets implemented** (the
|
||||
decomposition's target tasks, all via the §2 layout: stable
|
||||
`fuzz/shared` invariant library, thin `fuzz_target!` binaries,
|
||||
committed seed corpora from `gen_fuzz_seeds.py` — 97 + 66 + 44).
|
||||
§3.2 resolved to the option-*(b)* `#[doc(hidden)]` exposure
|
||||
(doc-hidden re-exports of the three parsers + `skip_forward`; the
|
||||
every_parse campaign's two earlier findings — the boundary-arithmetic
|
||||
division-by-zero/negative-interval wrap and the i64 gap-subtraction
|
||||
overflow, fixed crate-side and registered as PROVENANCE D-38). The
|
||||
§3.2 suite-companion option-*(a)* added no suite rows — suite-pinned
|
||||
rows already carried equivalence; noted here per the decomposition's
|
||||
resolution scope.
|
||||
- **2026-10-10 — first campaign finding: the `Value` round-trip
|
||||
invariant over-tightened (harness-model, not a crate bug).** The
|
||||
`payload_codec` campaign crashed ~38 s in on raw input
|
||||
`46741844073709.51615` (`crash-0ce050f1…`, exit 77):
|
||||
`decode(encode(v)) ≠ v` — `46741844073709.52` vs `46741844073709.516`.
|
||||
Reproduced and characterized probe-side (serde_json 1.0.152): its
|
||||
**default fast-path f64 parser is not exactly correctly-rounded** —
|
||||
parsing serde_json's own ryu serialization can drift 1–2 ULPs
|
||||
(measured over 600k random-bits finite f64s: ~30% fail one
|
||||
parse∘serialize∘parse pass, `max drift = 2.00` ULP, overwhelmingly
|
||||
exactly 1 ULP; the one-pass re-parse is **not a fixpoint** at extreme
|
||||
significand/exponent shapes), so the byte-level closure holds only
|
||||
outside float leaves. This is serde_json's *documented shipping
|
||||
behavior*, and [ADR-020](../architecture/decisions/020-enqueue-opt-semantics-and-bridges.md)
|
||||
§4 pins exactly that — "not pinned to a serde_json *feature flavor*
|
||||
… beyond default shipping behavior" — so enabling its
|
||||
`float_roundtrip` feature would be an ADR deviation, not a fix.
|
||||
The harness was the defect: it pinned the suite's example-based
|
||||
round-trip equality (authored floats are ryu-stable) over the whole
|
||||
raw input space. Fixed in `fuzz/shared`: the closure is now exact
|
||||
outside `Number` float leaves, each float leaf drift-bounded at
|
||||
2 ULPs (pinned-not-regressed), plus a decode-determinism pin (two
|
||||
parses of the same stored bytes are identical). The crash input is
|
||||
promoted to `corpus/payload_codec/seed-900`; the campaign re-ran
|
||||
clean.
|
||||
- **2026-10-10 — wave-1 15-minute detached campaigns run** (§6 runner,
|
||||
`-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25`;
|
||||
flags per §7): all three exited 0, `oom/timeout/crash: 0/0/0`
|
||||
throughout, empty per-target artifact dirs at completion (the one
|
||||
first-run crash artifact above is the promoted finding's evidence).
|
||||
- `payload_codec` (post-fix re-run; the pre-fix run died at the
|
||||
finding) — 927 s, ~586k execs (~650 exec/s), 41 fork jobs,
|
||||
coverage 2395 edges (ft 7608, in-memory corp 1544 from the 98
|
||||
committed seeds).
|
||||
- `every_parse` — 927 s, ~3.7M execs (~4.1k exec/s), 41 fork jobs,
|
||||
coverage saturated at 285 edges (ft 3898, corp 805 from the 66
|
||||
committed seeds) — the three parsers' grammar space is fully
|
||||
enumerated post-fix; the earlier boundary-arithmetic findings were
|
||||
the campaign-relevant depth.
|
||||
- `name_validation` — 939 s, ~59k execs (~66 exec/s), 41 fork jobs,
|
||||
coverage 327 edges (ft 3248, corp 378 from the 44 committed
|
||||
seeds); the corpus-replay seeds already covered the domain guards,
|
||||
and the slow exec/s profile is the long-string shapes, not a
|
||||
harness defect.
|
||||
- Grown corpora were **not** merged into the committed seeds (the
|
||||
§2 policy; `fuzz/.gitignore` now tracks only `seed-*` files —
|
||||
the scaffold's directory-level negation leaked SHA-named grown
|
||||
units into `git status` during the campaigns, fixed here).
|
||||
- **NO crate bugs found by the campaigns.** The sole campaign finding
|
||||
was the harness-model defect above (fixed harness-side); every
|
||||
contract invariant held across the adversarial input space: the
|
||||
typed-failure partition (`Codec` only), deep-input loudness at
|
||||
serde_json's recursion limit, the empty-byte-store loudness
|
||||
(ADR-023 §1), the typed round-trip batteries, the three-way parser
|
||||
agreement and overflow posture, and name-validation
|
||||
determinism/typedness. That is the family's
|
||||
successful-negative-result posture (alksocks §7's first campaigns:
|
||||
zero crate bugs after the two earlier wave-7 crate-side
|
||||
boundary-arithmetic fixes, which the every_parse task itself landed).
|
||||
Reference in new issue
Block a user