[3] HTMLPlugInElement skips content extensions for embed/object loads
Rated Medium because the diff fixes a deterministic content-blocker bypass reachable from any page that can author <embed>/<object> markup; impact is policy-evasion (tracker/malware/ad loads escaping user-installed rules), not memory corruption.
Plugin content loaded via <embed> and <object> elements was not checked against content extension rules. The fix adds a content-extension check in HTMLPlugInElement::canLoadURL so blocked URLs are rejected before the wouldLoadAsPlugIn deferral in updateWidget.
Source/WebCore/html/HTMLPlugInElement.cpp
LayoutTests/http/tests/contentextensions/block-embed-element.html
Patch Details
When ENABLE(CONTENT_EXTENSIONS) is on and the URL is valid, canLoadURL now obtains the document's Page, DocumentLoader, and UserContentProvider, then calls UserContentProvider::processContentRuleListsForLoad(page, completeURL, ContentExtensions::ResourceType::Other, documentLoader). If the aggregated result's shouldBlock() is true, canLoadURL returns false so the caller skips loading the plugin resource. Regression tests load <embed> and <object> resources matching a .*should-be-blocked URL filter and assert the standard 'Content blocker prevented frame...' console message fires.
Missing content-policy consultation on a resource-load path, allowing the plugin element class to bypass a filter applied uniformly to other element types.
Background
Content Extensions (also called content blockers) expose a declarative rule-list API to WebKit extensions and managed configurations. Each rule pairs a trigger (URL pattern, resource type, load context) with an action (block, block-cookies, css-display-none, make-https). UserContentProvider::processContentRuleListsForLoad is the engine entry point that evaluates all active rule lists for a load and returns aggregated actions; callers inspect results.shouldBlock() to decide whether to abort the load. HTMLPlugInElement is the C++ base class for <embed>, <object>, and legacy <applet>; its canLoadURL is the single funnel both subclasses consult before updateWidget asks the SubframeLoader to instantiate the plugin or subframe widget. Content-extension hooks on <iframe>, <img>, <script>, and XHR/fetch loads live in their respective loader paths.
Analysis
HTMLPlugInElement::canLoadURL validated the URL against self-reference checks but never consulted the content-extension engine. The rule-list evaluation path that exists for <img>, <script>, <iframe>, XHR, and other resource types was simply not wired into the plugin element class.
An attacker authoring or injecting HTML places an <embed src="BLOCKED_URL"> or <object data="BLOCKED_URL"> referencing a URL that the user's installed content blocker would otherwise reject. Pre-fix, the load proceeded — a third-party tracker, malware-hosting domain, or PDF on a domain the user blocked rendered inside the page, since HTMLPlugInElement::canLoadURL returned true without consulting UserContentProvider. The added call to processContentRuleListsForLoad(..., ResourceType::Other, ...) rejects those loads at the same choke point used by SubframeLoader. The test cases exercise plain <embed> and <object> markup against a .*should-be-blocked filter and demonstrate the new path emitting the 'Content blocker prevented frame...' console message.
This vulnerability weakened the content-blocker trust boundary that users and managed-device policy rely on to prevent specific URLs from loading inside a page. The HTML content-extension model assumes every subresource load — including plugin element loads — is evaluated against the active rule lists; before the fix this invariant was violated for <embed> and <object>. Content-extension enforcement in WebKit is not centralised in a single load gate — it is replicated across each element/loader class that initiates a network fetch, which makes coverage gaps a recurring pattern.
Audit directions
- HTML element classes that initiate resource loads without consulting the content-extension engine. Audit every element/loader with its own
canLoad*orrequestResource-style gate and verify it callsUserContentProvider::processContentRuleListsForLoad. Start withHTMLTrackElement::canLoadURL(text-track loads),HTMLFrameElementBase::canLoadURL,HTMLMediaElementresource selection,HTMLLinkElementfor<link rel=preload>, and Service Worker / WorkerimportScriptspaths — grepprocessContentRuleListsForLoadto enumerate existing hook sites and compare againstcanLoadURL/canLoad/isAllowedToLoadcallers. - Silent bypass via
ResourceTypemis-bucketing. Audit howContentExtensions::ResourceTypeis selected at each call site — a load placed in the wrong bucket can match too few or too many rules. Verify that plugin loads, PDF embeds, MathML/SVG external loads, and<object>-as-image fallback paths use the correct type. - Feature-flag-gated security checks. Confirm that
ENABLE(CONTENT_EXTENSIONS)is the build configuration used on the actual shipping ports (macOS, iOS, visionOS) for any caller ofcanLoadURL; a check guarded by the flag is dead code on ports where it is off. - Verify the new check fires before subframe creation rather than after: trace
HTMLPlugInElement::updateWidgetandSubframeLoader::requestObject/requestEmbedto confirm there is no second resource-load path (fallback content, type-detection sniff fetch, PDF preview thumbnail) that proceeds without re-consulting the content-extension engine.