Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b10e980346 | ||
|
|
f809f6ca7e | ||
|
|
a0dd3d2de4 | ||
|
|
b7ead99724 | ||
|
|
9ca9922fd4 | ||
|
|
aef8d9f6ab | ||
|
|
16b9023f60 | ||
|
|
752e36b526 | ||
|
|
ed41d77e72 | ||
|
|
8d779e7672 |
No files matched your search
+10
-1
@@ -1,3 +1,12 @@
|
||||
target/
|
||||
node_modules/
|
||||
.worktrees/
|
||||
.worktrees/
|
||||
|
||||
fuzz/target/
|
||||
fuzz/artifacts/
|
||||
fuzz/coverage/
|
||||
# Grown corpora: the hash-named files the campaigns drop into
|
||||
# fuzz/corpus/<target>/ are gitignored (the quinn/h2 policy); the
|
||||
# committed seeds are the seed-* files, kept via the per-dir
|
||||
# .gitignore un-ignores.
|
||||
fuzz/corpus/*/[0-9a-f][0-9a-f]*
|
||||
@@ -165,8 +165,18 @@ cargo clippy --all-targets -- -D warnings
|
||||
cargo doc --no-deps # if docs changed
|
||||
cargo build --target wasm32-unknown-unknown --release # if layout/wasm-relevant code changed
|
||||
cargo publish --dry-run --allow-dirty # before a release
|
||||
cargo test --manifest-path fuzz/shared/Cargo.toml # fuzz corpus replay (the fuzz gate)
|
||||
```
|
||||
|
||||
The corpus replay is the standing fuzz gate (the alkcall
|
||||
`docs/research/fuzzing.md` §7.9 posture): it replays every committed
|
||||
seed through the same invariant functions the fuzz targets run, on
|
||||
stable, without nightly. Campaigns (nightly, cargo-fuzz) run manually
|
||||
via `fuzz/run-detached.sh` — never as a foreground child of an agent
|
||||
session — before releases, after touching `src/data_access.rs`,
|
||||
`src/schema.rs`, the compile walks, or the sequential reader. See
|
||||
`docs/plans/fuzzing.md` and `fuzz/README.md`.
|
||||
|
||||
## Architecture Context
|
||||
|
||||
- `docs/architecture/` — the authoritative spec. Read it before
|
||||
|
||||
@@ -4,6 +4,49 @@ All notable changes to this crate are documented here. The format is
|
||||
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
||||
this crate adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [0.4.0] - 2026-09-30
|
||||
|
||||
The fuzzing release. The fuzz/ workspace (docs/plans/fuzzing.md) put
|
||||
five libFuzzer targets on the engine — bast_compile, data_access,
|
||||
read_opseq, layout_build, validate_pair — with release-budget campaigns
|
||||
across all five. Two genuine engine bugs found and fixed same-day,
|
||||
plus one upstream pin (docs/plans/fuzzing.md §6, §5 campaign numbers).
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **`VariableEncoding` gains `MaxLengthReserved`** (finding W3-3):
|
||||
the aligned-mode `maxLength` reservation (ADR-003 strategy 2, the
|
||||
`VARCHAR(N)` pattern) is now recorded as its own encoding variant
|
||||
instead of masquerading as `LengthPrefixed`. Code matching on
|
||||
`VariableEncoding` exhaustively must add an arm; in the document
|
||||
form the strategy is still expressed via `maxLength`, never as an
|
||||
`encoding` value.
|
||||
- **`read_field`/`write_field` fix for aligned `maxLength`
|
||||
reservations** (finding W3-3, commit `a0dd3d2`): an aligned
|
||||
String/Bytes leaf with a declared `maxLength` is now read as a raw
|
||||
zero-padded, NUL-trimmed window and written zero-padded — previously
|
||||
the first four raw bytes of the reservation window were misparsed as
|
||||
a u32 length prefix, breaking the validate_bytes ⇒ read_field
|
||||
agreement for every aligned schema declaring `maxLength`.
|
||||
- **`plan_read_array` bounds check** (finding W2-1, wave 2): a
|
||||
fixed-stride array whose declared window (count × stride) extends
|
||||
past the buffer now returns an `Access` error naming the array
|
||||
field instead of reporting success and deferring the failure to the
|
||||
next field read (or masking it entirely when the array was last).
|
||||
|
||||
### Additions
|
||||
|
||||
- **`data_access::read_reservation` / `read_reservation_string` /
|
||||
`write_reservation`** — the single source of truth for the
|
||||
`maxLength` reservation leaf semantics, shared with the aligned
|
||||
materializer.
|
||||
- **`fuzz/` subtree** — five libFuzzer targets, 257 committed seeds,
|
||||
the corpus-replay gate (`cargo test --manifest-path
|
||||
fuzz/shared/Cargo.toml`, AGENTS.md verification checklist), the
|
||||
detached campaign runner; nightly confined to `fuzz/`, `fuzz/`
|
||||
excluded from the package. All targets, seeds, and the replay gate
|
||||
are stable-toolchain safe.
|
||||
|
||||
## [0.3.0] - 2026-09-07
|
||||
|
||||
The compiled-forms release. The packed read path — the hot path for
|
||||
|
||||
Generated
+1
-1
@@ -27,7 +27,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alktype"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"jsonschema",
|
||||
|
||||
+6
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "alktype"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.85"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -9,11 +9,15 @@ repository = "https://git.alk.dev/alkdev/alktype"
|
||||
readme = "README.md"
|
||||
keywords = ["binary", "jsonschema", "wire-format", "serialization", "layout"]
|
||||
categories = ["encoding", "data-structures", "parsing"]
|
||||
exclude = [".opencode/", "docs/reviews/", "docs/research/", "docs/sdd_process.md", "Cargo.lock", "AGENTS.md"]
|
||||
exclude = [".opencode/", "docs/reviews/", "docs/research/", "docs/sdd_process.md", "Cargo.lock", "AGENTS.md", "fuzz/"]
|
||||
|
||||
[lib]
|
||||
name = "alktype"
|
||||
|
||||
[workspace]
|
||||
members = ["."]
|
||||
exclude = ["fuzz"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
|
||||
@@ -0,0 +1,655 @@
|
||||
# Plan: alktype fuzzing
|
||||
|
||||
Adopted from alkhttp's `docs/plans/fuzzing.md` (the pattern is operational
|
||||
in six sibling crates: alkcall, alktty, alktunnels, alksocks, alkhttp —
|
||||
each with the same `fuzz/` layout, the detached runner, and the
|
||||
corpus-replay-as-plain-test gate). The rationale research lives in
|
||||
alkcall's `docs/research/fuzzing.md` (tool landscape, comparable-crate
|
||||
survey, the no-hosted-CI policy §7.9); this plan stays focused on what
|
||||
alktype fuzzes and in what order.
|
||||
|
||||
Rationale for this crate in one paragraph (the detailed version applies
|
||||
by reference from the two docs above): alktype is the binary engine the
|
||||
alk* family consumes — alkcall's hub/spoke accepts BAST schema documents
|
||||
from arbitrary internet peers, and those documents flow into this
|
||||
crate's compile paths downstream; both untrusted-input shapes exist here
|
||||
(attacker-shaped JSON BAST docs → `AlkTypeEngine::compile`, and
|
||||
attacker-shaped byte buffers read according to a schema →
|
||||
`validate_bytes` / `SequentialReader` / `tunion` dispatch /
|
||||
`materialize`), and the byte side is hand-rolled decode
|
||||
(`data_access`, `tunion` discriminators, indirect `{offset,length}`
|
||||
pairs) — exactly the shapes where example tests miss off-by-one bugs.
|
||||
The crate is fully synchronous, so targets are simpler than
|
||||
alkcall/alkhttp's (no current-thread runtime shims anywhere).
|
||||
|
||||
**Status:** waves 1–3 implemented and verified (2026-09-30). The
|
||||
pre-fuzzing inventory (§6) is verified against the code at 0.3.0.
|
||||
All five targets and the full infrastructure are in-tree (commits
|
||||
`ed41d77`, wave 1; `16b9023`, wave 2; `aef8d9f` + findings commits,
|
||||
wave 3); the smoke campaigns ran clean (§5); the release-budget
|
||||
campaigns across all five targets are complete (§5) — two targets
|
||||
surfaced real findings: the wave-2 `read_opseq` packing bug (§6
|
||||
candidate 6, fixed same-day) and three wave-3 `validate_pair`
|
||||
findings (W3-1 harness pin, W3-2 upstream serde_json pin, W3-3 a real
|
||||
engine bug — read_field/write_field misread aligned maxLength
|
||||
reservations — fixed same-day with regression tests).
|
||||
|
||||
Progress log:
|
||||
|
||||
- **2026-09-30 — wave 3 landed.** Target 5 (`validate_pair`): the
|
||||
two-input harness (10-lane schema menu incl. a raw-JSON-bytes lane
|
||||
fused with the buffer), 48 committed seeds, decode-pin tests, and
|
||||
the §3 target-5 invariants (mode agreement incl. the documented
|
||||
aligned-rejection taxonomy, the materialize⇄validate_bytes verdict
|
||||
lattice with verbatim error propagation, unknown-path echo,
|
||||
non-finite-float Access pin, enum-Validation pin, record spin
|
||||
bound). Pre-campaign hand-drives all held. **Release-budget
|
||||
campaigns (§5):** 45–46 min across all five targets. bast_compile
|
||||
818k execs / 19,894 edges (still growing at budget end);
|
||||
data_access 11.3M execs / 684 edges (a value-profile run lifted the
|
||||
saturated corpus from 379 to 684 edges); read_opseq 63k execs /
|
||||
17,519 edges; layout_build 63k execs / 17,659 edges; validate_pair
|
||||
37k execs / 10,850 edges. **Finding W3-1 (harness invariant
|
||||
corrected — the fuzzer fired an over-assertion):** the harness
|
||||
claimed validate_bytes Ok ⇒ every offset-map leaf's range.end ≤
|
||||
buffer.len() — false for offset-indirect entries, whose pair points
|
||||
absolutely into the buffer while a maxLength window may dwarf the
|
||||
validated buffer; the wave-1 data_access bounds partition is the
|
||||
real contract. Fixed the invariant, pinned the artifact bytes as
|
||||
seed-044 + a named regression test (commit `9ca9922`).
|
||||
**Finding W3-2 (upstream, pinned with slack — no alktype bug):**
|
||||
serde_json's non-`float_roundtrip` parser drifts one ulp when
|
||||
re-parsing its own emitted shortest repr of adversarial f64 values
|
||||
(probe: 0x5bffffffffffffff emits 1.4536774485912136e+135 and
|
||||
parses back one ulp low; std's parser and ryu's own float parse are
|
||||
exact — the concise reparse is the drift). The harness replaced
|
||||
bare `Value` equality with a one-ulp structural comparison; the
|
||||
artifact bytes are seed-047 (commits `a8e955c`/`b7ead99`).
|
||||
**Finding W3-3 (real engine bug — fixed, commit `a0dd3d2`):**
|
||||
`AlkTypeEngine::read_field`/`write_field` treated an aligned
|
||||
`maxLength` reservation (ADR-003 strategy 2, `VARCHAR(N)`: raw
|
||||
zero-padded window, NUL-trimmed on read — exactly what the
|
||||
materializer and `validate_bytes` implement) as length-prefixed,
|
||||
parsing the window's first four raw bytes as a u32 length. Every
|
||||
aligned schema declaring `maxLength` broke the
|
||||
validate_bytes⇒read_field lattice whenever the reservation's first
|
||||
bytes looked like a large prefix (validate Ok, read_field Access
|
||||
with bogus bounds; write_field wrote prefix+data into a raw
|
||||
window). Third crash artifact (W3-1's shape family: the campaign
|
||||
re-found the disagreement space after W3-1's invariant was
|
||||
corrected). 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`, preserving
|
||||
W3-1's combination semantics); `read_field`/`write_field` dispatch
|
||||
through new `data_access::read_reservation{,_string}/
|
||||
write_reservation` (single source of truth with the materializer);
|
||||
three engine regression tests + the W3-1/W3-2 artifacts as
|
||||
committed corpus seeds. Post-fix restart: 30-min validate_pair
|
||||
campaign clean to budget end (37k execs, 10,850 edges, exit 0).
|
||||
- **2026-09-30 — wave 2 landed.** Targets 3 (`read_opseq`) and 4
|
||||
(`layout_build`) with 73 committed seeds (58 + 15), decode-pin tests
|
||||
for the arbitrary 1.4.2 derive encoding, and the plan §6 candidate-1
|
||||
spin bound encoded as the explicit End-op assertion. Smoke campaigns:
|
||||
see §5. **Finding W2-1 (fixed same session):** `plan_read_array`
|
||||
returned `Ok` for a fixed-stride array whose declared window
|
||||
(count × stride) extended past the buffer — the bounds check was
|
||||
missing entirely from the array arm (struct/union arms had theirs).
|
||||
A truncated array reported success with the failure deferred to the
|
||||
*next* field read (wrong field path), or masked entirely when the
|
||||
array was the last field. Found by hand-running the target-3 drive
|
||||
before the campaign (the first semantic fixture); fixed in
|
||||
`plan_read_array` with an end-vs-buffer bounds check + Access error
|
||||
naming the array field, regression test
|
||||
`array_truncated_below_fixed_stride_window_is_access_error_not_ok`.
|
||||
**Finding W2-2 (pinned, not a bug):** packed mode compiles
|
||||
`"encoding": "offset-indirect"` fields but the sequential reader
|
||||
always reads them inline length-prefixed — this matches
|
||||
bast-format.md §"Default strategy selection" ("Packed sequential
|
||||
mode: always inline length-prefixing"), so the annotation is a
|
||||
no-op in packed mode. Pinned as a corpus-replay invariant
|
||||
(`packed_mode_is_always_inline_length_prefixed`) so any future
|
||||
change to the packed reader's encoding awareness is deliberate. The
|
||||
aligned materializer honors the encoding correctly. An upstream
|
||||
question — should packed compile either honor the encoding or reject
|
||||
the declaration — is recorded in §6 candidate 7.
|
||||
- **2026-09-30 — wave 1 landed (commit `ed41d77`).** The `fuzz/`
|
||||
workspace, 136 committed seeds (38 `bast_compile` + 98
|
||||
`data_access`), the detached runner, the corpus-replay gate
|
||||
(AGENTS.md checklist gains the line), nightly pinned subtree,
|
||||
explicit root `[workspace]` exclusion, publish-exclude gain,
|
||||
`json.dict`. `cargo fuzz build` clean; corpus replay 4/4 green;
|
||||
main crate untouched (569 tests, clippy `-D warnings` clean). One
|
||||
dict-format fix on the way: libFuzzer's dictionary parser does not
|
||||
accept `\u` escapes (`"\u0000"` → the `\xAB` form) and needs fully
|
||||
quoted lines — caught by the campaign launcher, not the fuzzer.
|
||||
- **2026-09-30 — smoke campaigns clean (see §5).** `data_access`
|
||||
saturated (pure decode core, the alkcall `chunk_header` profile);
|
||||
`bast_compile` still discovering coverage at budget end (longer
|
||||
campaigns keep paying). No crate findings — the §6 candidates
|
||||
(record-count loops, indirect pairs) held under the parser-level
|
||||
drives; both remain encoded as wave-2/3 invariants in the stateful
|
||||
targets.
|
||||
- **2026-09-30 — wave-2 smoke campaigns (see §5).** Results recorded
|
||||
there alongside the wave-1 numbers.
|
||||
|
||||
---
|
||||
|
||||
## 1. Why alktype fuzzes (the if)
|
||||
|
||||
1. **Downstream of the trust boundary.** alktype is compiled against in
|
||||
alkcall (the integration crate), whose peers are untrusted and whose
|
||||
wire payloads carry schema-shaped JSON. A panic on a malicious BAST
|
||||
doc or bytes read under one is the quinn-CVE class
|
||||
(RUSTSEC-2026-0037) at one further hop: the alk* stack parses
|
||||
documents it never vetted, and alktype is where they get walked.
|
||||
2. **Both input shapes, one crate.** Sibling crates each had mostly one
|
||||
parse shape (wire bytes); alktype has the schema-JSON shape *and*
|
||||
the raw-buffer shape, plus two-input combined paths
|
||||
(`read_field`/`write_field`, `materialize_aligned` are doc+bytes).
|
||||
3. **Infrastructure is proven and cheap; the crate is the simplest
|
||||
consumer yet.** Six siblings run the layout; alktype is sync, has
|
||||
zero `unsafe`, zero `unwrap`/`expect` outside tests, and no
|
||||
allocation-from-wire-count anywhere (grep-verified inventory). The
|
||||
marginal cost is target logic only.
|
||||
|
||||
**Honest caveat (alkcall §1's shape):** the code is already well
|
||||
hardened — `checked_add`/`check_bounds` everywhere, parse-time caps
|
||||
(`MAX_ARRAY_ELEMENTS`, `MAX_ARRAY_BYTES`, `MAX_ALIGN` = 4096,
|
||||
`MAX_LENGTH`), `MAX_GRAPH_DEPTH`/`MAX_COMPILE_DEPTH` = 128, meta-schema
|
||||
gate before any walker. Expected yield is low-moderate: the residual
|
||||
candidates in §6 are the first things to probe; a clean first campaign
|
||||
is the successful negative result — "we think the engine is robust"
|
||||
converted into a demonstrated property.
|
||||
|
||||
## 2. Infrastructure (identical to the siblings)
|
||||
|
||||
Layout (copy of alkcall/alkhttp):
|
||||
|
||||
```
|
||||
fuzz/
|
||||
├── Cargo.toml alktype-fuzz (nightly-only bins; own [workspace])
|
||||
├── rust-toolchain.toml pins nightly + llvm-tools for this subtree only
|
||||
├── fuzz_targets/ thin fuzz_target! wrappers (3 lines each)
|
||||
├── shared/ alktype-fuzz-shared — STABLE-toolchain library:
|
||||
│ invariant logic + corpus-replay tests
|
||||
├── corpus/<target>/ committed seeds (generated by gen_fuzz_seeds.py)
|
||||
├── artifacts/ gitignored crash/oom/timeout artifacts + logs
|
||||
├── gen_fuzz_seeds.py deterministic seed generator (quiche pattern)
|
||||
├── json.dict JSON/BAST token dictionary (bast_compile)
|
||||
├── run-detached.sh detached campaign runner (copied from the siblings)
|
||||
└── README.md operational cheat-sheet
|
||||
```
|
||||
|
||||
Load-bearing details (all six siblings hit these; alkcall's doc is the
|
||||
deep reference):
|
||||
|
||||
- **Invariant logic lives in `fuzz/shared/`**, not the target binaries.
|
||||
The stable-toolchain shared crate replays every committed seed through
|
||||
the identical invariant functions as plain `cargo test` — the standing
|
||||
fuzz gate (alkcall §7.9 tier-3 deliverable; no hosted CI in this repo
|
||||
by policy). The `fuzz_target!` binaries are thin wrappers.
|
||||
- **Root `Cargo.toml` needs an explicit `[workspace]` table**
|
||||
(`members = ["."]`, `exclude = ["fuzz"]`); without it auto-discovery
|
||||
pulls `fuzz/shared/` into the main workspace and the stable toolchain
|
||||
builds nightly-consumed dev-deps. alkcall hit this trap.
|
||||
- **`fuzz/rust-toolchain.toml` pins nightly** so `cargo fuzz build`
|
||||
works from any CWD; nightly stays confined to `fuzz/`, MSRV 1.85
|
||||
untouched here. `fuzz/` joins the publish `exclude` list.
|
||||
- **`.gitignore` additions**: `fuzz/artifacts/`, grown-corpus dirs
|
||||
(committed seeds stay).
|
||||
- **Detached runner (non-negotiable operating rule).** Campaigns never
|
||||
run as a foreground child of an agent session; the runner pins
|
||||
`-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25` and
|
||||
detaches via `setsid` + `nohup` + log redirect; the agent polls the
|
||||
log and artifact directory, never waits. Copied from the siblings.
|
||||
- **No feature-gating needed in `fuzz/shared`**: alktype has
|
||||
`default = []` and no feature flags, so the shared crate rides the
|
||||
main crate build unconditionally (unlike alkhttp's gated
|
||||
`openapi`/`mcp`).
|
||||
- **Exposure needs are minimal.** The inventory found every target
|
||||
entry point already `pub` (`compile`, `data_access::*`,
|
||||
`SequentialReader`, `LayoutBuilder`, `tunion::*`, `materialize::*`,
|
||||
`validate_bast_doc`, `build_validator`). No `#[cfg(fuzzing)]` hub is
|
||||
expected — the first choice remains a minimal `fuzzing` hub only if a
|
||||
needed item turns out `pub(crate)`, per the sibling pattern (alkcall
|
||||
never needed one).
|
||||
|
||||
**Verification-gate change:** `cargo test --manifest-path
|
||||
fuzz/shared/Cargo.toml` (corpus replay) joins AGENTS.md's verification
|
||||
checklist, as the siblings did.
|
||||
|
||||
## 3. Target inventory (5, in waves)
|
||||
|
||||
| # | Target | Drives | Input style | Status |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `bast_compile` | `AlkTypeEngine::compile` both modes (via `bast_meta` → `BastDoc` → plans → layout → validator) | raw bytes → serde_json → BAST doc | implemented 2026-09-30 |
|
||||
| 2 | `data_access` | the hand-rolled decode core (`src/data_access.rs`, read + write side) | raw `&[u8]` + chosen (offset, endian) | implemented 2026-09-30 |
|
||||
| 3 | `read_opseq` | stateful `SequentialReader` op sequences over hostile bytes under a fixed plan | `#[derive(Arbitrary)]` op enum | implemented 2026-09-30 |
|
||||
| 4 | `layout_build` | `LayoutBuilder::build` with adversarial `var_sizes` | `#[derive(Arbitrary)]` map shapes | implemented 2026-09-30 |
|
||||
| 5 | `validate_pair` | two-input structured: compile a schema once per exec, hammer hostile bytes through `validate_bytes`/`read_field`/`materialize` | `#[derive(Arbitrary)]` (doc, bytes) pair | implemented 2026-09-30 |
|
||||
|
||||
### Target 1 — `bast_compile` (the whole schema side, one choke point)
|
||||
|
||||
`AlkTypeEngine::compile` (`src/engine.rs:127-176`) fans out through the
|
||||
entire untrusted-JSON surface: `bast_meta::validate_bast_doc` →
|
||||
`BastDoc::new` → `ValidationPlan::compile` → `LayoutBuilder::new` +
|
||||
`ReadPlan::compile` (packed) or `OffsetMap::compute` (aligned) →
|
||||
`validation::build_validator` (when `json_schema` is `Some`).
|
||||
|
||||
- **Drives:** raw bytes → `serde_json` → `compile(value, root_name,
|
||||
mode, None)` in both modes; a second lane feeds `Some(schema)` with a
|
||||
second attacker-shaped JSON value for the jsonschema-build path.
|
||||
- **Invariants:**
|
||||
- no-panic on any JSON document, both modes;
|
||||
- compile is always `Result` — every rejection is a clean
|
||||
`AlkTypeError` (`Schema`/`Offset`/`Validation` payload classes,
|
||||
`src/error.rs:11-28`), never a panic or a silent bogus engine;
|
||||
- meta-schema gate ordering: any doc that fails
|
||||
`validate_bast_doc` must surface `Schema(...)` and must never reach
|
||||
layout/plan walks (shape partition);
|
||||
- parse-time caps hold exactly: `align > 4096`, arrays above
|
||||
`MAX_ARRAY_ELEMENTS`/`MAX_ARRAY_BYTES`, `maxLength >
|
||||
MAX_LENGTH`, depth > 128, and `$ref` cycles all reject at compile
|
||||
with the documented error classes (the walk-guard
|
||||
`check_ref_graph`, compile-depth, and cycle-`seen` machinery
|
||||
pinned by adversarial corpus entries);
|
||||
- if compile fails in packed it must also fail in aligned (mode
|
||||
independence of the schema-gate layer — the parse layers are
|
||||
shared; divergence means a mode-specific parse bug);
|
||||
- a successfully compiled engine's `endian()` equals the root
|
||||
struct's declared endianness.
|
||||
- **Seeds:** the full BAST feature menu (each kind, endian ×2, TUnion
|
||||
byte/field/enum discriminators, records, arrays, string/bytes
|
||||
encodings, `$ref` diamond), each reject-class corpus entry, plus the
|
||||
hostile menu in §4.
|
||||
|
||||
### Target 2 — `data_access` (the decode core)
|
||||
|
||||
Every byte-touching decode funnels through `read_array<const N: usize>`
|
||||
(`src/data_access.rs:48-75`): `checked_add(N)` → `check_bounds` →
|
||||
`.get(..)` → `try_into`. The widest attacker-influenced values in the
|
||||
crate are `read_bytes_indirect`'s absolute `{offset,length}` pair
|
||||
(`src/data_access.rs:328-350`).
|
||||
|
||||
- **Drives:** the `pub` read functions directly with the fuzzer
|
||||
choosing buffer, offset (including far-past-end and huge values),
|
||||
and endianness; lanes for `read_bytes`/`read_string` (u32 length
|
||||
prefix), `read_bytes_indirect`/`read_string_indirect` (the
|
||||
`{offset,length}` pair), `read_enum`, `read_bool` strictness, and
|
||||
each fixed-width kind from the macro family.
|
||||
- **Invariants:**
|
||||
- no-panic for any (buffer, offset, endian) triple;
|
||||
- `bool` accepts exactly 0x00/0x01 and rejects everything else
|
||||
(`:134-144` — the strictness is contract, pin it);
|
||||
- invalid UTF-8 in `read_string` errors (`Access`), never a lossy
|
||||
silently-corrupting parse (`:195-208`);
|
||||
- bounds partition: an error implies `checked_add`-overflow or
|
||||
`end > buffer_len` with the offending `field_path` named; an Ok
|
||||
implies the field sits fully inside the buffer;
|
||||
- nothing before/end-of-buffer is read: the decode consumes
|
||||
exactly its declared width (offset unchanged on error paths);
|
||||
- `read_bytes_indirect`'s data region always satisfies
|
||||
`data_offset + data_length ≤ buffer_len` on Ok, and neither
|
||||
field can push arithmetic past the buffer without an error
|
||||
(the two `u32` widening casts at `:334, :341` widening-only,
|
||||
verified by the partition).
|
||||
|
||||
### Target 3 — `read_opseq` (stateful, wave 2)
|
||||
|
||||
`SequentialReader` is stateful against attacker bytes (mutable cursor:
|
||||
`field_index`, `position`; `src/sequential_reader.rs:144-148`) and
|
||||
fuzzer-reachable operations are `read_next`, `read_next_borrowed`,
|
||||
`read_field` (out-of-order names), `reset` (`:157-300`).
|
||||
|
||||
- **Drives:** `#[derive(Arbitrary)]` op sequences (Next, Field(name
|
||||
choice), Reset, End) against a compiled plan — the plan built once
|
||||
per exec from a fixed small schema menu, bytes adversarial.
|
||||
- **Invariants:**
|
||||
- no-panic over any op interleaving and any buffer;
|
||||
- cursor discipline: a failed read leaves the reader usable (a
|
||||
subsequent `reset` restores the exact initial state; cursor never
|
||||
exceeds the buffer);
|
||||
- `read_next` returns fields exactly in plan order and `None`
|
||||
exactly at plan end; interleaved `read_field` for any field at
|
||||
any cursor state never panics and never mutates the sequential
|
||||
cursor (its offset argument comes from the plan, not the reader);
|
||||
- record-count spin bound: wire-controlled `count` loops
|
||||
(`src/materialize.rs:439-442`, `:817-820`,
|
||||
`src/sequential_reader.rs:985-988`) consume ≥ 4 verified bytes per
|
||||
iteration, so iterations are bounded by
|
||||
`remaining_bytes / 4` — a hostile count fails fast with `Access`
|
||||
(encode as an explicit per-exec assertion, not just
|
||||
no-panic/OOM);
|
||||
- engine-issued readers are independent: two readers over the same
|
||||
plan and buffer never observe each other's cursors
|
||||
(ADR-007's owned-fresh-reader contract).
|
||||
|
||||
### Target 4 — `layout_build` (wave 2)
|
||||
|
||||
`LayoutBuilder::new` parses once (`src/layout_builder.rs:154-156`);
|
||||
`build(&HashMap<String, usize>)` (`:189`) is repeatable with
|
||||
attacker-shaped `var_sizes` driving write-position arithmetic in
|
||||
`walk_struct`.
|
||||
|
||||
- **Drives:** a fixed schema menu containing every variable-width
|
||||
encoding × `#[derive(Arbitrary)]` `var_sizes` maps and write values
|
||||
(`FieldValue` shapes).
|
||||
- **Invariants:**
|
||||
- no-panic across adversarial size maps (zero, huge, mismatched
|
||||
with `max_length`/`count` declarations);
|
||||
- every failed write leaves the buffer untouched (byte-equal to the
|
||||
pre-call snapshot) or documented-partial exactly where the
|
||||
contract allows — pin the actual contract the code implements;
|
||||
- field positions from a successful `build` are disjoint and
|
||||
in-bounds for the reported total size;
|
||||
- `data_offset/length` pairs written by
|
||||
`write_string_indirect`/`write_bytes_indirect` always satisfy the
|
||||
read-side `read_*_indirect` bounds partition above — the write
|
||||
side and the read side of the pair are one contract
|
||||
(round-trip pair; `:373-417` guards verified by
|
||||
`:733-750`-style assertions).
|
||||
|
||||
### Target 5 — `validate_pair` (two-input structured, wave 3)
|
||||
|
||||
The integration target: schema and bytes are both adversarial.
|
||||
|
||||
- **Drives:** `#[derive(Arbitrary)]` (doc, bytes) — compile once per
|
||||
exec with whichever mode the fuzzer picks, then drive
|
||||
`validate_bytes`, `read_field` (arbitrary field paths, including
|
||||
junk paths), `materialize_packed`/`materialize_aligned`, and
|
||||
`read_next` under the compiled plan.
|
||||
- **Invariants:**
|
||||
- no-panic for any (doc, bytes) pair, either mode;
|
||||
- validate/read/materialize agreement lattice: `validate_bytes` Ok
|
||||
⇒ `materialize_*` Ok and every `read_field` over a declared path
|
||||
Ok; `materialize_*` error ⇒ `validate_bytes` error on the same
|
||||
buffer (exact agreement direction pinned per the code's actual
|
||||
contract — determine the strict/loose ordering from the
|
||||
`validate_bytes` implementation, don't assume);
|
||||
- non-finite floats (NaN/Inf) surfaced by `materialize` are always
|
||||
`Access` errors, never silently `Null`/`0.0`
|
||||
(`src/materialize.rs:876-883`);
|
||||
- unknown field-path strings always error with `Access` naming the
|
||||
path, never panic, never index the map by substring drift;
|
||||
- `Value` output is serde-safe: `materialize_*` output round-trips
|
||||
through `serde_json::to_vec` and back to a structurally equal
|
||||
`Value` (structural only — this crate's serde_json builds with
|
||||
`preserve_order`, so object key order is preserved; byte-identity
|
||||
round-trips are acceptable only where the docs say lossless,
|
||||
per the alkcall §7.3 false-positive trap when they don't).
|
||||
|
||||
## 4. Corpus policy
|
||||
|
||||
Committed hand-made seeds per target, generated by
|
||||
`fuzz/gen_fuzz_seeds.py` (deterministic, in-tree, quiche pattern);
|
||||
grown corpora and artifacts gitignored. Seed menus:
|
||||
|
||||
- `bast_compile`: a minimal valid packed doc and aligned doc; every
|
||||
`AlkTypeKind` once; each reject class (`align` 4097/65536/u32-max,
|
||||
`count` over cap, `maxLength` over cap, depth-129 nesting both
|
||||
inline-nested and via `$ref` chains, `$ref` cycle, `$ref` to
|
||||
missing def, root not a struct, missing `type`, unknown kind
|
||||
string, duplicate field names first-wins probe, non-object doc,
|
||||
deeply-nested JSON at serde_json's own 128 limit); `json.dict`
|
||||
carries the BAST token set.
|
||||
- `data_access`: minimal valid encodings per kind per endianness;
|
||||
truncation at every prefix length (1..N-1 for each width);
|
||||
`len = 0` / `MAX_LENGTH` / `u32::MAX` prefixes; the indirect pair at
|
||||
{0,0}, {len, big}, {big, 0}, {u32::MAX, u32::MAX}; offset one-past-
|
||||
end, offset u32-magnitude; 0x02 bool byte; invalid UTF-8 in a
|
||||
string; enum value out of range; NaN/Inf bytes.
|
||||
- `read_opseq` / `layout_build` (wave 2, **done 2026-09-30**): the
|
||||
semantic fixtures — full sequential walk, reset-mid-walk then full
|
||||
walk again, failed read then reset, record loop with a hostile count
|
||||
under a real buffer, junk field paths; zero/huge/mismatched
|
||||
`var_sizes`; overwrite-everything write; indirect-pair overflow
|
||||
write, plus the write-then-read pair fixture. `read_opseq` seeds
|
||||
also encode truncation at every prefix of the full-walk buffer. The
|
||||
hand-written seeds are byte-encoded against the pinned arbitrary
|
||||
1.4.2 derive layout and *pinned by decode tests* (both targets carry
|
||||
a `decode_lands_on_the_intended_variants` replay test, the alkhttp
|
||||
target-4 pattern).
|
||||
- `validate_pair` (wave 3, **done 2026-09-30**): hostile-schema/
|
||||
valid-bytes, valid-schema/hostile-bytes, valid/valid — plus the §6
|
||||
candidate shapes as pinned reproducers. 48 committed seeds incl.
|
||||
the W3-1/W3-2 artifact bytes, the aligned maxLength fixtures, and
|
||||
the mode-agreement pins (ADR-006/ADR-008 lanes).
|
||||
|
||||
Stateful `Arbitrary` seeds are hand-encoded against the `arbitrary`
|
||||
1.4.x derive layout with per-element keep-going bytes, pinned by
|
||||
seed-decode tests (alkhttp's target-4 pattern).
|
||||
|
||||
## 5. Campaign + gate policy
|
||||
|
||||
Identical to the siblings (alkcall §7.9 posture; no hosted CI in this
|
||||
repo):
|
||||
|
||||
- **Corpus replay is the standing fuzz gate:** `cargo test
|
||||
--manifest-path fuzz/shared/Cargo.toml` — joins AGENTS.md's
|
||||
verification checklist.
|
||||
- **Campaigns run detached** via `fuzz/run-detached.sh`; budget 10 min
|
||||
per target for a smoke campaign, 30–45 min before a release or after
|
||||
touching `src/data_access.rs`, `src/schema.rs`, the compile walks, or
|
||||
the sequential reader.
|
||||
- **Grown corpora stay gitignored** (hash-named files ignored via
|
||||
pattern; committed `seed-*` files stay); merge worthy entries into
|
||||
seeds only deliberately.
|
||||
- libFuzzer flags worth pinning: `-rss_limit_mb=2048`,
|
||||
`-malloc_limit_mb=2048`, `-timeout=25`, `-max_len=65536`,
|
||||
`-use_value_profile=1`, and `-dict=json.dict` on the JSON targets
|
||||
(alkcall §7.2's set, minus the CI-tier concerns).
|
||||
- Nightly stays confined to `fuzz/`; the main crate's stable build,
|
||||
MSRV, and wasm target are untouched — `cargo fuzz build` must never
|
||||
be a prerequisite for `cargo test`/`clippy`/`build`.
|
||||
|
||||
Release-budget campaign results (2026-09-30, 45–46 min per target,
|
||||
§5 policy, detached, `-use_value_profile=1`, `-dict=json.dict` on the
|
||||
JSON lanes): all five exited 0.
|
||||
|
||||
- `bast_compile` — 818k execs at ~300–500/s (2,756 s), coverage
|
||||
19,894 edges / 86,455 features / 4,439 in-memory corpus entries,
|
||||
**still growing at budget end** (2× the wave-1 45-min smoke edge
|
||||
count). Longest campaigns keep paying on the schema side.
|
||||
- `data_access` — 11.3M execs at ~3.7–4.7k/s (2,761 s), coverage 684
|
||||
edges / 6,208 features — value-profile lifted the "saturated" 379
|
||||
edges to 684 (the wave-1 ceiling was the no-profile ceiling).
|
||||
- `read_opseq` — 63k execs at ~23/s (2,737 s), coverage 17,519 edges /
|
||||
65,059 features / 1,668 entries; the stateful search kept adding
|
||||
features across the full budget (assertion-throughput-bound).
|
||||
- `layout_build` — 63k execs at ~23/s (2,751 s), coverage 17,659
|
||||
edges / 71,477 features / 1,873 entries; write-side contracts held.
|
||||
- `validate_pair` — three crashes over two runs before and one clean
|
||||
run after the W3-1/W3-2/W3-3 fixes (final post-fix campaign 30 min):
|
||||
37k execs (final 1,820 s run), coverage 10,850 edges / 38,364
|
||||
features / 1,544 entries, `oom/timeout/crash: 0/0/0` at budget end.
|
||||
The two-input harness is the highest-yield target in the crate: 1
|
||||
real engine bug + 2 pinned contracts in its first campaigns.
|
||||
- Artifact totals: the three `validate_pair` crash artifacts are all
|
||||
pinned as committed seeds/regression tests (W3-1 `seed-044`, W3-2
|
||||
`seed-047`, W3-3 reproduced by the aligned maxLength fixtures);
|
||||
`bast_compile`/`data_access`/`read_opseq`/`layout_build` artifacts
|
||||
empty. No OOM, timeout, or leak on any fork job of any target.
|
||||
|
||||
Wave-2 smoke campaign results (2026-09-30, 10 min per target, §5
|
||||
policy, detached):
|
||||
|
||||
- `read_opseq` — 52.1k+ execs at ~90/s, exit 0, empty artifact dir,
|
||||
no oom/timeout/crash on any fork job. The typed-input decode plus
|
||||
the per-op assertion work makes this the slowest target per exec in
|
||||
the crate so far; coverage ~9,365 edges / 15,797 features / 293
|
||||
in-memory corpus entries. The heavy semantic invariants
|
||||
(replay-after-failure, full-walk spin bounds, reader independence,
|
||||
plan-order assertion) run per op, not per exec — the campaign is
|
||||
assertion-throughput-bound, not coverage-bound; the stateful search
|
||||
(op × cursor × buffer shape) was still adding features at budget end.
|
||||
- `layout_build` — 42.4k+ execs at ~84/s, exit 0, empty artifact dir,
|
||||
no oom/timeout/crash on any fork job; coverage 9,432 edges / 19,204
|
||||
features / 181 in-memory corpus entries. The adversarial `var_sizes`
|
||||
map space over five schema menus exercised the missing-size /
|
||||
unknown-discriminator / overflow rejection paths; the write-side
|
||||
contracts (failed write leaves buffer byte-identical, positional
|
||||
disjointness and bounds) held everywhere.
|
||||
|
||||
Wave-1 smoke campaign results (2026-09-30, 10 min per target, both
|
||||
exited 0, artifact dirs empty — no crash/hang/OOM/leak):
|
||||
|
||||
- `bast_compile` — 543k execs at ~1.1k/s (each exec compiles two
|
||||
engines through the whole fan-out — meta gate, parse, plans, layout
|
||||
walks — in both modes, so per-exec work is heavy), coverage 10,830
|
||||
edges / 25,326 features / 1,293 in-memory corpus entries, **still
|
||||
growing at budget end** — longer campaigns keep paying.
|
||||
- `data_access` — 3.5M+ execs at ~7–8k/s, coverage saturated at 379
|
||||
edges / 574 features / 37 corpus entries (the pure-decode-core
|
||||
ceiling is fully enumerated; the alkcall `chunk_header` profile).
|
||||
- Both exited 0 with empty artifact directories; `oom/timeout/crash:
|
||||
0/0/0` on every fork job.
|
||||
- **Seeds regenerate deterministically:** `python3
|
||||
fuzz/gen_fuzz_seeds.py`.
|
||||
- Toolchain notes live in `fuzz/README.md`; nightly stays confined to
|
||||
`fuzz/`.
|
||||
|
||||
## 6. Pre-fuzzing candidate findings (confirm or refute)
|
||||
|
||||
These are pre-fuzzing code-review findings from the 0.3.0 inventory,
|
||||
verified against the code. They define what the targets must encode as
|
||||
invariants and are the first corpus entries to add; the fuzzer
|
||||
confirms or refutes them.
|
||||
|
||||
1. **Wire-controlled `Record` count loops** (`src/materialize.rs:439-
|
||||
442`, `:817-820`, `src/sequential_reader.rs:985-988`): the only
|
||||
buffer-derived loop counts (`read_u32(..)? as usize` then
|
||||
`for i in 0..count`). Each iteration performs at least one
|
||||
bounds-checked read, so a hostile count should fail fast with
|
||||
`Access` — bounded by `remaining_bytes / 4`, no allocation, no
|
||||
spin. Correct as designed *if and only if* that holds; the
|
||||
`read_opseq` target encodes it as an explicit assertion (§3
|
||||
target 3) so the fuzzer can break it the moment any per-entry
|
||||
cost stops being `≥ 4 verified bytes`.
|
||||
2. **Attacker-controlled absolute `{offset,length}` pairs**
|
||||
(`read_bytes_indirect`/`read_string_indirect`,
|
||||
`src/data_access.rs:307-350`): the widest attacker-influenced
|
||||
values in the crate (each `u32`, up to 2³²−1). The pattern
|
||||
(widening cast → `checked_add` pair-sum → full bounds check)
|
||||
looks correct; campaign confirms the bounds partition on every
|
||||
(buffer, pair) input, both sides of the write/read contract
|
||||
(§3 target 2 / target 4).
|
||||
3. **Duplicate field-path tolerance by design** (`OffsetMap::build_
|
||||
index`, `src/offset_map.rs:199-205`: first-wins; `BastStruct::
|
||||
parse` does not reject duplicates): ambiguous lookups are
|
||||
documented behavior. Encode as an invariant — a successful
|
||||
engine's `read_field` resolves duplicates deterministically
|
||||
(first wins) — so a future "reject duplicates" change shows up
|
||||
as a deliberate contract change, not silent drift.
|
||||
4. **`align_up`/`round_up` plain `+` arithmetic** (`src/offset_map.rs:
|
||||
618-639`): un-checked `+ align - rem` in a field of
|
||||
schema-controlled values. Overflow-infeasible today because
|
||||
align is capped at parse (`MAX_ALIGN` = 4096) and the running
|
||||
offset is monotonically checked — a `bast_compile` corpus entry
|
||||
with align at the cap pinning the boundary keeps it that way if
|
||||
the cap ever moves.
|
||||
5. **`bast_validation::validate_value` recompiles a `ValidationPlan`
|
||||
per call** (`src/bast_validation.rs:64-65`): a repeated-op DoS
|
||||
surface if any consumer compiles-per-call. Not a bug in this
|
||||
crate's API; fuzz targets compile once per exec, and the doc
|
||||
records the compile cost as the consumer's responsibility.
|
||||
|
||||
6. **Missing bounds check in `plan_read_array` — CONFIRMED and FIXED
|
||||
(wave 2, finding W2-1, 2026-09-30).** The struct arm
|
||||
(`plan_bounds_check`) and the union fixed-size arm both verify the
|
||||
computed end against the buffer; `plan_read_array`
|
||||
(`src/sequential_reader.rs:898` pre-fix) computed `end` and
|
||||
returned `Ok` without the check. A truncated fixed-stride array
|
||||
reported `Ok(Some(...))` with `element_start + count × stride` past
|
||||
the buffer; the failure surfaced at the *next* field (naming the
|
||||
wrong field in the error), or never when the array was the last
|
||||
field — a completed walk returning `Ok` over a short buffer. Found
|
||||
by hand-running the `read_opseq` drive before the campaign; fixed
|
||||
with an end-vs-buffer check returning `Access` naming the array
|
||||
field; regression test in `src/sequential_reader.rs`. The wave-2
|
||||
seeds' array-truncation fixtures keep the boundary pinned.
|
||||
7. **Packed-mode `encoding: offset-indirect` is a silent no-op —
|
||||
PINNED, open design question (wave 2).** `BastField::parse`
|
||||
records the annotation; the packed reader
|
||||
(`plan_read_primitive`) ignores it and reads inline
|
||||
length-prefixed — correct per bast-format.md's "Default strategy
|
||||
selection" table, but nothing rejects the declaration in packed
|
||||
mode (aligned mode consumes it; aligned rejects it on record
|
||||
fields only). The wave-2 replay test
|
||||
`packed_mode_is_always_inline_length_prefixed` pins the current
|
||||
behavior. If the packed reader ever grows encoding awareness, the
|
||||
pin flips deliberately; if the format instead wants the
|
||||
declaration rejected in packed mode, that is a schema-gate change
|
||||
with the mode-agreement invariant to re-verify.
|
||||
|
||||
8. **Aligned `maxLength` reservation read paths disagreed —
|
||||
CONFIRMED as a real engine bug and FIXED (wave 3, finding W3-3,
|
||||
found by the `validate_pair` campaign).** The materializer and
|
||||
`validate_bytes` implement ADR-003 strategy 2 correctly (raw
|
||||
zero-padded window, NUL-trimmed), but `AlkTypeEngine::read_field`
|
||||
dispatched every aligned String/Bytes leaf through the
|
||||
length-prefixed `data_access::read_string`/`read_bytes` — parsing
|
||||
the reservation window's first four raw bytes as a u32 length —
|
||||
and `write_field` wrote prefix+data into the raw window. Any
|
||||
aligned schema declaring `maxLength` whose first reservation bytes
|
||||
looked like a large prefix broke the validate_bytes ⇒ read_field
|
||||
lattice (validate Ok, read Access with bogus bounds). Fixed by
|
||||
recording the strategy in `LeafMeta`'s encoding
|
||||
(`VariableEncoding::MaxLengthReserved`, additive variant) and
|
||||
dispatching read/write through new
|
||||
`data_access::read_reservation{,_string}`/`write_reservation`
|
||||
sharing the materializer's exact semantics. Regression tests in
|
||||
`src/engine.rs` + `data_access.rs`; the W3-1 artifact bytes are
|
||||
the committed reproducer (`seed-044`).
|
||||
|
||||
None of these rises to the alkcall §6.2 / alkhttp FWD-20 class; they
|
||||
are boundary-confirmations, which is exactly the expected profile of
|
||||
this crate (§1 honest caveat). Smoke-campaign evidence: no panics or
|
||||
OOM/timeouts on any of the 383k+ wave-1/2 combined execs; the
|
||||
release-budget campaigns added 12.3M+ execs with the W3 findings
|
||||
above. Candidates 1, 2, and 4 held under the parser-level drives —
|
||||
candidates 1 and 2 got their explicit stateful assertions in waves
|
||||
2–3 (targets 3–5), and 4 keeps its boundary corpus entry. Candidate 3
|
||||
(duplicate first-wins) is pinned by the wave-2 `layout_build` index
|
||||
assertions and the existing crate tests; candidate 6 was confirmed as
|
||||
a genuine bug and fixed in wave 2; candidate 7 is pinned with an open
|
||||
design question; candidate 8 was confirmed as a genuine bug and fixed
|
||||
in wave 3.
|
||||
|
||||
## 7. Sequencing
|
||||
|
||||
1. ✅ **Wave 1 (2026-09-30, commit `ed41d77`)** — infra (`workspace`
|
||||
exclude, toolchain pin, runner, seed generator, README,
|
||||
`.gitignore`) + targets 1–2 + corpora (136 seeds) + corpus replay +
|
||||
AGENTS.md gate + smoke campaigns (clean, see §5).
|
||||
2. ✅ **Wave 2 (2026-09-30)** — targets 3–4 (stateful `read_opseq`,
|
||||
`layout_build`) + 73 seeds + decode-pin tests + smoke campaigns.
|
||||
One real bug found and fixed first-session (`plan_read_array`
|
||||
bounds check, §6 candidate 6); packed-mode offset-indirect pinned
|
||||
as a documented no-op (§6 candidate 7).
|
||||
3. ✅ **Wave 3 (2026-09-30)** — target 5 `validate_pair` (the
|
||||
two-input structured harness) + 48 seeds + decode-pin tests +
|
||||
45-min release-budget campaigns across all five targets. Three
|
||||
findings: W3-1 (harness over-assertion corrected + pinned), W3-2
|
||||
(upstream serde_json one-ulp f64 parse drift pinned with slack),
|
||||
W3-3 (real engine bug — aligned maxLength reservation misread by
|
||||
`read_field`/`write_field` — fixed with `VariableEncoding::
|
||||
MaxLengthReserved` + `data_access::read_reservation*`/
|
||||
`write_reservation`, commit `a0dd3d2`). Post-fix
|
||||
validate_pair campaign clean to budget end. Fuzzing complete per
|
||||
§2 scope: corpus replay 30/30 is the standing gate.
|
||||
4. Everything else inherited verbatim: no hosted CI, OSS-Fuzz out,
|
||||
no Actions/workflow files anywhere in the repo (alkcall §7.9).
|
||||
|
||||
## 8. References
|
||||
|
||||
- alkcall `docs/research/fuzzing.md` — rationale, tool landscape,
|
||||
campaign containment (§7.6), no-hosted-CI policy (§7.9)
|
||||
- alkhttp `docs/plans/fuzzing.md` — the live pattern this plan copies
|
||||
(waves, findings log, `fuzzing` hub convention)
|
||||
- RUSTSEC-2026-0037 / CVE-2026-31812 (quinn-proto) — the remote-DoS
|
||||
class this crate's peers are exposed to
|
||||
- Internal: ADR-002 (layout modes), ADR-004 (error/validation
|
||||
strategy), ADR-006 (aligned-mode variable-field rejection), ADR-008
|
||||
(aligned-mode TUnion rejection), ADR-010 (`validate_bytes`,
|
||||
materialize-then-validate)
|
||||
Generated
+1049
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,52 @@
|
||||
[package]
|
||||
name = "alktype-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
alktype-fuzz-shared = { path = "shared" }
|
||||
|
||||
[dependencies.alktype]
|
||||
path = ".."
|
||||
|
||||
[[bin]]
|
||||
name = "bast_compile"
|
||||
path = "fuzz_targets/bast_compile.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "data_access"
|
||||
path = "fuzz_targets/data_access.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "read_opseq"
|
||||
path = "fuzz_targets/read_opseq.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "layout_build"
|
||||
path = "fuzz_targets/layout_build.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "validate_pair"
|
||||
path = "fuzz_targets/validate_pair.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[workspace]
|
||||
@@ -0,0 +1,67 @@
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
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)
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
cd fuzz && cargo fuzz build
|
||||
# or from the repo root — the toolchain file is picked up by path:
|
||||
cargo fuzz build -D
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "x", "kind": "uint8"}]}, "root": "S"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "endian": "big", "fields": [{"name": "x", "kind": "uint8"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "x", "kind": "uint8"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": []}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "f0", "kind": "int8"}, {"name": "f1", "kind": "int16"}, {"name": "f2", "kind": "int32"}, {"name": "f3", "kind": "int64"}, {"name": "f4", "kind": "uint8"}, {"name": "f5", "kind": "uint16"}, {"name": "f6", "kind": "uint32"}, {"name": "f7", "kind": "uint64"}, {"name": "f8", "kind": "float32"}, {"name": "f9", "kind": "float64"}, {"name": "f10", "kind": "bool"}, {"name": "f11", "kind": "string"}, {"name": "f12", "kind": "bytes"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "endian": "little", "fields": [{"name": "a", "kind": "uint32", "endian": "big"}, {"name": "b", "kind": "string", "encoding": "length-prefixed", "maxLength": 64}, {"name": "c", "kind": "bytes", "encoding": "offset-indirect", "maxLength": 128}, {"name": "d", "kind": "string", "encoding": "offset-indirect"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "child", "kind": {"$ref": "#/$defs/Nested"}}, {"name": "arr", "kind": {"kind": "array", "element": "uint32", "count": 3}}, {"name": "arr0", "kind": {"kind": "array", "element": "uint8", "count": 0}}, {"name": "rec", "kind": {"kind": "record", "values": "string"}}, {"name": "en", "kind": {"$ref": "#/$defs/E"}}, {"name": "un", "kind": {"$ref": "#/$defs/U1"}}, {"name": "un2", "kind": {"$ref": "#/$defs/U2"}}]}, "Nested": {"kind": "struct", "fields": [{"name": "y", "kind": "int16"}]}, "E": {"kind": "enum", "values": ["a", "b", "c"]}, "U1": {"kind": "union", "discriminator": {"kind": "byte", "offset": 0, "type": "uint8"}, "mapping": {"0": "uint8", "1": {"$ref": "#/$defs/Nested"}}}, "U2": {"kind": "union", "discriminator": {"kind": "field", "name": "tag"}, "fields": [{"name": "tag", "kind": "uint32"}], "mapping": {"0": "uint8", "7": "string"}}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "inl", "kind": {"kind": "struct", "fields": [{"name": "z", "kind": "uint8"}]}}, {"name": "inle", "kind": {"kind": "enum", "values": ["x"]}}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "l", "kind": {"$ref": "#/$defs/N"}}, {"name": "r", "kind": {"$ref": "#/$defs/N"}}]}, "N": {"kind": "struct", "fields": [{"name": "v", "kind": "uint8"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "x", "kind": "uint8"}], "align": 4096}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "a", "kind": "uint32", "align": 16}, {"name": "b", "kind": "uint8", "align": 1}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "s", "kind": "string", "maxLength": 67108864}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "s", "kind": "string", "maxLength": 0}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "align": 4097, "fields": []}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "align": 65536, "fields": []}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "align": 18446744073709551615, "fields": []}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "a", "kind": {"kind": "array", "element": "uint8", "count": 65537}}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "a", "kind": {"kind": "array", "element": "uint8", "count": 18446744073709551615}}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "s", "kind": "string", "maxLength": 67108865}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "x", "kind": "nonsense"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "uint8", "fields": []}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct"}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "a", "kind": {"$ref": "#/$defs/S"}}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "a", "kind": {"$ref": "#/$defs/Missing"}}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {}}
|
||||
@@ -0,0 +1 @@
|
||||
[1, 2, 3]
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "a", "kind": 123}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "9bad", "kind": "uint8"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "a", "kind": "uint8", "bogus": true}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "union", "discriminator": {"kind": "byte", "offset": 0, "type": "uint8"}}}}
|
||||
@@ -0,0 +1 @@
|
||||
not json at all
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S":
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n",Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n", "kind": {"kind": "struct", "fields": [{"name": "n",Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "x", "kind": "uint8"}]}, "D100": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/S"}}]}, "D99": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D100"}}]}, "D98": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D99"}}]}, "D97": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D98"}}]}, "D96": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D97"}}]}, "D95": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D96"}}]}, "D94": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D95"}}]}, "D93": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D94"}}]}, "D92": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D93"}}]}, "D91": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D92"}}]}, "D90": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D91"}}]}, "D89": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D90"}}]}, "D88": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D89"}}]}, "D87": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D88"}}]}, "D86": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D87"}}]}, "D85": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D86"}}]}, "D84": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D85"}}]}, "D83": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D84"}}]}, "D82": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D83"}}]}, "D81": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D82"}}]}, "D80": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D81"}}]}, "D79": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D80"}}]}, "D78": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D79"}}]}, "D77": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D78"}}]}, "D76": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D77"}}]}, "D75": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D76"}}]}, "D74": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D75"}}]}, "D73": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D74"}}]}, "D72": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D73"}}]}, "D71": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D72"}}]}, "D70": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D71"}}]}, "D69": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D70"}}]}, "D68": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D69"}}]}, "D67": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D68"}}]}, "D66": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D67"}}]}, "D65": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D66"}}]}, "D64": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D65"}}]}, "D63": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D64"}}]}, "D62": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D63"}}]}, "D61": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D62"}}]}, "D60": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D61"}}]}, "D59": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D60"}}]}, "D58": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D59"}}]}, "D57": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D58"}}]}, "D56": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D57"}}]}, "D55": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D56"}}]}, "D54": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D55"}}]}, "D53": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D54"}}]}, "D52": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D53"}}]}, "D51": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D52"}}]}, "D50": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D51"}}]}, "D49": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D50"}}]}, "D48": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D49"}}]}, "D47": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D48"}}]}, "D46": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D47"}}]}, "D45": {"kind": "struct", "fields": [{"name": "n", "kind": {"$ref": "#/$defs/D46"}}]}, "D44": {"kind": "struct", "fields": [{"name": "Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "struct", "fields": [{"name": "a", "kind": "uint8"}, {"name": "a", "kind": "uint16"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"$defs": {"S": {"kind": "enum", "values": ["only"]}, "root": "S"}}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
�
|
||||
@@ -0,0 +1 @@
|
||||
�
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
4
|
||||
@@ -0,0 +1 @@
|
||||
4
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
��
|
||||
@@ -0,0 +1 @@
|
||||
��
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
D3"
|
||||
@@ -0,0 +1 @@
|
||||
"3D
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
����
|
||||
@@ -0,0 +1 @@
|
||||
����
|
||||
@@ -0,0 +1 @@
|
||||
�fUD3"
|
||||
@@ -0,0 +1 @@
|
||||
"3DUfwˆ
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
����
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
����
|
||||
Binary file not shown.
Loaded 100 of 289 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user