[cocoa] AVStreamDataParser accepts media segments not preceded by an init segment
Component: WebCore Media Source Extensions | 4ed0007
LayoutTests/media/media-source/media-source-append-media-before-init-expected.txt
LayoutTests/media/media-source/media-source-append-media-before-init.html
WebKit's Media Source Extensions implementation on Cocoa delegates ISO-BMFF parsing to AVStreamDataParser, an AVFoundation private API. The MSE spec's Segment Parser Loop mandates strict ordering — an initialization segment (ftyp+moov) must precede any media segment (moof+mdat) — but AVStreamDataParser does not enforce it, happily accepting media data before any init segment and producing CMSampleBuffers with null CMFormatDescription that fail downstream during decode. Mid-stream format changes, a new ftyp arriving without a preceding abort() or changeType(), trigger an internal CoreMedia -16046 error from MoofManifold that is swallowed silently, leaving the parser in a bad state. This commit inserts a new ISOBMFFPreParser between SourceBufferParserAVFObjC and AVStreamDataParser: it walks ISO-BMFF box headers across appendData() call boundaries — without parsing box contents — to reject media segments appended before any init segment and to inject an AVStreamDataParserStreamDataDiscontinuity signal when a new ftyp appears mid-stream. The AppendFlags::Discontinuity plumbing in SourceBufferParserAVFObjC already existed but had never been triggered on the mid-stream re-init path. The pre-parser uses BitReader rather than the existing ISOBox::peekBox, because that path requires JSC::DataView and routes through Gigacage while SharedBuffer contents are not Gigacage-allocated — the combination faults with EXC_BAD_ACCESS.
Before:
appendData(attacker data)
│
▼
SourceBufferParserAVFObjC
│ (no ordering check)
▼
AVStreamDataParser ──► CMSampleBuffer (null CMFormatDescription → decode failure)
After:
appendData(attacker data)
│
▼
SourceBufferParserAVFObjC
│
▼
ISOBMFFPreParser (stateful box-header scanner)
│
├─[media seg, no prior init]──► parsing error → SourceBuffer error event
│
├─[new ftyp mid-stream]──► SPLIT append at ftyp offset
│ ├─► data[0..ftyp_offset) ──► AVStreamDataParser (original flags)
│ └─► data[ftyp_offset..) ──► AVStreamDataParser (Discontinuity flag)
│
└─[normal]──────────────────────► AVStreamDataParser ──► valid CMSampleBuffers
Significance
A new stateful binary parser now sits in the MSE ingestion path on Apple platforms, processing attacker-supplied data before it reaches AVFoundation. The previously dead AppendFlags::Discontinuity code path is also activated for the first time outside abort()/changeType(). The user-visible motivation was player stalls on Twitch's ad-to-content transitions.
Audit directions
Five surfaces, all on attacker-supplied bytes. Box size field decoding: the pre-parser handles all three ISO-BMFF size encodings — standard 32-bit, 64-bit extended (size field == 1, followed by an 8-byte length), and open-ended (size == 0, "rest of stream"). Integer overflow computing the next-box offset from a 64-bit attacker-controlled size is the classic attack; the 32→64-bit promotion path and the size==0 sentinel deserve the closest look, and the same triad applies to every other box-walking parser in the tree.
Cross-boundary partial header state (m_pendingHeaderBytes): headers split across two appendData() calls are reassembled through a byte-accumulation buffer, and off-by-one errors in bytes consumed versus buffered could corrupt the parser's view of box boundaries, misclassifying a media segment as an init segment or the reverse.
Append split logic: on detecting a mid-append ftyp, the pre-parser computes a byte offset and slices the SharedBuffer in two. An arithmetic error in the split offset — particularly given header sizes varying between 8 bytes for 32-bit and 16 for 64-bit extended — sends misaligned data to AVStreamDataParser.
Dead code path reactivation: AppendFlags::Discontinuity was never reached outside resetParserState(), so any assumption embedded in downstream handling of that flag — what state AVStreamDataParser is expected to be in when it arrives — has never been tested in the mid-stream re-init scenario. Sending Discontinuity after partial media data may expose unexpected AVFoundation behavior.
Non-Gigacaged memory via BitReader: the commit explicitly documents that ISOBox::peekBox faults on SharedBuffer memory and uses BitReader instead. If BitReader has bounds checking gaps and the pre-parser misjudges the remaining buffer length — via a truncated 64-bit size field, for instance — this path operates on attacker data without the Gigacage safety net. Any other parser recently moved off the DataView/Gigacage path for the same reason inherits the same question.