fix(moq-net): enforce MAX_FRAME_SIZE at the frame allocation chokepoint#1881
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make the frame producer construction fallible so
MAX_FRAME_SIZEis enforced at the allocation chokepoint, not just on the wire-decode paths.FrameProducer::newis the only place that allocates the declared frame size (vec![0u8; size]). It now checkssize > MAX_FRAME_SIZEand returnsError::FrameTooLargebefore 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_framebecomesinfo.into().produce()?; its doc notes theFrameTooLargepath.MAX_FRAME_SIZEdoc updated; the old TODO pointing at this follow-up is gone.Notes / deviations from the deferring PR
This was deferred from #1870, which was based on
main.devhas since moved, so two things differ from that PR's framing:Not a public API break on
dev.Frame::produceandFrameProducer::neware alreadypub(crate)here (from the per-frame timestamp work), andFrom<Frame> for FrameProduceris already gone.create_frame/append_framekeep their existingResultsignatures, so this is an internal change that only tightenscreate_frame's behavior (oversized now returns the existingFrameTooLargevariant). There were also nocreate_frame/append_framesize checks to remove (those aremain/moq-gst: replace moqsink with a GstAggregator-based core #1870-only). Targetingdevanyway, per the original instruction and because it builds on the dev-only frame/compression code.The lite compression path keeps its bound. In
lite/subscriber.rsthe compression branch readssizeas the compressed wire length and feeds it toread_exact(size), which never reachesproduce(). Removing that check outright would reintroduce an unbounded read, so it was moved into the compression branch; the uncompressed path now relies on theproduce()chokepoint. The IETF path has no compression branch, so its check was purely redundant and was removed (along with its now-unusedMAX_FRAME_SIZEimport).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. newrejects_oversized_frame/create_frame_rejects_oversizedcargo test -p moq-native -p hang -p moq-mux -p moq-relay— all passCross-package sync
No
js/netordoc/conceptchanges: the wire format and public API are unchanged. This is a receiver-side allocation guard only.(Written by Claude)