docs(fuzz): record wave-3 campaign results + FWD-21

This commit is contained in:
glm-5.3-flash committed 2026-09-29 17:28:23 +00:00
1 parent e023d3275e
commit 4e8a2cfddd
1 file changed
+89 -20
+89 -20
View File
@@ -10,8 +10,11 @@ survey, CI tiers) — this plan stays focused on what alkhttp fuzzes and
in what order.
**Status:** targets 1–3 implemented and verified (1–2 on 2026-09-29,
wave 1; 3 on 2026-09-29, wave 2); target 4 is planned and can be
handed to subsequent sessions.
wave 1; 3 on 2026-09-29, wave 2); target 4 implemented (2026-09-29,
wave 3: `ws_session_opseq` + `retry_throttle`; campaign results in
§5); the only crash found so far (FWD-21, the `WriteChunkWalk`
failure-carry drop) was a crate contract fix, not a harness
misassert.
Progress log:
@@ -45,6 +48,29 @@ Progress log:
10 min, zero crash/OOM/timeout, coverage still growing at budget
end (see §5). One harness-assert fix on the way (the quoted-`""`
YAML key noted above).
- **2026-09-29 — target 4 (`ws_session_opseq` + `retry_throttle`)
implemented (wave 3).** Two stateful harnesses + 16 seeds + the
`byte_adapter::fuzzing` drive hub (generic pump bodies over a mock
socket flavor) + the `client::fuzzing` throttle hub. One crate
finding on the way: FWD-21 (above) — fixed same commit.
- **2026-09-29 — FWD-21, the `WriteChunkWalk` failure-carry drop.**
Found minutes into the first `ws_session_opseq` campaign: one feed
that drains a complete chunk and then hits an oversize header (or
the pending cap) in the same call returned
`Walk::Oversize`/`PendingOverCap` and **discarded the already
drained pieces** — the walk's own consumption-accounting contract
("every parsed byte is either emitted or retained", wave-1
invariant 6) was violated by the crate, not the harness. Production
could not reach the shape (the mux's per-write invariant caps
`pending` at one chunk + header), so impact was nil, but
`WriteChunkWalk` is public API and the wave-1 `split_walk` equality
check was silently skipped on exactly this path. Fix (user-approved
semver-relevant API change): `Walk::Oversize(Vec<Vec<u8>>)` /
`Walk::PendingOverCap(Vec<Vec<u8>>)` carry the pre-failure pieces;
the write pump emits them before `fail_write_pump`, so a 1011
close never swallows a legal chunk the mux already counted as
written. Regression seeds cover the drain-then-fail shape in both
chunk harnesses.
---
@@ -141,7 +167,7 @@ checklist, as the siblings did.
| 1 | `sse_parser` | `SseParser::feed` + `sse_event_envelope` mapping (`src/adapters/forward.rs`) | chunk-split sequence × eof flag | implemented 2026-09-29 |
| 2 | `ws_chunk_framing` | the WS↔byte-adapter chunk parsers (`WriteChunkWalk`, the write pump's extracted chunk walk) (`src/websocket/byte_adapter.rs`) | byte sequences + message-split boundaries | implemented 2026-09-29 |
| 3 | `spec_import` | `OpenAPISpec::from_str`/`from_yaml`/`from_json`, `CompiledInputSchema`/`harden_closed_by_default`/`validate`, `FromMCP::build_spec` (`src/adapters/openapi_spec.rs`, `from_jsonschema.rs`, `from_mcp/mod.rs`) | attacker-shaped OpenAPI/JSON/YAML docs + schemas | implemented 2026-09-29 |
| 4 | `ws_session_opseq` / `retry_throttle` | stateful op sequences over the WS byte-adapter pumps (write/read pump interleavings, close timing, oversize frames); `RetryAfterMiddleware` throttle-map growth | `#[derive(Arbitrary)]` op enums | planned (second wave) |
| 4 | `ws_session_opseq` / `retry_throttle` | stateful op sequences over the WS byte-adapter pumps (write/read pump interleavings, close timing, oversize frames); `RetryAfterMiddleware` throttle-map growth | `#[derive(Arbitrary)]` op enums | implemented 2026-09-29 |
### Target 1 — `sse_parser` (the chunk_header-class target)
@@ -276,24 +302,39 @@ the chunk-walk loop and the pending accumulator.
duplicate-key rejection with line/column) as corpus expectations.
42 committed seeds cover the menu below.
### Target 4 — stateful wave (planned)
### Target 4 — stateful wave (implemented 2026-09-29)
- **`ws_session_opseq`:** `#[derive(Arbitrary)]` op sequences over
the WS byte adapter driven end-to-end (like alktty's
`session_opseq` / alkcall's `manager_routing`): write-side ops
(`write(bytes)` ≤ cap, `shutdown`, oversized-write),
read-side ops (feed messages, close, text message, oversize
message), close timing both sides. Asserts: backpressure
accounting (`READ_SLOTS`/`WRITE_SLOTS`), idle-timeout resets only
on chunk progress (WS-01 semantics), close frames only at legal
points, EOF sentinel semantics preserved, the alkcall 0.8.1 bug
class (a rejected duplicate must leave live state intact) never
reachable through this adapter.
- **`retry_throttle`:** bounded-map growth of
`RetryAfterMiddleware` (the inlined middleware): capacity bound,
Farthest-future drop policy, ceiling clamp, expired-first
eviction, jitter bounds; op sequences of responses with arbitrary
`Retry-After` values (seconds, HTTP-date, garbage).
(well-formed chunks, oversize headers, the pending shape),
read-side ops (binary deliveries, dribble, text, inbound read
error, peer close), a lane-healthy requested close, shutdown vs
close ordering, backpressure scaled 1..=8 read slots. Asserts:
lossless inbound forwarding (forwarded == delivered exactly after
the teardown joins, prefix mid-sequence), the WS-01 walk's
completed-chunk count vs the declared frame count, the loud-gate
close mapping (1002/1011/verbatim-requested + a per-cause reason,
WS-15), lossless write framing (outbound concat == the declared
chunk stream incl. the FWD-21 failure-carry), lane-failure
isolation (the alkcall 0.8.1 class), and pump termination within
the harness bound. Exposure: `byte_adapter::fuzzing`
(`FuzzMockSocket` flavor, `FuzzChannels`/`FuzzWriteSide` channel
bundle, pump spawn funnels, `FuzzInboundChunkProgress`).
- **`retry_throttle`:** bounded-map growth of the inlined
`RetryAfterMiddleware`: capacity bound (exact, distinct-URL
observable partition), expired-first + Farthest-future eviction
policy, ceiling clamp (deadline never grows past the ceiling of
the record moment), refresh contraction (a higher `Retry-After`
never extends the first-seen deadline; the missing-header refresh
is a documented no-op), `Retry-After` parse partition
(non-negative seconds ∪ future HTTP dates, post-clamp), and the
jitter bounds (≤ 25% of a short wait, capped at 2 s). Op sequences
of records/refreshes/queries/rebuilds with seconds, huge-seconds,
HTTP-date, past-date, garbage, and no-header shapes. Exposure:
`client::fuzzing` (constructor, synthetic throttled response,
record/deadline/parse/jitter funnels — the middleware map never
leaks).
- Sequenced after 1–3 stabilize, per the sibling pattern.
## 4. Corpus policy
@@ -323,8 +364,16 @@ grown corpora and artifacts gitignored. Seed menus:
`$ref`; `additionalProperties` absent (hardening probe) and
present both ways.
- Stateful seeds (target 4): the semantic fixtures the campaign must
always replay — e.g. an adopted-then-teardown interleaving, a
close-during-stream, a throttle-map cycle at capacity.
always replay — a write/echo flow, the oversize loud-gate after a
clean chunk, the text-rejection + surviving write lane, an inbound
read error, a peer close mid-flight + shutdown drain, a requested
close, the throttle-map fill/evict cycle at capacity, the
missing-header refresh no-op, hostile-seconds clamping, refresh
contraction, and a mid-sequence rebuild (16 total; the
`ws_session_opseq`/`retry_throttle` blocks in
`fuzz/gen_fuzz_seeds.py`, hand-encoded against the `arbitrary`
1.4.x derive layout with ceiling-division tags and per-element
keep-going bytes, pinned by seed-decode tests).
## 5. Campaign + gate policy
@@ -376,7 +425,21 @@ artifact dirs empty):
- Toolchain notes live in `fuzz/README.md`; nightly stays confined to
`fuzz/`.
## 6. Sequencing
Wave-3 stateful smoke campaigns (2026-09-29, 10 min per target, both
exited 0, artifact dirs empty — no crash/hang/OOM/leak; the first
`ws_session_opseq` launch aborted at 17 s on FWD-21, fixed before
the counted run):
- `ws_session_opseq` — 206k execs at ~340/s (each exec interleaves up
to 64 two-lane ops with yield quiesce points per exec, so per-exec
work is heavy), coverage 2251 edges / 5831 features / 232 in-memory
corpus entries — coverage still growing at budget end.
- `retry_throttle` — 897k execs at ~1.4k/s, coverage saturated at
1154 edges / 6925 features / 324 corpus entries.
- **Seeds regenerate deterministically:** `python3
fuzz/gen_fuzz_seeds.py`.
- Toolchain notes live in `fuzz/README.md`; nightly stays confined to
`fuzz/`.
1. ✅ **Done (2026-09-29, commit 5971c0a)** — infra (workspace
exclude, toolchain pin, runner, seed generator, README) + targets
@@ -389,5 +452,11 @@ artifact dirs empty):
modules. Smoke campaign clean (see §5).
3. **Next** — target 4 stateful wave + longer campaigns; revisit
corpus merges if any campaign yields inputs worth curating.
4. ✅ **Done (2026-09-29, wave 3)** — target 4 stateful
(`ws_session_opseq` + `retry_throttle`): two harnesses + 16 seeds
+ the `byte_adapter::fuzzing` / `client::fuzzing` hubs + FWD-21
(the `WriteChunkWalk` failure-carry drop — a crate contract fix;
user-approved semver-relevant API change). Smoke campaigns clean
(see §5). Longer (30+ min) campaigns before the next release.
Status updates land in §3's table and §6's list as work completes.