wai: reader limits for picture, audio and video payloads #255

Closed
dcharlot wants to merge 0 commits from wai/decode-output-limits into main
Owner

SECURITY.md listed this gap first: the image, audio and AV1 decoders allocated whatever a payload declared, up to their libraries' own limits. SPEC §2 now gives them reader limits, the same way it already bounds manifests and text payloads.

The limits (SPEC §2)

A reader must accept:

payload up to past it, it may refuse with
picture: wai.image.*, or one frame of wai.video.av1* 65 536 pixels on each side, and 2²⁷ pixels in all image_too_large
wai.audio.flac, wai.audio.opus 2²⁷ samples, counting every channel audio_decoded_too_long
wai.video.av1* 2²⁰ temporal units video_too_large

A reader refuses from what the payload declares, before it decodes:

  • Pictures: PNG IHDR, the JPEG frame header, the AVIF ispe of each image item, the JPEG XL size header, or the AV1 framing header.
  • FLAC: STREAMINFO.
  • Audio while decoding: the decoder counts its output and refuses once the limit is passed; it never sizes its buffer past the limit.

The reference writers refuse anything past these limits.

Payload framing (SPEC §5)

SPEC itself now states how the AV1 payload is framed: n, h, w, fps_num, fps_den, then length-prefixed temporal units in the low-overhead format. Until now only packaging §7 described this. SPEC §5 also states:

  • the FLAC and Ogg Opus forms;
  • that an image payload decodes only as its own format.

Defects fixed in the reference decoders

Audio

  • flac_decode reserved memory for STREAMINFO's declared total. A stream declaring 2³⁶ − 1 samples asked the allocator for 256 GiB before decoding anything.
  • 24-bit FLAC decoded to about ±256 instead of ±1.
  • opus_decode refused 44.1 kHz-source streams. RFC 7845 makes that field informational.
  • opus_decode had no bound on its output.

Images

  • png_decode and jpeg_decode guessed the format from the bytes.
  • jxl_decode refused pictures deeper than 8 bits.
  • jxl_decode returned 1 or 4 channels where it promises 3.

AV1

  • av1_decode decoded every packet, then truncated to n.
  • It panicked on monochrome frames.
  • It misread 4:2:2, 4:4:4 and 10- and 12-bit frames.
  • It let dav1d allocate whatever size a sequence header declared. dav1d is now held to the framing header's h × w.
  • Streaming: av1_decode_each hands over one frame at a time.
  • The whole-output entry points (av1_decode, wai_video_av1_decode) refuse past 4 GiB, checked from the header.
  • wai decode streams frames to its output file.

What stays the same

  • 8-bit 4:2:0 output: decodes byte for byte as before; the conversion path for it is untouched.
  • AVIF: now decodes through libavif directly, with the reader limits as libavif's own decoder limits. libavif-image and libavif's internal rav1e build drop out of the lockfile.
  • include/wai.h: regenerated. This also picks up the # Safety lines #241 added to the sources, which main's committed header was missing.

Tests

tests/media_decode_bounds.rs has 14 tests, run in the codec-wrappers job:

  • each limit at its edge and one past it, on real payloads and on headers rewritten to declare more than they hold;
  • each defect listed above.

Five mutations each reintroduce one defect, and each one fails the suite:

  • the unbounded FLAC reserve;
  • the 4:2:0-only conversion;
  • format guessing;
  • an off-by-one pixel limit;
  • no dav1d frame-size limit.

Green on the build host:

  • those 14 tests;
  • fuzz_smoke, ffi_robustness, text_decode_bounds, text_decode_memory, corpus_provenance and video_receipt;
  • the 193 lib tests with codecs.

Clean: the section-refs, spec-refs, tests-in-CI, lockfiles, workflow-concurrency, vanity-metrics, comparative-prose and public-names checks.

🤖 Generated with Claude Code

