Chrome · CSS
CVE-2026-17977
Logic Error in CSS
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fthird_party/blink/renderer/core/css/css_image_value_test.cc |
modified |
Files Changed
third_party/blink/renderer/core/css/css_image_value_test.ccthird_party/blink/renderer/core/css/css_url_data.h
Patch
From f479b1fc9e0cddd2a6d3c0b1c452dcf315411a44 Mon Sep 17 00:00:00 2001
From: Kevin Babbitt <kbabbitt@microsoft.com>
Date: Mon, 15 Jun 2026 09:37:19 -0700
Subject: [PATCH] Serialize relative URL for reference properties in dangling markup cases
Similar to https://crrev.com/c/7895140, this allows the potentially-
dangling-markup flag to be re-derived when the value is re-parsed.
Fixed: 519603552
Change-Id: I8318c523d8b72cb3b88c41fa90b091b6085ee115
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7929541
Commit-Queue: Kevin Babbitt <kbabbitt@microsoft.com>
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1646881}
---
diff --git a/third_party/blink/renderer/core/css/css_image_value_test.cc b/third_party/blink/renderer/core/css/css_image_value_test.cc
index 3f12c03..47fb20b 100644
--- a/third_party/blink/renderer/core/css/css_image_value_test.cc
+++ b/third_party/blink/renderer/core/css/css_image_value_test.cc
@@ -6,6 +6,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/web/web_script_source.h"
+#include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
@@ -201,4 +202,38 @@
}
}
+// Verifies that serializing a `filter: url(...)` computed value does not
+// strip the potentially_dangling_markup flag from the URL. The serializer
+// path (ComputedStyleUtils::ValueForFilter) builds a fresh CSSUrlData from
+// CSSUrlData::ValueForSerialization(), which must return the raw relative
+// URL when the flag is set so that re-parsing the serialized form does not
+// launder it.
+TEST_F(CSSImageValueTest, FilterUrlDoesNotLaunderDanglingMarkup) {
+ SimRequest main_resource("https://example.com/index.html", "text/html");
+
+ LoadURL("https://example.com/index.html");
+
+ main_resource.Complete(R"HTML(
+ <!doctype html>
+ <style>
+ #victim { filter: url('/exfil?\a <secret#x'); }
+ </style>
+ <div id="victim"></div>
+ )HTML");
+
+ test::RunPendingTasks();
+ GetDocument().UpdateStyleAndLayoutTree();
+ Compositor().BeginFrame();
+
+ auto* victim = GetDocument().getElementById(AtomicString("victim"));
+ ASSERT_TRUE(victim);
+ auto* computed = MakeGarbageCollected<CSSComputedStyleDeclaration>(victim);
+ String serialized = computed->GetPropertyValue(CSSPropertyID::kFilter);
+ // The serialized value must use the raw relative URL form rather than the
+ // canonicalized absolute URL with whitespace stripped. The latter would
+ // launder the dangling markup flag when re-parsed.
+ EXPECT_EQ(serialized.find("example.com"), kNotFound) << serialized;
+ EXPECT_NE(serialized.find("/exfil"), kNotFound) << serialized;
+}
+
} // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_url_data.h b/third_party/blink/renderer/core/css/css_url_data.h
index a3cdb86..a2872b7 100644
--- a/third_party/blink/renderer/core/css/css_url_data.h
+++ b/third_party/blink/renderer/core/css/css_url_data.h
@@ -102,8 +102,14 @@
// Returns a copy where the referrer has been reset.
const CSSUrlData* MakeWithoutReferrer() const;
+ // For dangling-markup URLs, return the raw relative form so that
+ // round-tripping through computed-style serialization cannot launder the
+ // potentially-dangling-markup flag. See MakeResolved()/MakeComputed() for
+ // the same rationale.
const AtomicString& ValueForSerialization() const {
- return is_local_ || absolute_url_.empty() ? relative_url_ : absolute_url_;
+ return is_local_ || absolute_url_.empty() || potentially_dangling_markup_
+ ? relative_url_
+ : absolute_url_;
}
const AtomicString& UnresolvedUrl() const { return relative_url_; }
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/css/css_image_value_test.cc b/third_party/blink/renderer/core/css/css_image_value_test.cc
index 3f12c03..47fb20b 100644
--- a/third_party/blink/renderer/core/css/css_image_value_test.cc
+++ b/third_party/blink/renderer/core/css/css_image_value_test.cc
@@ -6,6 +6,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/web/web_script_source.h"
+#include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
@@ -201,4 +202,38 @@
}
}
+// Verifies that serializing a `filter: url(...)` computed value does not
+// strip the potentially_dangling_markup flag from the URL. The serializer
+// path (ComputedStyleUtils::ValueForFilter) builds a fresh CSSUrlData from
+// CSSUrlData::ValueForSerialization(), which must return the raw relative
+// URL when the flag is set so that re-parsing the serialized form does not
+// launder it.
+TEST_F(CSSImageValueTest, FilterUrlDoesNotLaunderDanglingMarkup) {
+ SimRequest main_resource("https://example.com/index.html", "text/html");
+
+ LoadURL("https://example.com/index.html");
+
+ main_resource.Complete(R"HTML(
+ <!doctype html>
+ <style>
+ #victim { filter: url('/exfil?\a <secret#x'); }
+ </style>
+ <div id="victim"></div>
+ )HTML");
+
+ test::RunPendingTasks();
+ GetDocument().UpdateStyleAndLayoutTree();
+ Compositor().BeginFrame();
+
+ auto* victim = GetDocument().getElementById(AtomicString("victim"));
+ ASSERT_TRUE(victim);
+ auto* computed = MakeGarbageCollected<CSSComputedStyleDeclaration>(victim);
+ String serialized = computed->GetPropertyValue(CSSPropertyID::kFilter);
+ // The serialized value must use the raw relative URL form rather than the
+ // canonicalized absolute URL with whitespace stripped. The latter would
+ // launder the dangling markup flag when re-parsed.
+ EXPECT_EQ(serialized.find("example.com"), kNotFound) << serialized;
+ EXPECT_NE(serialized.find("/exfil"), kNotFound) << serialized;
+}
+
} // namespace blink
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page