- Rewrote em dash prose across the whole tree outside docs/reviews/ (the historical reviews stay verbatim, matching the alktunnels and alktty precedent): src doc comments and inline comments (587), docs/architecture (319), docs/research + sdd_process (484), tests (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77), AGENTS.md (85), and the .opencode/agents prompts (109) - Fenced content follows the precedent: comments, ASCII-diagram label separators, and template placeholders rewritten; wire data, JSON/BAST documents, byte diagrams, and transcripts survive verbatim; assert, expect, panic, error-display, and format message literals rewritten as prose; no code, fixture, or test data change - src/rfc1928/LICENSE: the two dashes in alksocks' own provenance header rewritten; the MIT legal text untouched - AGENTS.md adds Project Convention 18: no em dashes in prose; varied restructure, no spaced-hyphen substitutes, fenced prose rule, en dash numeric ranges exempt, docs/reviews/ exempt from the greps - tasks/*.md frontmatter: the name values rewritten by the sweep are now quoted (a raw colon inside an unquoted YAML scalar parsed as null); taskgraph validate reports 37 tasks, no issues Verification: cargo test; cargo clippy --all-targets -D warnings; cargo fmt --check; cargo doc --no-deps; fuzz corpus replay (cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and clippy for wasm32-unknown-unknown with -D warnings; em dash grep over the repo returns matches only in docs/reviews/ and the bast.md BAST JSON data fence
435 lines
23 KiB
Markdown
435 lines
23 KiB
Markdown
# Fuzzing alksocks: Research and Plan
|
||
|
||
**Status:** adopted; wave-1 targets (`datagram_codec`, `rfc1928_codec`,
|
||
`params_parse`) implemented, seeds committed, campaigns run clean (§7).
|
||
Wave 2 (`session_pumps`) deferred to post-0.1 (§4). Campaigns run via the
|
||
detached runner (§6): **never as a foreground child of an agent
|
||
session** (§5).
|
||
**Date:** 2026-09-28
|
||
**Upstream precedent:** `/workspace/@alkdev/alkcall/docs/research/fuzzing.md`
|
||
(one real finding: the duplicate adopt/open destroying the live channel,
|
||
fixed in 0.8.1) and
|
||
`/workspace/@alkdev/alktunnels/docs/research/fuzzing.md` (wave-1/wave-2
|
||
adopted, six harness-model findings, no crate bugs). Tooling, corpus
|
||
policy, runner, and the no-hosted-CI posture are inherited from those
|
||
docs wholesale; this doc does not re-litigate them. It scopes them to
|
||
this crate.
|
||
|
||
---
|
||
|
||
## 1. Should this crate be fuzzed?
|
||
|
||
**Yes, and alkcall's fuzz doc already assigned this to us.** Three
|
||
lines, all pointing the same way:
|
||
|
||
1. **alkcall's fuzz doc §5 named this crate's surfaces as downstream
|
||
targets.** Its "non-surfaces (deliberately)" note ends: *the channels
|
||
layer carries opaque `Bytes` and the handler owns sub-stream framing
|
||
(ADR-035); those downstream parsers are downstream crates' fuzz
|
||
targets. Our `src/wire.rs` datagram codec **is** that named
|
||
downstream target (shaped on alktunnels' codec, which alktunnels
|
||
fuzzed as its wave-1 first target; the same assignment, landed
|
||
here). Fuzzing it completes the upstream scope rather than
|
||
duplicating it.
|
||
|
||
2. **We own an entire RFC handshake state machine fed by arbitrary
|
||
peers, on both sides.** The targeted fork (`src/rfc1928/`, ADR-013)
|
||
is fully owned code with project conventions applying unconditionally
|
||
(convention 17). Its parse surfaces, namely `read_address` (the address
|
||
codec), `negotiate_auth` (method negotiation), `read_command` (the
|
||
command encoding), `read_username_password` (RFC 1929) and the client's
|
||
`read_request_reply`, are fed bytes produced by **arbitrary peers**
|
||
in both directions: the producer drives the server typestate against
|
||
consumer bytes, and the consumer sessions + front doors drive the
|
||
client path against producer bytes. An RFC state machine is exactly
|
||
the shape the quinn-proto CVE class (RUSTSEC-2026-0037, remote DoS
|
||
via `unwrap()` in parsing, the upstream doc §1's strongest
|
||
datapoint) lives in: hand-rolled state transitions, length arithmetic,
|
||
and boundary handling. One fork-carried correction is already of this
|
||
class: upstream's `ReplyError::from_u8` ended in `unreachable!()`
|
||
for unassigned reply codes; the fork made it a typed catch-all (the
|
||
review's F-1) precisely because the value comes off the wire. A fuzz
|
||
target is how that posture is *demonstrated* across the whole codec,
|
||
not believed spotwise.
|
||
|
||
3. **The differential suite's blind spot is exactly fuzzing's
|
||
strength.** `tests/rfc1928-differential.rs` pins our fork's behavior
|
||
against the reference checkout on *valid* RFC edge cases, curated
|
||
inputs a human thought of. What no example-based suite provides:
|
||
random *malformed* input exploration (the panic-on-attacker-bytes
|
||
class), stateful interleavings, and resource-bound assertions under
|
||
adversarial sequences. The fork is about to be published (0.1) as
|
||
wire-stable; after that, targets are regression protection for a
|
||
frozen surface (cheapest now).
|
||
|
||
**The honest caveat** (mirroring both upstream docs): the crate has no
|
||
`unsafe` (checked at the crate root), the codec's decode direction is
|
||
infallible by contract (oversize rejected at frame time; the decoder
|
||
buffers, bounded by the u16 declared length), the RFC 1929 length
|
||
fields are one byte each (the attacker-controlled byte cannot ask for
|
||
more than 255), and the fork's error vocabulary is typed end-to-end
|
||
(the `unreachable!()`/`unwrap` sweep already ran in review 001).
|
||
Expected yield is therefore not memory-safety bugs; it is (a) the
|
||
demonstration that the infallibility/no-panic contracts hold on
|
||
adversarial input, (b) the decoder-state bound assertion (alktunnels
|
||
§1.3's finding class: a peer chunk amplifying into unbounded state),
|
||
and (c) regression protection for the published wire surface. If the
|
||
campaigns find nothing, that is the successful-negative-result posture
|
||
alkcall's §1 codified.
|
||
|
||
## 2. Toolchain (inherited, not re-decided)
|
||
|
||
Identical to both upstream docs: **cargo-fuzz 0.13.2 + `arbitrary`
|
||
1.4.x**, nightly confined to the `fuzz/` subtree via
|
||
`fuzz/rust-toolchain.toml`, the crate itself stays stable at MSRV 1.88.
|
||
AFL optional/deferred; bolero deferred (the async-friction argument);
|
||
libafl/honggfuzz/OSS-Fuzz not adopted. Sanitizer: `--sanitizer=address`
|
||
with debug assertions (cargo-fuzz defaults); no MSAN (no `unsafe` in
|
||
this crate).
|
||
|
||
**Layout (the upstream pattern, load-bearing):**
|
||
|
||
```
|
||
fuzz/
|
||
├── Cargo.toml alksocks-fuzz (nightly-only bins; own [workspace])
|
||
├── rust-toolchain.toml pins nightly for this subtree only
|
||
├── fuzz_targets/ thin fuzz_target! wrappers (3-line each)
|
||
│ ├── datagram_codec.rs
|
||
│ ├── rfc1928_codec.rs
|
||
│ └── params_parse.rs
|
||
├── shared/ alksocks-fuzz-shared (STABLE-toolchain library)
|
||
│ └── src/… invariant logic + corpus replay (§3)
|
||
├── corpus/<target>/ committed hand-made seeds (§3)
|
||
├── gen_fuzz_seeds.py deterministic seed generator (quiche pattern)
|
||
├── run-detached.sh §6 detached runner (CWD-independent)
|
||
└── README.md
|
||
```
|
||
|
||
- **Invariant logic lives in `fuzz/shared/`**, not inline in the target
|
||
binaries: the stable-toolchain corpus replay
|
||
(`cargo test --manifest-path fuzz/shared/Cargo.toml`) runs the SAME
|
||
invariant functions the fuzzer runs, on stable, in the release
|
||
verification checklist. This is alkcall §7.2 tier-3's deliverable
|
||
minus automation; per alkcall §7.9 (adopted here identically) this
|
||
repo has no hosted CI and will not get one. Corpus replay as plain
|
||
`cargo test` is the fuzz gate.
|
||
- **Root `Cargo.toml` needs the explicit `[workspace]` table**
|
||
(`members = ["."]`, `exclude = ["fuzz"]`) and `"fuzz/"` on the
|
||
publish exclude list; without it cargo auto-discovers `fuzz/shared/`
|
||
into the main workspace and the MSRV toolchain tries to build the
|
||
nightly-consumed dev-deps (the exact failure alkcall hit).
|
||
- **Corpus policy:** commit hand-made seeds, gitignore grown corpora
|
||
(quinn/h2 model). Seeds per target listed in §3.
|
||
|
||
## 3. Invariants by target
|
||
|
||
### 3.1 `datagram_codec` (the ASSOCIATE frame codec, `src/wire.rs`)
|
||
|
||
Input: raw `&[u8]`. Drives `DatagramReader::feed` + `frame` /
|
||
`frame_inner` / `addr_encoding_len`. This is the alktunnels wave-1
|
||
`datagram_codec` target's shape, scoped to this crate's codec (the
|
||
frame carries `addr + data` inner bytes and decodes to
|
||
`(TargetAddr, payload)`, not opaque datagrams; the invariants follow
|
||
the inner structure). Invariants:
|
||
|
||
- **no-panic on any byte sequence** (the baseline).
|
||
- **infallible reassembly**: `feed` never errors at the reassembly
|
||
layer; every frame completes as `Ok` or as one of the four
|
||
*decode-garbage* `WireError` variants (`EmptyFrame` / `BadAtyp` /
|
||
`ShortAddress` / `DomainNotUtf8`); `Oversize`/`Address` are
|
||
unreachable from `feed` (they are frame-time errors; a shape
|
||
assertion pins the partition).
|
||
- **empty-payload F-2 layering**: a decoded `(addr, empty)` pair comes
|
||
only from a real `[len][addr]` frame whose payload region is empty,
|
||
never invented by the codec; a `len == 0` frame (zero inner bytes)
|
||
always surfaces as `EmptyFrame`, never as an empty datagram.
|
||
- **bad-ATYP loudness**: every `BadAtyp` error's byte is the actual
|
||
first inner byte of its frame, and it is not one of the three
|
||
RFC ATYPs (the post-reply control-byte semantic: any non-ATYP
|
||
control byte must fail loud, never guess).
|
||
- **consumption accounting / splitting transparency**: feeding in
|
||
3-byte slices yields exactly the datagram sequence the whole-input
|
||
feed yields (both directions' framing is transparent to splitting).
|
||
- **round-trip**: every decoded datagram re-frames via `frame` (or
|
||
`frame_inner` over its inner bytes) and re-decodes to itself;
|
||
well-framed input re-frames byte-for-byte.
|
||
- **joint bound**: `frame` rejects iff
|
||
`addr_encoding_len(addr) + payload.len() > MAX_INNER_LEN`, echoing
|
||
`(total, max)` exactly, including the max-domain case where a legal
|
||
RFC payload (65507 to a 255-char domain = 65766) must reject (the
|
||
joint-bound reason the codec exists); `frame_inner` rejects iff
|
||
`inner.len() > MAX_INNER_LEN`. The `checked_add` path (payload
|
||
overflow wrapping the u16) rejects, never wraps.
|
||
- **decoder-state bound**: `buf` + in-flight `current` stay bounded by
|
||
the fed chunk size plus one frame (header + `MAX_INNER_LEN`) on any
|
||
adversarial input, the alktunnels §1.3 finding-class assertion
|
||
(state must never grow with input). The crate's `DatagramReader`
|
||
exposes this via `is_mid_frame()` + a new `retained_bytes()`
|
||
diagnostic (the alktunnels `retained_bytes()` posture).
|
||
|
||
Seeds: valid frames (all three ATYPs), truncation at every prefix
|
||
length, `len = 0` / `len = MAX` / `len = MAX+1`, empty payloads,
|
||
batched frames, split frames, garbage ATYP, non-UTF-8 domains, the
|
||
65766 joint-bound shape, deep batch/split mixes.
|
||
|
||
### 3.2 `rfc1928_codec` (the forked RFC 1928 core, `src/rfc1928/`)
|
||
|
||
Input: raw `&[u8]`. Drives the fork's byte-level parsers over tokio
|
||
duplex streams (the in-tree test shape, mock bytes, no sockets):
|
||
|
||
- **`read_address`** (the address codec, both directions consume it):
|
||
no-panic on any ATYP/bytes; every failure is a typed `AddrError`
|
||
(`IncorrectAddressType` for unknown ATYP, the F-1 class, already
|
||
fixed; never a panic); round-trip for every well-formed address
|
||
(`to_be_bytes` → parse → equal).
|
||
- **the server handshake** (`negotiate_auth` → `read_command` over a
|
||
duplex, `accept_no_auth` + `read_command`): no-panic on any byte
|
||
sequence; every malformed shape is a typed `SocksServerError`
|
||
(version mismatch, unacceptable method, unknown command, bad addr);
|
||
a well-formed negotiation + CONNECT request parses to the exact
|
||
command + target.
|
||
- **the client reply path** (`read_request_reply`): no-panic on any
|
||
server byte sequence; unassigned reply codes map to the
|
||
`GeneralFailure` catch-all (the F-1 pin, never `unreachable!()`);
|
||
version mismatches are typed errors.
|
||
- **`parse_udp_request`** (the RFC UDP header codec, front doors only
|
||
but still wire-reachable): no-panic; garbage RSV / unknown ATYP /
|
||
truncation are typed `UdpHeaderError`s; round-trip with
|
||
`new_udp_header`.
|
||
- **auth sub-handshakes** (`read_username_password`, RFC 1929):
|
||
no-panic; the u8 length fields bound allocations structurally
|
||
(≤ 255 bytes, asserted); empty username/password are typed errors;
|
||
round-trip accept/reject status bytes.
|
||
- **`ReplyError::from_u8`** full-domain table: every `u8` maps, no
|
||
panic, unassigned → catch-all (the direct probe of the F-1 class).
|
||
|
||
This is the wave-1 target of highest value: it is the entire RFC 1928
|
||
byte surface, fed attacker bytes from both protocol directions, and the
|
||
fork is owned code whose behavior the differential suite pins only on
|
||
valid inputs.
|
||
|
||
Seeds: valid handshakes (no-auth / password), version mismatches,
|
||
truncations at every prefix length of each message shape, unknown
|
||
methods/commands/ATYPs, empty and 255-byte domains, non-UTF-8 domains,
|
||
unassigned reply codes, RSV garbage.
|
||
|
||
### 3.3 `params_parse` (the open-op params, `src/params.rs`)
|
||
|
||
Input: raw bytes → `serde_json` (alkcall's bounded `arbitrary_value`
|
||
generator) → `Socks5Params` deserialization + `socks5_open_spec()` +
|
||
`SocksEstablishmentReason::parse` / `establishment_reason`. Invariants:
|
||
|
||
- **no-panic on any JSON-shaped input** (`deny_unknown_fields`
|
||
dispatch, the registry-spec builder).
|
||
- **the trivial-params pin**: `{}` parses, `Socks5Params::as_json()` ==
|
||
`{}`, and unknown fields always reject (the loud-rejection posture:
|
||
the serde pin and the schema's `additionalProperties: false` agree).
|
||
- **the spec is a constant**: `socks5_open_spec()`'s wire-visible
|
||
fields (op id, ALPN, scopes, schemas) are identical on every
|
||
execution; the open-op surface is wire-stable (ADR-001); the
|
||
reason-enum table equals the typed parse table (the schema/typed-view
|
||
drift guard, mirrored from the in-suite test).
|
||
- **the reason surface is a pass-through extraction, not a validated
|
||
table**: `establishment_reason` yields `Some(details.reason)` iff the
|
||
code is `channel:open_failed` and the reason is a string; any string
|
||
is surfaced as-is (the alktunnels harness-model finding #2, adopted
|
||
here as the modeled contract); `SocksEstablishmentReason::parse`
|
||
never panics and is total.
|
||
|
||
### 3.4 What is deliberately NOT a target (yet)
|
||
|
||
- **The producer pump (`serve_socks5_over`) / consumer sessions as
|
||
stateful op-sequence targets**: wave 2, deferred (§4). The
|
||
handshake-level target (§3.2) covers the byte surface; the
|
||
session-level interleavings (associate stage select-shape, BIND quiet
|
||
wait, the two-pump CONNECT plane) are stateful shapes over a live
|
||
channels stack (the alktunnels wave-2 pattern), worth its own session
|
||
post-0.1.
|
||
- **The doors (`door.rs`, `local/`, `composed/`)** are feature-gated
|
||
assembly; their RFC-codec boundary (`parse_udp_request` /
|
||
`new_udp_header`) is covered by the §3.2 target. The rest is I/O
|
||
plumbing over the fuzzed cores, not parse surfaces.
|
||
- **`front_door` RFC 1929 negotiation**: covered by §3.2's auth
|
||
sub-handshake invariants (the door drives the same forked parsers).
|
||
|
||
## 4. Scope: two waves
|
||
|
||
**Wave 1 (this session):** the three targets above. All drive
|
||
already-`pub` APIs; no `#[cfg(fuzzing)]` exposure needed (matching
|
||
both upstream docs' experience across all eight targets to date). One
|
||
diagnostic addition to the crate: `DatagramReader::retained_bytes()`
|
||
(the decoder-state byte count §3.1's bound asserts against, in
|
||
alktunnels' `retained_bytes()` style).
|
||
|
||
**Wave 2 (deferred to post-0.1):** `session_pumps`, the stateful
|
||
op-sequence target over the producer pump + consumer sessions in the
|
||
alkcall `manager_routing` / alktunnels `session_pumps` style (live
|
||
channels stack; ops over CONNECT/ASSOCIATE/BIND interleavings; the
|
||
two-pump shutdown-on-completion contract, the egress readiness
|
||
contract, and the datagram-stage exit matrix as invariants). Deferred
|
||
because wave 1 covers the wire byte surface completely, the session
|
||
harness is a large build, and the crate is one commit from its first
|
||
publish. The alktunnels sequencing (wave 1 at 0.2.0's fuzz adoption,
|
||
wave 2 the same session) was possible there because its session shape
|
||
was the crate's whole surface; here the byte-level targets carry the
|
||
attacker-reachable load and the stateful session target is a separate
|
||
planning unit.
|
||
|
||
Sequencing mirrors the upstream docs: init + wave-1 targets → 10–30
|
||
min campaigns per target via the detached runner → fix anything found →
|
||
wave 2 when scheduled.
|
||
|
||
## 5. Operational isolation: campaigns never share fate with the agent session
|
||
|
||
**Adopted verbatim from the upstream docs (alkcall §7.6, alktunnels
|
||
§5)**: an environment constraint, not a nice-to-have. Agent sessions
|
||
run fuzzer builds/campaigns through a bash tool that spawns the fuzzer
|
||
as a child of the session's own process tree and cgroup; an OOM in a
|
||
fuzz target must cost the fuzzer, never the session. Three layers, all
|
||
mandatory: (1) libFuzzer soft limits (`-rss_limit_mb=2048`
|
||
`-malloc_limit_mb=2048`); never `ulimit -v` under ASAN; (2) fork mode
|
||
(`-fork=1`) as blast-radius containment (crash/timeout/OOM kills only
|
||
the short-lived child; the harness survives and persists the artifact;
|
||
also the deadlock answer); (3) process detachment: the runner script
|
||
wraps `cargo fuzz run` in `setsid` + `nohup` + full redirection to a
|
||
log file; poll, never `wait`.
|
||
|
||
Also inherited: the operational note that `cargo fuzz run` returns
|
||
before its last fork-mode children fully exit; prefer `pgrep -af` to
|
||
LIST, kill by explicit pid from the runner's echoed `pid=`, and never
|
||
pattern-kill-and-recheck in one call.
|
||
|
||
## 6. No hosted CI
|
||
|
||
Per alkcall §7.9 / alktunnels §6, the standing policy applies to this
|
||
repo identically: no hosted CI will exist; do not add 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, so add it
|
||
to the release verification checklist (AGENTS.md). Campaigns run
|
||
manually via the detached runner (§6); e.g. before a release, or after
|
||
touching `src/wire.rs`, `src/rfc1928/`, or `src/params.rs`.
|
||
|
||
## 7. Implementation log
|
||
|
||
- **2026-09-28: proposal adopted** (this doc). Wave-1 targets
|
||
`datagram_codec` + `rfc1928_codec` + `params_parse`; wave 2 deferred
|
||
(§4).
|
||
- **2026-09-28: `alkcall` bumped 0.8.0 → 0.8.1,
|
||
`alktunnels` 0.2.0 → 0.2.1.** Bug-fix releases, no API/wire change;
|
||
alkcall's fix (the duplicate adopt/open destroying the live channel)
|
||
is directly relevant to this crate's consumer sessions: every
|
||
session's teardown reaps via the manager, and the
|
||
open-op-response-replayed scenario alkcall's changelog names is the
|
||
consumer open path. Verification: full suite (default + all-features)
|
||
green, clippy, fmt, wasm check (§8 commands).
|
||
- **2026-09-28: wave-1 implemented.** The `fuzz/` workspace laid out
|
||
per §2 (nightly-pinned subtree, stable `fuzz/shared` library, runner,
|
||
dict, generator); the three targets over already-`pub` APIs, with no
|
||
`#[cfg(fuzzing)]` exposure needed (the upstream docs' experience,
|
||
now across eleven targets). One diagnostic addition to the crate:
|
||
`DatagramReader::retained_bytes()` (§3.1's bound target). Seeds
|
||
committed: 45 + 65 + 19 generated (fuzz/gen_fuzz_seeds.py) + 2
|
||
campaign-found crash inputs promoted (`datagram_codec/seed-900`,
|
||
`seed-901`, the §7 harness findings below).
|
||
- **2026-09-28: two harness-model defects caught by corpus replay
|
||
pre-campaign** (the alkcall §7.7 fuzzer-as-harness-checker pattern):
|
||
1. **A `len == 0` frame is well-framed but decodes to
|
||
`Err(EmptyFrame)`**: the re-frame round-trip first expected every
|
||
well-framed frame to decode `Ok`. The frame count still holds over
|
||
every frame; the byte-for-byte re-frame holds over the decodable
|
||
ones; `EmptyFrame` is pinned as the only zero-inner-byte shape.
|
||
The same arm widened for `ShortAddress`/`DomainNotUtf8`/
|
||
`BadAtyp`: a well-framed frame can carry any addr-garbage shape;
|
||
the decode refuses the addr, and the raw frame is the only
|
||
re-frame possible.
|
||
2. **A fieldless `deny_unknown_fields` struct deserializes from ANY
|
||
empty JSON composite** (the serde seq form `[]` included), not
|
||
only `{}`. Not wire-reachable (the registry's `type: object`
|
||
input schema is the gate that keeps non-object forms from ever
|
||
reaching the type), but the harness must model the serde reality:
|
||
the trivial-params invariant is now "an empty composite parses,
|
||
everything non-empty rejects, the canonical round-trip is `{}`."
|
||
- **2026-09-28: first campaign finding, fork-mode containment as
|
||
designed (a third harness-model defect).** The `datagram_codec`
|
||
campaign crashed at ~35 s on input `0005 0301 1203 03bb 00`
|
||
(`BadAtyp(0)` shape); the re-frame walk's `Err` arm (fixed in the
|
||
replay pass above) still omitted `BadAtyp`. Fixed; the crash input is
|
||
committed as `seed-900`; the campaign relaunched clean.
|
||
- **2026-09-28: second campaign finding (also harness, fork-mode
|
||
containment); the count walk's `declared >= 7` gate excluded short
|
||
domains.** Input `0005 0301 1203 03bb 00`, a domain frame with a
|
||
1-byte domain (`addr_len = 5`) is a legal empty-payload frame, but
|
||
the walk's `declared >= 7` gate (meant to skip addr-less frames)
|
||
rejected it; the per-ATYP `addr_len` computation already does that
|
||
job. The gate lowered to `>= 4` (the minimum ATYP + length-byte
|
||
prefix the walk reads); the crash input is committed as `seed-901`;
|
||
the campaign relaunched clean.
|
||
- **2026-09-28: wave-1 campaigns run (10-min detached runs, §6
|
||
runner; `-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048
|
||
-timeout=25`; codec `-max_len=65536`, rfc1928
|
||
`-max_len=4096 -use_value_profile=1`, params
|
||
`-dict=json.dict -use_value_profile=1). All three exited 0 with
|
||
empty artifact directories: no crashes, hangs, OOMs, or leaks.**
|
||
- `datagram_codec` (post-harness-fixes), ~1.9M execs (~3k exec/s),
|
||
33 fork jobs, `oom/timeout/crash: 0/0/0` throughout. Coverage
|
||
saturated at 481 edges (ft 2222, corp 209); the 2-byte-header
|
||
state machine is fully enumerated (the alktunnels wave-1
|
||
saturation profile).
|
||
- `rfc1928_codec`, ~2.0M execs (~3.6k exec/s), 33 fork jobs,
|
||
`oom/timeout/crash: 0/0/0` throughout. Coverage saturated at 1429
|
||
edges (ft 7987, corp 704); the handshake state machines'
|
||
byte-level shape space is fully enumerated; both protocol
|
||
directions held across every adversarial sequence.
|
||
- `params_parse`, ~3.1M execs (~5.5k exec/s), 33 fork jobs,
|
||
`oom/timeout/crash: 0/0/0` throughout. Coverage 1512 edges (ft
|
||
12970, in-memory corpus 2981 from the 19 committed seeds); the
|
||
serde/untagged shape space was still expanding at budget end; the
|
||
spec-constant and pass-through-reason invariants held everywhere.
|
||
- The decoder-state bound (§3.1) held everywhere:
|
||
`retained_bytes()` never exceeded fed + one frame on any
|
||
adversarial input. Grown corpora were **not** merged into the
|
||
committed seeds (the §2 policy).
|
||
- **NO crate bugs found by wave 1.** All three findings this session
|
||
were harness-model defects, each invisible to example-based review
|
||
and caught by fuzzing/replay: the upstream "fuzzer as a
|
||
harness-checker" pattern, confirmed a third time (alkcall §7.7,
|
||
alktunnels §7, here §7). The codec's contracts (infallible
|
||
reassembly, the F-2 layering, the joint bound, the retained-bytes
|
||
bound) and the RFC core's no-panic posture held across every
|
||
adversarial input the campaigns generated.
|
||
- (progress appended as implementation proceeds; see git log)
|
||
|
||
## 8. Verification commands
|
||
|
||
```bash
|
||
cargo test # main suite (stable)
|
||
cargo test --all-features # the feature-gated suite
|
||
cargo test --manifest-path fuzz/shared/Cargo.toml # corpus replay (fuzz gate)
|
||
cargo fuzz build # in fuzz/ (nightly, ASAN)
|
||
fuzz/run-detached.sh <target> # campaigns (never foreground)
|
||
cargo clippy --all-targets -- -D warnings
|
||
cargo fmt --check
|
||
cargo clippy --manifest-path fuzz/shared/Cargo.toml --all-targets -- -D warnings
|
||
cargo fmt --manifest-path fuzz/shared/Cargo.toml --check
|
||
cargo check --target wasm32-unknown-unknown # default crate stays wasm-clean
|
||
```
|
||
|
||
Campaign flags per target: `datagram_codec -max_len=65536`;
|
||
`rfc1928_codec -max_len=4096 -use_value_profile=1`;
|
||
`params_parse -dict=json.dict -use_value_profile=1`.
|
||
|
||
## 9. References
|
||
|
||
- Upstream campaigns + operational model (authoritative for tooling,
|
||
runner, corpus policy, no-CI posture):
|
||
`/workspace/@alkdev/alkcall/docs/research/fuzzing.md`,
|
||
`/workspace/@alkdev/alktunnels/docs/research/fuzzing.md`
|
||
- alkcall 0.8.1 changelog, the finding our consumer open/adopt paths
|
||
are exposed to
|
||
- This crate: ADR-001 (op/params), ADR-003 (the frame codec), ADR-013
|
||
(the targeted fork), `docs/architecture/wire.md`,
|
||
`docs/architecture/rfc1928.md`
|
||
- External: RUSTSEC-2026-0037 (quinn-proto, the maintainers'
|
||
fuzzing-coverage statement), rust-fuzz trophy case, cargo-fuzz Rust
|
||
Fuzz Book (all linked from the upstream doc's §9) |