← All issues

[6] Content served as video/mp2t can be loaded as html

Severity: Medium | Component: WebCore DOMImplementation / MIME registry | 59efb64

Rated Medium because the observable effect is a MIME-confusion script-execution primitive in the response's origin, the regression test demonstrates it from a data:video/mp2t,... iframe, and impact in real-world deployments is bounded by attacker control over response Content-Type rather than memory safety.

Source/WebCore/dom/DOMImplementation.cpp

#if ENABLE(VIDEO)
- MediaEngineSupportParameters parameters;
- parameters.type = ContentType { contentType };
- parameters.url = url;
- if (MediaPlayer::supportsType(parameters) != MediaPlayer::SupportsType::IsNotSupported)
+ if (MIMETypeRegistry::isSupportedMediaMIMEType(contentType))
return MediaDocument::create(frame, settings, url);
#endif

Source/WebCore/platform/MIMETypeRegistry.cpp

bool MIMETypeRegistry::isSupportedMediaMIMEType(const String& mimeType)
{
if (mimeType.isEmpty())
return false;
+ auto lowercaseType = mimeType.convertToASCIILowercase();
+ if (!lowercaseType.startsWith("video/"_s) && !lowercaseType.startsWith("audio/"_s) && !lowercaseType.startsWith("application/"_s))
+ return false;
return supportedMediaMIMETypes().contains(mimeType);
}

Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm

void MediaPlayerPrivateMediaSourceAVFObjC::getSupportedTypes(HashSet<String>& types)
{
- types = AVStreamDataParserMIMETypeCache::singleton().supportedTypes();
+ types.clear();
}

Three coordinated changes. DOMImplementation::createDocument no longer asks MediaPlayer::supportsType() to decide whether to instantiate a MediaDocument; it now consults MIMETypeRegistry::isSupportedMediaMIMEType(contentType) directly. isSupportedMediaMIMEType gains a top-level allowlist that rejects any MIME not beginning with video/, audio/, or application/. MediaPlayerPrivateMediaSourceAVFObjC::getSupportedTypes is changed to types.clear() — the MSE-only AVFoundation engine no longer pollutes the global supported-media cache.

MIME-type-driven document-class dispatch consulted a different oracle than the up-front canShowMIMEType admission check, so a media MIME admitted as renderable fell through to the HTML default path.

When a frame load arrives, FrameLoader calls MIMETypeRegistry::canShowMIMEType(type) to decide whether to render it inline; only types that pass proceed to DOMImplementation::createDocument. createDocument is a chained if/return cascade that maps Content-Type to Document subclass — text/htmlHTMLDocument, image MIMEs → ImageDocument, supported media MIMEs → MediaDocument, and anything that falls through ultimately yields HTMLDocument. The media MIME registry aggregates types from every MediaPlayerFactory's getSupportedTypes callback. MSE (Media Source Extensions) is a JS-driven streaming pipeline; MediaPlayerPrivateMediaSourceAVFObjC::supportsTypeAndCodecs returns IsNotSupported unless the caller passes platformType == PlatformMediaDecodingType::MediaSource, so MSE-only types like video/mp2t are claimed by the MSE engine but cannot be played as bare-URL loads.

canShowMIMEType (registry-based) and createDocument (live MediaPlayer::supportsType query) implemented the same conceptual decision against different sources of truth. The MSE engine had registered video/mp2t into supportedMediaMIMETypes(), so canShowMIMEType("video/mp2t") returned true and the load proceeded. createDocument then asked MediaPlayer::supportsType(parameters) with a non-MSE platformType (a bare-URL load); the MSE engine refused, no other engine claimed video/mp2t, and execution fell through past every MediaDocument/ImageDocument/PDFDocument branch to the default HTMLDocument tail.

🔒

Two MIME oracles, one decision — and when they disagreed the fall-through path led somewhere unexpected. Full mechanism, attacker model, and registry-pollution analysis inside.

Subscribe to read more

🔒

Four reusable audit patterns covering document-factory dispatch, MIME-registry hygiene, predicate validation, and a data-URI amplifier — each with concrete grep starting points.

Subscribe to read more