SECURITY.md listed this gap first: the image, audio and AV1 decoders allocated whatever a payload declared, up to their libraries' own limits. SPEC §2 now gives them reader limits, the same way it already bounds manifests and text payloads. ## The limits (SPEC §2) A reader must accept: | payload | up to | past it, it may refuse with | |---|---|---| | picture: `wai.image.*`, or one frame of `wai.video.av1*` | 65 536 pixels on each side, and 2²⁷ pixels in all | `image_too_large` | | `wai.audio.flac`, `wai.audio.opus` | 2²⁷ samples, counting every channel | `audio_decoded_too_long` | | `wai.video.av1*` | 2²⁰ temporal units | `video_too_large` | A reader refuses from what the payload declares, before it decodes: - **Pictures:** PNG `IHDR`, the JPEG frame header, the AVIF `ispe` of each image item, the JPEG XL size header, or the AV1 framing header. - **FLAC:** `STREAMINFO`. - **Audio while decoding:** the decoder counts its output and refuses once the limit is passed; it never sizes its buffer past the limit. The reference writers refuse anything past these limits. ## Payload framing (SPEC §5) SPEC itself now states how the AV1 payload is framed: `n`, `h`, `w`, `fps_num`, `fps_den`, then length-prefixed temporal units in the low-overhead format. Until now only packaging §7 described this. SPEC §5 also states: - the FLAC and Ogg Opus forms; - that an image payload decodes only as its own format. ## Defects fixed in the reference decoders **Audio** - `flac_decode` reserved memory for STREAMINFO's declared total. A stream declaring 2³⁶ − 1 samples asked the allocator for 256 GiB before decoding anything. - 24-bit FLAC decoded to about ±256 instead of ±1. - `opus_decode` refused 44.1 kHz-source streams. RFC 7845 makes that field informational. - `opus_decode` had no bound on its output. **Images** - `png_decode` and `jpeg_decode` guessed the format from the bytes. - `jxl_decode` refused pictures deeper than 8 bits. - `jxl_decode` returned 1 or 4 channels where it promises 3. **AV1** - `av1_decode` decoded every packet, then truncated to `n`. - It panicked on monochrome frames. - It misread 4:2:2, 4:4:4 and 10- and 12-bit frames. - It let dav1d allocate whatever size a sequence header declared. dav1d is now held to the framing header's `h × w`. - Streaming: `av1_decode_each` hands over one frame at a time. - The whole-output entry points (`av1_decode`, `wai_video_av1_decode`) refuse past 4 GiB, checked from the header. - `wai decode` streams frames to its output file. ## What stays the same - **8-bit 4:2:0 output:** decodes byte for byte as before; the conversion path for it is untouched. - **AVIF:** now decodes through libavif directly, with the reader limits as libavif's own decoder limits. `libavif-image` and libavif's internal rav1e build drop out of the lockfile. - **`include/wai.h`:** regenerated. This also picks up the `# Safety` lines #241 added to the sources, which main's committed header was missing. ## Tests `tests/media_decode_bounds.rs` has 14 tests, run in the `codec-wrappers` job: - each limit at its edge and one past it, on real payloads and on headers rewritten to declare more than they hold; - each defect listed above. Five mutations each reintroduce one defect, and each one fails the suite: - the unbounded FLAC reserve; - the 4:2:0-only conversion; - format guessing; - an off-by-one pixel limit; - no dav1d frame-size limit. Green on the build host: - those 14 tests; - `fuzz_smoke`, `ffi_robustness`, `text_decode_bounds`, `text_decode_memory`, `corpus_provenance` and `video_receipt`; - the 193 lib tests with `codecs`. Clean: the section-refs, spec-refs, tests-in-CI, lockfiles, workflow-concurrency, vanity-metrics, comparative-prose and public-names checks. 🤖 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]>
dcharlot closed this pull request 2026-10-15 01:04:49 -04:00
Some checks failed
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Successful in 11m4s
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 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!255
No description provided.