wai: coverage-guided fuzzing of the reader, and the container reservations it found #256

Closed
dcharlot wants to merge 0 commits from wai/coverage-fuzzing into main
Owner

Stacked on #255. This branch contains #255's commit unchanged. Until #255 merges, the diff here includes it; after that it needs no rebase.

This PR adds coverage-guided fuzzing of the WAI reader, closing the last known gap SECURITY.md listed. In its first seconds the fuzzer found a defect in every extension container reader, which this PR also fixes.

What it adds

wai/wai-rs/fuzz/ holds four libFuzzer targets, built by cargo-fuzz with AddressSanitizer:

target what it reads
envelope version detection, the WAI1 and WAI2 readers, and the listing form
int_payload int_payload::decode_int, against each (capability, parameter set) pair the integer conformance corpus commits; the first byte picks the pair
media the picture, audio, video and text decoders of the floor and the recommended set; the first byte picks the decoder
containers the extension containers: world, causal op, film, reel, score, haptic, splat4d and audio scene

Seeds. The seeds binary builds the corpora from the repository itself:

  • every committed envelope, integer payload and container;
  • one valid payload per decoder, made by the reference writers.

Running it. run.sh <seconds> [targets] runs the targets for that long each:

  • exit 1 means a find; its input is kept in fuzz/artifacts/;
  • exit 2 means the tool itself failed.

Schedule. wai-fuzz.yml runs each target for 15 minutes every night on the macOS runner, which has the codec libraries. It never runs per pull request, and it keeps the corpus between nights.

Two build fixes:

  • run.sh builds with --no-cfg-fuzzing. rav1e depends on libfuzzer-sys under cfg(fuzzing), which left wai's cdylib with an undefined libFuzzer symbol.
  • wai-rs's one multi-line inline table (c2pa) now sits on one line, because cargo-fuzz's TOML parser rejects the multi-line form.

What it found

The audio scene reader reserved space for a u32 count of entries read from the input, before reading a single entry. So a few bytes could ask for 48 GiB. The world, reel, score, haptic and splat4d readers had the same defect.

Each reader now reserves no more entries than its remaining bytes could hold.

tests/container_reservations.rs checks this:

  • It rewrites a committed vector of each container to declare 2³² − 1 entries in four bytes.
  • It requires a refusal with no single allocation past a few megabytes. Its allocator records the largest request and refuses anything over 256 MiB, so a reader that still sizes from the count fails the test instead of reserving.
  • On the old readers it fails: the world asset table alone asked for 256 GiB (falsified on the build host).

wai-conformance runs the test on every leg.

SECURITY.md

SECURITY.md now says what is fuzzed. It keeps two known gaps:

  • The C libraries are not instrumented, so a memory error inside one shows up only if it crashes.
  • The extension formats' declared durations have no reader limit. Reading a container is bounded, but rendering one allocates in proportion to its declared duration. The fix is a duration limit in each extension's spec, which is queued as its own change.

Checked

Build host:

  • smoke runs of envelope, int_payload and media, 120 s each: nothing found;
  • containers: found the reservations above within seconds; once they were fixed, it ran 300 s and found nothing;
  • container_reservations passes;
  • 275 lib tests pass with the container features.

Guard scripts, all clean: tests-in-CI, spec refs, section refs, workflow concurrency, lockfiles, vanity metrics, comparative prose, public names.

🤖 Generated with Claude Code

