Skip to content

Harden binary subtitle parsers against malformed files (crashes, OOM, infinite loops)#12581

Open
ivandrofly wants to merge 13 commits into
SubtitleEdit:mainfrom
ivandrofly:fix/binary-parser-crash-guards
Open

Harden binary subtitle parsers against malformed files (crashes, OOM, infinite loops)#12581
ivandrofly wants to merge 13 commits into
SubtitleEdit:mainfrom
ivandrofly:fix/binary-parser-crash-guards

Conversation

@ivandrofly

Copy link
Copy Markdown
Member

Adds missing bounds/sanity guards across the binary parsers so malformed or truncated files degrade gracefully instead of crashing the import or hanging the app. Thirteen fixes, one commit each:

  • BluRaySup: RLE image decode clamped to the declared width*height pixel span — a malformed ODS with long runs wrote past the buffer, and the exception escaped the per-segment try/catch via the post-parse merge path, aborting the file open.
  • VobSub: SetColor/SetContrast (commands 0x03/0x04) read two argument bytes with no bounds check, unlike the guarded sibling commands.
  • CEA-608 SEI parsing: bounded the payload-type loop, the 8-byte cc-data header probe, and the cc-triple loop against the buffer length (reachable from the MP4Parser constructor with no try/catch); GetSeiData also underflowed endPos - startPos in ulong arithmetic for NAL sizes 0/1, attempting a huge allocation.
  • CEA-608 layout: CcRow.Position now clamps to the 32-column range in its setter (a malformed PAC indent could index Chars[-1]/Chars[32]), MoveCursor/InsertChar clamp locally, and RollUp guards a possibly-negative RemoveAt index.
  • CEA-708: the six one-byte-argument window/Delay commands (0x88–0x8D) now check remaining length, mirroring the guarded SetPen*/DefineWindow commands.
  • MP4: stco/co64/stts loops bound file-declared entry counts against the actual buffer (mirroring stsz/ctts), and the stts run-length expansion is capped so a sampleCount of 0xFFFFFFFF can no longer allocate toward 4 billion entries; Box size-0 ("extends to EOF") Position math is corrected and forward progress enforced so a malformed size can't loop forever; the CMAF parser bails on a zero time scale instead of dividing by zero.
  • Transport stream: DVB subtitle handling accessed PageCompositions[0] before its own Count > 0 check; the PES constructor now validates the buffer is long enough for the fixed header and the private-stream-1 id byte (a declared packet length of 0 produced a too-short buffer).

Build succeeds; all 499 libse tests pass.

🤖 Generated with Claude Code

ivandrofly and others added 13 commits July 17, 2026 18:21
A malformed ODS (e.g. declaring a 1x1 object but encoding long RLE
runs) made the running pixel offset exceed the destination span,
throwing IndexOutOfRangeException that escaped the decode try/catch.
Clamp FillPixels/PutPixelFast writes to the span bounds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A truncated control sequence could end right after the SetColor (3) or
SetContrast (4) command byte, so reading _data[commandIndex+1/+2] threw
IndexOutOfRangeException. Guard both with the same length check the
SetDisplayArea (5) case already uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Truncated SEI payloads let ParseCcDataFromSei run past the buffer: an
all-0xFF payloadType run, the 8-byte CC-data header probe, and the
cc-triple loop all indexed without bounds checks, throwing
IndexOutOfRangeException. Bound the payloadType loop and header read
and clamp the triple loop to the buffer length.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When a NAL declares nalSize 0 or 1 it still passes the nalSize<10000
check, but endPos-startPos (= nalSize-2) underflows the ulong buffer
length into a near-4GB allocation. Bail out when endPos <= startPos.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stco, co64 and stts trusted file-declared entry counts and read past
the box buffer, and stts additionally expanded a per-entry run length
(sampleCount up to 0xFFFFFFFF) into an unbounded list. Add per-iteration
bounds checks mirroring stsz/ctts/stsc and cap the stts expansion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A backspace at column 0 indexed Chars[-1] and a 33rd printable char
indexed Chars[32], throwing IndexOutOfRangeException on malformed
caption data. Clamp the cursor position to the valid column range in
MoveCursor and InsertChar and skip out-of-range pen-state writes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A roll-up count larger than the current row (e.g. from malformed
CEA-608 data) made RemoveAt use a negative index, throwing
ArgumentOutOfRangeException. Bail out when the computed index is
outside the rows list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ClearWindows/DisplayWindows/HideWindows/ToggleWindows/DeleteWindows and
Delay (0x88-0x8D) read the argument byte at bytes[i+1] without checking
the buffer length, so a command byte at the end of a truncated service
block threw IndexOutOfRangeException. Add the same length guard the
SetPen*/DefineWindow commands already use.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GetDvbSup indexed PageCompositions[0] before the Count>0 check on the
next line, so a segment with no page composition threw
IndexOutOfRangeException. Move the access inside the existing guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The PES constructor read the fixed header bytes (buffer[index+6..8])
and the private-stream id at buffer[index+9+HeaderDataLength] without a
length check, so a pack with a zero-length PES packet threw
IndexOutOfRangeException. Bail out when the buffer is too short and
guard the private-stream id read.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A size-0 box (extends to EOF) set Position to fs.Length-8 instead of
fs.Length because the remaining-bytes size did not include the header
the Position formula subtracts, so the box-scan loops re-read the last
8 bytes forever. Add the header offset back and clamp Position so a
malformed size can never point before the current position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When a CMAF file provides no positive time scale in either the track
Mdhd or the Mvhd box, timeScale stayed 0 and the per-sample time
conversion divided by zero, throwing DivideByZeroException. Bail out
before the conversion loop when no usable time scale is present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A malformed PAC indent assigned directly to CcRow.Position
(CaptionScreen sets row.Position = pacData.Indent.Value) was never
range-checked, so SetPenStyles and the ClearFromPos loops could index
the 32-entry Chars array out of bounds. Clamp the value in the Position
setter so every reader stays within the valid column range.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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