← All issues

[3] HTMLPlugInElement skips content extensions for embed/object loads

Severity: Medium | Component: WebCore HTML plugin elements | 7d28e39

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

+#if ENABLE(CONTENT_EXTENSIONS)
+#include "ContentExtensionsBackend.h"
+#include "ResourceLoadInfo.h"
+#include "UserContentProvider.h"
+#endif
...
bool HTMLPlugInElement::canLoadURL(const URL& completeURL) const
{
+#if ENABLE(CONTENT_EXTENSIONS)
+ if (completeURL.isValid()) {
+ RefPtr page = document().page();
+ RefPtr frame = document().frame();
+ RefPtr documentLoader = frame ? frame->loader().documentLoader() : nullptr;
+ RefPtr userContentProvider = frame ? frame->userContentProvider() : nullptr;
+ if (page && documentLoader && userContentProvider) {
+ auto results = userContentProvider->processContentRuleListsForLoad(*page, completeURL, ContentExtensions::ResourceType::Other, *documentLoader);
+ if (results.shouldBlock())
+ return false;
+ }
+ }
+#endif
return !isProhibitedSelfReference(completeURL);
}

LayoutTests/http/tests/contentextensions/block-embed-element.html

+<embed src="http://127.0.0.1:8000/contentextensions/resources/should-load.html?should-be-blocked" type="text/html" width="100" height="100"></embed>
+<embed src="http://127.0.0.1:8000/media/resources/test.pdf?should-be-blocked" type="application/pdf" width="100" height="100"></embed>

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.

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.

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.