**Stacked on #255.** This branch contains #255's commit unchanged. Until #255 merges, the diff here includes it; after that it needs no rebase. This PR adds coverage-guided fuzzing of the WAI reader, closing the last known gap SECURITY.md listed. In its first seconds the fuzzer found a defect in every extension container reader, which this PR also fixes. ## What it adds `wai/wai-rs/fuzz/` holds four libFuzzer targets, built by cargo-fuzz with AddressSanitizer: | target | what it reads | |---|---| | `envelope` | version detection, the `WAI1` and `WAI2` readers, and the listing form | | `int_payload` | `int_payload::decode_int`, against each (capability, parameter set) pair the integer conformance corpus commits; the first byte picks the pair | | `media` | the picture, audio, video and text decoders of the floor and the recommended set; the first byte picks the decoder | | `containers` | the extension containers: world, causal op, film, reel, score, haptic, splat4d and audio scene | **Seeds.** The `seeds` binary builds the corpora from the repository itself: - every committed envelope, integer payload and container; - one valid payload per decoder, made by the reference writers. **Running it.** `run.sh <seconds> [targets]` runs the targets for that long each: - exit 1 means a find; its input is kept in `fuzz/artifacts/`; - exit 2 means the tool itself failed. **Schedule.** `wai-fuzz.yml` runs each target for 15 minutes every night on the macOS runner, which has the codec libraries. It never runs per pull request, and it keeps the corpus between nights. **Two build fixes:** - `run.sh` builds with `--no-cfg-fuzzing`. rav1e depends on libfuzzer-sys under `cfg(fuzzing)`, which left wai's cdylib with an undefined libFuzzer symbol. - wai-rs's one multi-line inline table (`c2pa`) now sits on one line, because cargo-fuzz's TOML parser rejects the multi-line form. ## What it found The audio scene reader reserved space for a `u32` count of entries read from the input, before reading a single entry. So a few bytes could ask for 48 GiB. The world, reel, score, haptic and splat4d readers had the same defect. Each reader now reserves no more entries than its remaining bytes could hold. `tests/container_reservations.rs` checks this: - It rewrites a committed vector of each container to declare 2³² − 1 entries in four bytes. - It requires a refusal with no single allocation past a few megabytes. Its allocator records the largest request and refuses anything over 256 MiB, so a reader that still sizes from the count fails the test instead of reserving. - On the old readers it fails: the world asset table alone asked for 256 GiB (falsified on the build host). `wai-conformance` runs the test on every leg. ## SECURITY.md SECURITY.md now says what is fuzzed. It keeps two known gaps: - **The C libraries are not instrumented**, so a memory error inside one shows up only if it crashes. - **The extension formats' declared durations have no reader limit.** Reading a container is bounded, but rendering one allocates in proportion to its declared duration. The fix is a duration limit in each extension's spec, which is queued as its own change. ## Checked **Build host:** - smoke runs of `envelope`, `int_payload` and `media`, 120 s each: nothing found; - `containers`: found the reservations above within seconds; once they were fixed, it ran 300 s and found nothing; - `container_reservations` passes; - 275 lib tests pass with the container features. **Guard scripts, all clean:** tests-in-CI, spec refs, section refs, workflow concurrency, lockfiles, vanity metrics, comparative prose, public names. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wai: reader limits for picture, audio and video payloads
Some checks failed
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
tests in CI / every test file is run by a job or listed with its reason (pull_request) Has been cancelled
standards workspaces build + test / jouleclaw-rs (pull_request) Has been cancelled
standards workspaces build + test / aac-rs (pull_request) Has been cancelled
standards workspaces build + test / arl-rs (pull_request) Has been cancelled
standards workspaces build + test / eoc-rs (pull_request) Has been cancelled
standards workspaces build + test / jcp-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-ui-rs (pull_request) Has been cancelled
standards workspaces build + test / joulecontract-rs (pull_request) Has been cancelled
standards workspaces build + test / jouledesk-rs (pull_request) Has been cancelled
standards workspaces build + test / joulehook-rs (pull_request) Has been cancelled
standards workspaces build + test / map-rs (pull_request) Has been cancelled
standards workspaces build + test / mesh-rs (pull_request) Has been cancelled
standards workspaces build + test / openpay (pull_request) Has been cancelled
standards workspaces build + test / proof-rs (pull_request) Has been cancelled
standards workspaces build + test / sandbox-rs (pull_request) Has been cancelled
standards workspaces build + test / sense-rs (pull_request) Has been cancelled
standards workspaces build + test / smart-byte-rs (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
spec citations resolve / Code cites no section its SPEC lacks (pull_request) Has been cancelled
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no comparison (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no company or product (pull_request) Has been cancelled
no-vanity-metrics / No new orphan source files (pull_request) Has been cancelled
lockfiles / every Cargo.lock is current (pull_request) Has been cancelled
f9215b5e01
The image, audio and AV1 decoders allocated what a payload declared, within whatever
their libraries allowed: the gap SECURITY.md listed first. SPEC §2 now sets reader
limits for them as it does for manifests and text: a picture up to 65 536 on a side
and 2^27 pixels, audio up to 2^27 samples counting every channel, AV1 up to 2^20
temporal units of such pictures. A reader may refuse past them (image_too_large,
audio_decoded_too_long, video_too_large), from what the payload declares; the writers
refuse the same. SPEC §5 now states the payload framing of the AV1, FLAC and Ogg Opus
capabilities, which only packaging §7 described for AV1.

Defects found and fixed on the way:
- flac_decode reserved STREAMINFO's declared total: 2^36 - 1 samples asked the
  allocator for 256 GiB before a sample was decoded.
- A 24-bit FLAC decoded to about ±256: the scale assumed 16 bits.
- opus_decode refused a stream whose source rate was not an Opus rate (44.1 kHz);
  RFC 7845 makes that field informational. Its output had no bound at all.
- png_decode and jpeg_decode guessed the format, so a PNG payload holding WebP decoded.
- jxl_decode refused deeper than 8 bits and returned 1 or 4 channels for grey or alpha.
- av1_decode decoded every packet and only then truncated to n; panicked on monochrome
  (empty chroma planes); misread 4:2:2, 4:4:4 and high bit depth; and let dav1d
  allocate whatever a sequence header declared. dav1d is now held to the header's
  h × w, a frame wider or taller than declared is refused, and av1_decode_each streams
  one frame at a time; the whole-output entry points refuse past 4 GiB from the header.

8-bit 4:2:0 frames decode byte for byte as before. AVIF decodes through libavif
directly with the reader limits as its decoder limits (libavif-image and the rav1e
build inside libavif are gone from the lockfile). include/wai.h is regenerated, which
also brings in the `# Safety` lines #241 added to the sources.

tests/media_decode_bounds.rs (14 tests, run by codec-wrappers) checks every limit at
its edge and one past it, on real payloads and on headers rewritten to declare more
than they hold. Falsified: the unbounded FLAC reserve, the 4:2:0-only conversion,
format guessing, an off-by-one pixel limit and the missing dav1d limit each fail it.
Green on the build host: those 14, fuzz_smoke, ffi_robustness, the text bounds and
memory tests, corpus_provenance, video_receipt, and the 193 lib tests with codecs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai: coverage-guided fuzzing of the reader, and the container reservations it found
Some checks failed
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
tests in CI / every test file is run by a job or listed with its reason (pull_request) Has been cancelled
standards workspaces build + test / jouleclaw-rs (pull_request) Has been cancelled
standards workspaces build + test / aac-rs (pull_request) Has been cancelled
standards workspaces build + test / arl-rs (pull_request) Has been cancelled
standards workspaces build + test / eoc-rs (pull_request) Has been cancelled
standards workspaces build + test / jcp-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-ui-rs (pull_request) Has been cancelled
standards workspaces build + test / joulecontract-rs (pull_request) Has been cancelled
standards workspaces build + test / jouledesk-rs (pull_request) Has been cancelled
standards workspaces build + test / joulehook-rs (pull_request) Has been cancelled
standards workspaces build + test / map-rs (pull_request) Has been cancelled
standards workspaces build + test / mesh-rs (pull_request) Has been cancelled
standards workspaces build + test / openpay (pull_request) Has been cancelled
standards workspaces build + test / proof-rs (pull_request) Has been cancelled
standards workspaces build + test / sandbox-rs (pull_request) Has been cancelled
standards workspaces build + test / sense-rs (pull_request) Has been cancelled
standards workspaces build + test / smart-byte-rs (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
spec citations resolve / Code cites no section its SPEC lacks (pull_request) Has been cancelled
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no comparison (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no company or product (pull_request) Has been cancelled
no-vanity-metrics / No new orphan source files (pull_request) Has been cancelled
lockfiles / every Cargo.lock is current (pull_request) Has been cancelled
3a6963ea0d
SECURITY.md's remaining known gap: fuzzing was a seeded-random smoke test, with no
coverage guidance for the manifest parser, the integer payload parsers or the codec
wrappers. wai-rs/fuzz/ adds four libFuzzer targets (cargo-fuzz, AddressSanitizer):

- envelope: version detection, the WAI1 and WAI2 readers, the listing form;
- int_payload: int_payload::decode_int against each (capability, parameter set) pair
  the integer conformance corpus commits, the first byte choosing the pair;
- media: the picture, audio, video and text decoders of the floor and the recommended
  set, the first byte choosing the decoder;
- containers: the extension containers (world, causal op, film, reel, score, haptic,
  splat4d, audio scene).

`seeds` builds the corpora from the repository: every committed envelope, integer
payload and container, and a valid payload per decoder from the reference writers.
`run.sh` runs each target for a given time, tells a find (exit 1, input kept in
fuzz/artifacts) from a tool failure (exit 2), and builds with --no-cfg-fuzzing because
rav1e depends on libfuzzer-sys under cfg(fuzzing), which left wai's cdylib with an
undefined symbol. wai-fuzz.yml runs each target for 15 minutes nightly on the macOS
runner (which has the codec libraries), never per pull request, keeping the corpus on
the CI volume between nights. wai-rs's one multi-line inline table (c2pa) is put on one
line: cargo-fuzz's TOML parser rejects it.

Found in the containers target's first seconds: the audio scene reader reserved a
u32 count of entries read from the input before reading one, so a few bytes asked for
48 GiB. The world, reel, score, haptic and splat4d readers did the same. Each now
reserves no more than its remaining bytes could hold. tests/container_reservations.rs
rewrites a committed vector of each to declare 2^32 - 1 entries in four bytes and
requires a refusal with no single allocation past a few megabytes, through an allocator
that records the largest request and refuses past 256 MiB; on the old readers the world
asset table alone asked for 256 GiB (falsified). wai-conformance runs it on every leg.

SECURITY.md now says what is fuzzed, and keeps two gaps: the C libraries are not
instrumented, and the extension formats' declared durations have no reader limit yet
(rendering allocates in proportion; reading is bounded).

Smoke runs on the build host: envelope, int_payload and media 120 s each, nothing
found; containers found the above, then ran 300 s clean once fixed. Green there: the new
test, and the 275 lib tests with the container features. Clean: tests-in-CI, spec and
section refs, workflow concurrency, lockfiles, vanity metrics, comparative prose,
public names.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
dcharlot closed this pull request 2026-10-15 01:04:51 -04:00
Some checks failed
workflow hygiene / every workflow supersedes its own queue (pull_request) Has been cancelled
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been cancelled
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Has been cancelled
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Has been cancelled
WAI section references / cited sections exist (pull_request) Has been cancelled
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Has been cancelled
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Has been cancelled
wai-pack / wai-pack · does this change touch it (pull_request) Has been cancelled
wai-pack / wai-pack · linux (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Has been cancelled
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Has been cancelled
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Has been cancelled
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been cancelled
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
tests in CI / every test file is run by a job or listed with its reason (pull_request) Has been cancelled
standards workspaces build + test / jouleclaw-rs (pull_request) Has been cancelled
standards workspaces build + test / aac-rs (pull_request) Has been cancelled
standards workspaces build + test / arl-rs (pull_request) Has been cancelled
standards workspaces build + test / eoc-rs (pull_request) Has been cancelled
standards workspaces build + test / jcp-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-ui-rs (pull_request) Has been cancelled
standards workspaces build + test / joulecontract-rs (pull_request) Has been cancelled
standards workspaces build + test / jouledesk-rs (pull_request) Has been cancelled
standards workspaces build + test / joulehook-rs (pull_request) Has been cancelled
standards workspaces build + test / map-rs (pull_request) Has been cancelled
standards workspaces build + test / mesh-rs (pull_request) Has been cancelled
standards workspaces build + test / openpay (pull_request) Has been cancelled
standards workspaces build + test / proof-rs (pull_request) Has been cancelled
standards workspaces build + test / sandbox-rs (pull_request) Has been cancelled
standards workspaces build + test / sense-rs (pull_request) Has been cancelled
standards workspaces build + test / smart-byte-rs (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
spec citations resolve / Code cites no section its SPEC lacks (pull_request) Has been cancelled
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no comparison (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no company or product (pull_request) Has been cancelled
no-vanity-metrics / No new orphan source files (pull_request) Has been cancelled
lockfiles / every Cargo.lock is current (pull_request) Has been cancelled

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!256
No description provided.