Repository navigation
fix(moq-mux): walk an H.265 SPS before scuffle_h265 parses it - #7685
Conversation
Quest-Claim: a20d069241c74476
scuffle-h265 0.2.2 subtracts and indexes with SPS fields unchecked, and scuffle-expgolomb 0.1.5 wraps an Exp-Golomb code of 64 or more leading zeros, so a hostile SPS panicked debug builds and fed wrapped values (or a multi-gigabyte palette allocation) to release builds. Every scuffle_h265 SPS parse in moq-mux now goes through `sps::parse_full`, which first walks the whole SPS in scuffle_h265's layout with h264_reader's bounded reader and refuses the values that reach those overflows. An SPS the walk refuses falls back to its head, as a scuffle refusal already did. Commits the fuzzers' SPS inputs as regressions for the annexb, fmp4 and ts targets, and closes quest/m1/fuzz-findings/sps-overflow.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 15 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. CodeRabbit Verify
|
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 18ed9db
The bounded pre-walk is a reasonable mitigation for the dependency’s known overflow paths, though maintaining a second parser tied to its exact layout carries ongoing risk. Removing the duplicate once the dependency is fixed would be simpler. No actionable bugs found in this static review. I did not run tests or fuzz the parser.
There was a problem hiding this comment.
ℹ️ Minor suggestion inline. The guard itself checks out.
Reviewed changes
codec/h265/sps.rs— newcheck/walk: a bit-exact walk of the SPS in scuffle-h265 0.2.2's layout (including its two layout quirks) that refuses the values scuffle would overflow on —MinTbLog2SizeY > CtbLog2SizeY, a scaling-list reference before matrix 0, anyue(v)of 32+ leading zeros, andpalette_max_size > 64/ oversized predictor initializers — beforeSpsNALUnit::parseruns.Sps::parsefallback: a walk refusal (InvalidData) falls back to the head-only parse, same as a scuffle refusal; a truncated SPS (UnexpectedEof) stays an error.- Callers:
config_from_sps/Sps::parse,sps_reorder,sps_hrd, and the MPEG-TS HEVC timing path all route throughparse_full. - Tests:
check_reads_what_scuffle_readsasserts the walk reaches the same stop bit scuffle does across the fixtures, scuffle's own test SPSes, and a synthetic all-branches SPS;overflowing_fields_are_refusedcovers each overflow. - Regressions / quest: 5 fuzz inputs committed;
sps-overflowclosed andsps-fuzz-h264-readerupdated.
I compared every walk branch against scuffle-h265 0.2.2's SpsRbsp::parse (and the sub-modules) field by field. The bit consumption matches on every branch — including the general-profile 96-bit skip (additional flags are always 43 bits), the twice-read sub_layer_level_idc, the HRD fixed_pic_rate_within_cvs_flag default, and the short-term-RPS derivation order — so no SPS that scuffle would overflow on can pass check. h264_reader's read_ue cap (31 leading zeros, max 2^32-2) keeps every downstream u64 sum in range. No false positive on a spec-valid stream found.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the delta since the prior pullfrog review at 18ed9db; nothing behavioral changed.
- Scoped
parse_full's doc guarantee:codec/h265/sps.rsnow states that every scuffle_h265 SPS parse in moq-mux goes throughparse_full, applying the prior review's suggestion and leaving themoq-videofollow-up tracked separately. - Quest bookkeeping: the fuzz-findings README gained a
## Requiredheading and dropped the resolved TS step-scan bullet.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the delta from 1846351 to 5ca8238; the Rust code is unchanged.
- Quest cleanup:
quest/m1/fuzz-findings/sps-overflow.mdis deleted on completion, its bullet is removed from the README's## Requiredlist, and the mp4-atom entries now cite upstream kixelated/mp4-atom#267 for thesgpd/iref/ilocregressions. - Second
origin/mainmerge: brings the branch to151ed3820d81with nors/diff change, so the reviewed guard is exactly what was approved previously.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
Resolve lite.md to main's SUBSCRIBE_STARTED decision (#7675) and keep the prefix-set Related link in the routes epic README (#7676). Per the PR's Merge order table, drop the edges to sps-overflow.md (#7685 merged) and repoint routes.md links to routes/README.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Closes quest
quest/m1/fuzz-findings/sps-overflow.md: a hostile H.265 SPS reaching moq-mux (fMP4hvcC, Annex-B, or MPEG-TS) is now refused before scuffle-h265 can overflow on it. The fuzzers' SPS inputs are committed as regressions for theannexb,fmp4, andtstargets.Is it still needed on today's main?
Yes, for H.265. Every reproducer below panicked on
main(3d927ca) before this change, except the original exp-golomb input (see the Annex-B entries). On today's main that input stops early at h264-reader'ssps_idread. A version with valid emulation prevention still reached scuffle-expgolomb's wrap.For H.264, moq-dev/moq#7398 moved SPS parsing to h264-reader 0.9. Its
read_uestops at 2^32 - 2, andpixel_dimensionsuseschecked_add/checked_mul/checked_subfor the width, height, and crop math. The old h264-parser reproducer passes, and is committed asfmp4/h264-sps-width. That check was done by reading the code. The longer fuzz rerun stays withquest/m2/sps-fuzz-h264-reader.md, whose text this PR updates.Changes
codec/h265/sps.rs: newparse_full(nal)runscheckfirst, then callsSpsNALUnit::parse.checkwalks the whole SPS with h264-reader's boundedBitReader, following scuffle-h265 0.2.2's layout, and refuses:MinTbLog2SizeY > CtbLog2SizeY. scuffle subtracts these unchecked for the transform-depthrange_check!(sps/mod.rs:303,305).scaling_list_pred_matrix_id_deltathat reaches before matrix 0. scuffle'smatrix_id - deltawraps atscaling_list.rs:90, then indexes out of bounds at:92, which panics in release builds too.ue(v)of 32 or more leading zeros, which ITU-T H.265 9.2 forbids. This covers scuffle-expgolomb'sresult - 1wrap at 64+ zeros, and the+ 3/+/*sums nearu64::MAX(CB size, PCM size, VUI display-window sums).palette_max_size > 64(7.4.3.3.8). scuffle allocatespalette_max_size-sized predictor initializers, and one ~2^32 entries long aborts the process.Callers:
Sps::parse,sps_reorder,sps_hrd, and the TS HEVC timing path (container/ts/video_timing.rs) all go throughparse_full.Sps::parsetreats a walk refusal (InvalidData) like a scuffle refusal: it keeps the SPS head and drops the VUI. A truncated SPS (UnexpectedEof) is still an error.Tests:
check_reads_what_scuffle_readsruns every moq-mux H.265 SPS fixture, two of scuffle's own test SPSes (scaling lists; 26 inter-predicted RPSs), and a synthetic SPS. The synthetic one enables PCM, long-term refs, a VUI with sub-picture NAL+VCL HRD, and all four extensions. For each SPS the test asserts scuffle accepts it and the walk ends exactly at its stop bit.overflowing_fields_are_refusedcovers each overflow with a synthetic SPS. Withcheckdisabled, the first three panic at the reported scuffle lines, and the palette case aborts on allocation.Regressions (
rs/moq-mux/fuzz/regressions/):annexb/scuffle-h265-transform-depth: feat(fuzz): fuzz the media parsers untrusted publishers reach #7313's input.annexb/scuffle-expgolomb: feat(fuzz): fuzz the media parsers untrusted publishers reach #7313's input. It passes on main already, because h264-reader refuses its raw00 00 00runs first.annexb/hevc-sps-exp-golomb-64-zeros: a version with valid emulation prevention that does reach scuffle-expgolomb's wrap.ts/hevc-sps-transform-depth:poc-hevc.tswith its SPS overwritten in place by the transform-depth SPS.fmp4/h264-sps-width: feat(fuzz): fuzz the media parsers untrusted publishers reach #7313's h264-parser input.Each H.265 input panics with
checkdisabled and passes with it.Choices made without asking
sub_layer_level_idctwice when a sub-layer signals both profile and level, and readsiv_di_mc_enabled_flag/iv_mv_scal_enabled_flagonce in the 3D extension. A walk on the spec layout would fall out of step with scuffle, and an input built on that gap could get past the checks.SpsHead's own reader keeps the spec layout.skip_sub_layersgained alevel_twiceflag for the two cases.palette_max_size≤ 64). Everything else is left to scuffle's own checks, so a valid SPS is never refused by the walk.00 00 00/01/02) is now refused by the walk, so it keeps its head without the VUI. Before, scuffle passed those bytes through. Annex-B splitting never yields one, and insidehvcCone is malformed.Verification
cargo fmt --check,cargo clippy -p moq-mux --all-targets --all-features -D warnings,cargo doc -p moq-mux -D warnings,cargo test -p moq-mux --all-features: all pass. On the final head5ca8238eee, clippy andcargo test -p moq-mux --all-featurespass again (2005 lib tests);fuzz::tests::regressionsreplays the committed inputs.SpsNALUnit::parse, andcheckrefused every one. Runningparse_fullandSps::parseover 404k mutations panicked nowhere.quest check --base origin/mainpasses.Follow-ups
decode/vui.rsstill callsscuffle_h265::SpsNALUnit::parsedirectly, so the same hostile SPS can panic a subscriber's decoder. Sharing this walk with moq-video is being planned as a separate m0 quest, so it stays out of this PR.Review
Codex (
quest review), gpt-6.1-sol, medium effort, round 1 of a new session on head1846351d6831e13f585e370647690bf151079fcfagainstorigin/main(151ed3820d81): no findings.Iteration on 2026-11-03:
origin/maintwice (the second time up to151ed3820d81, giving head5ca8238eee6). The only conflict both times was thefuzz-findingsREADME'sRequiredlist, which quest's merge driver resolved.git diff origin/main...HEAD -- rsis identical to the reviewed head's diff, so the review above still covers the code.parse_full's doc comment to moq-mux.Checkjob on18ed9dbc9fwas a network flake: the ffmpeg tarball download forjs/libavgot connection resets duringcheck biome. This PR changes nothing there.🤖 Generated with Claude Code
(Written by Claude Opus 5.5)