← All reports

[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

+Test 1: Append media segment before any init segment - should error
+EVENT(sourceopen)
+EXPECTED (gotError == 'true') OK
+InvalidStateError: The object is in an invalid state.
+PASS
+
+Test 5: After changeType, media segment without init should error
+EVENT(sourceopen)
+EVENT(updateend)
+EVENT(updateend)
+EXPECTED (sourceBuffer.buffered.length == '1') OK
+EXPECTED (gotError == 'true') OK
+PASS

LayoutTests/media/media-source/media-source-append-media-before-init.html

+function makeFreeBox(size) {
+ var buffer = new ArrayBuffer(size);
+ var view = new DataView(buffer);
+ view.setUint32(0, size);
+ view.setUint32(4, 0x66726565); // 'free'
+ return buffer;
+}

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

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.

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.