Skip to content

fix(moq-net): enforce MAX_FRAME_SIZE at the frame allocation chokepoint#1881

Merged
kixelated merged 1 commit into
devfrom
claude/unruffled-hamilton-4b338a
Jun 22, 2026
Merged

fix(moq-net): enforce MAX_FRAME_SIZE at the frame allocation chokepoint#1881
kixelated merged 1 commit into
devfrom
claude/unruffled-hamilton-4b338a

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

Make the frame producer construction fallible so MAX_FRAME_SIZE is enforced at the allocation chokepoint, not just on the wire-decode paths.

FrameProducer::new is the only place that allocates the declared frame size (vec![0u8; size]). It now checks size > MAX_FRAME_SIZE and returns Error::FrameTooLarge before allocating, making it the single chokepoint. A direct caller can no longer allocate an attacker-controlled size before any check.

  • FrameProducer::new(info) -> Result<Self> (was -> Self): the size guard lives here, before the allocation.
  • Frame::produce(self) -> Result<FrameProducer> (was -> FrameProducer).
  • GroupProducer::create_frame becomes info.into().produce()?; its doc notes the FrameTooLarge path.
  • IETF subscriber pre-check removed (now redundant); lite subscriber's check relocated into the compression branch (see below).
  • MAX_FRAME_SIZE doc updated; the old TODO pointing at this follow-up is gone.
  • Added a reject test at both the frame and group layers.

Notes / deviations from the deferring PR

This was deferred from #1870, which was based on main. dev has since moved, so two things differ from that PR's framing:

  1. Not a public API break on dev. Frame::produce and FrameProducer::new are already pub(crate) here (from the per-frame timestamp work), and From<Frame> for FrameProducer is already gone. create_frame / append_frame keep their existing Result signatures, so this is an internal change that only tightens create_frame's behavior (oversized now returns the existing FrameTooLarge variant). There were also no create_frame / append_frame size checks to remove (those are main/moq-gst: replace moqsink with a GstAggregator-based core #1870-only). Targeting dev anyway, per the original instruction and because it builds on the dev-only frame/compression code.

  2. The lite compression path keeps its bound. In lite/subscriber.rs the compression branch reads size as the compressed wire length and feeds it to read_exact(size), which never reaches produce(). Removing that check outright would reintroduce an unbounded read, so it was moved into the compression branch; the uncompressed path now relies on the produce() chokepoint. The IETF path has no compression branch, so its check was purely redundant and was removed (along with its now-unused MAX_FRAME_SIZE import).

Test plan

Run via nix develop (pinned toolchain):

  • just rs check (cargo check, clippy -D warnings, fmt --check, doc -D warnings, shear, sort)
  • cargo test -p moq-net — 398 unit + doctests pass, incl. new rejects_oversized_frame / create_frame_rejects_oversized
  • cargo test -p moq-native -p hang -p moq-mux -p moq-relay — all pass

Cross-package sync

No js/net or doc/concept changes: the wire format and public API are unchanged. This is a receiver-side allocation guard only.

(Written by Claude)

FrameProducer::new is the only place that allocates the declared frame size (vec![0u8; size]), so make it fallible and reject size > MAX_FRAME_SIZE with Error::FrameTooLarge before allocating. Frame::produce and GroupProducer::create_frame thread the Result through, guaranteeing the limit for every frame rather than just the wire-decode paths.

The IETF subscriber pre-check is now redundant (it funnels through create_frame) and is removed. The lite subscriber keeps a bound in the compression branch, where size is the compressed wire length that never reaches the chokepoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated kixelated merged commit b57f22b into dev Jun 22, 2026
1 check passed
@kixelated kixelated deleted the claude/unruffled-hamilton-4b338a branch June 22, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant