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
Tracker522781838
Fix commitde9ee25bb552 (chromium/src) +7/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • third_party/blink/renderer/core/css/css_font_feature_value.cc
  • third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html
From de9ee25bb552ef2091697a17e96355bd75cf304a Mon Sep 17 00:00:00 2001
From: Dileep Maurya <[email protected]>
Date: Thu, 18 Jun 2026 13:07:42 -0700
Subject: [PATCH] Escape font-feature-settings tag on serialization

`CSSFontFeatureValue::CustomCSSText()` wrapped the OpenType feature tag
in double quotes without escaping its contents. A feature tag is
parsed as a 4-byte string in which each byte may be any value in
`0x20-0x7E` per OpenType, including '"' (U+0022) \ (U+005C) and '}'
(U+007D). Serialization therefore emitted unbalanced quotes, breaking
the CSSOM serialize/re-parse round-trip and allowing CSS injection
when an application copies `element.style.cssText` into a <style> rule:
a tag such as "}/* could close the surrounding rule and inject
author-controlled CSS.

Serialize the tag with `SerializeString()`, which escapes '"' and '\',
matching `CSSFontVariationValue::CustomCSSText()`.

Test: `external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html`

Bug: 522781838
Change-Id: I5d7b4f87a0f3ea5262cbfd07eeb5f77120466504
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7954618
Reviewed-by: Kevin Babbitt <[email protected]>
Commit-Queue: Dileep Maurya <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1649252}
---

diff --git a/third_party/blink/renderer/core/css/css_font_feature_value.cc b/third_party/blink/renderer/core/css/css_font_feature_value.cc
index 98b6311..7d61ede 100644
--- a/third_party/blink/renderer/core/css/css_font_feature_value.cc
+++ b/third_party/blink/renderer/core/css/css_font_feature_value.cc
@@ -25,6 +25,7 @@
 
 #include "third_party/blink/renderer/core/css/css_font_feature_value.h"
 
+#include "third_party/blink/renderer/core/css/css_markup.h"
 #include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
@@ -37,9 +38,7 @@
 
 String CSSFontFeatureValue::CustomCSSText() const {
   StringBuilder builder;
-  builder.Append('"');
-  builder.Append(tag_);
-  builder.Append('"');
+  SerializeString(tag_, builder);
   // Omit the value if it's 1 as 1 is implied by default.
   if (!value_->IsNumericLiteralValue() ||
       ClampTo<int>(To<CSSNumericLiteralValue>(*value_).ClampedDoubleValue()) !=
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html
index 9ed4b5c..d35771c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html
@@ -22,6 +22,11 @@
 
 test_valid_value('font-feature-settings', '"PKRN"');
 
+// A feature tag may contain any byte in 0x20-0x7E per OpenType; quotes and
+// backslashes must be escaped on serialization.
+test_valid_value('font-feature-settings', '"\\22\\7d\\2f\\2a"', '"\\"}/*"');
+test_valid_value('font-feature-settings', '"\\22\\22\\22\\22"', '"\\"\\"\\"\\""');
+
 test_valid_value('font-feature-settings', '"dlig" 1, "smcp" on, "dlig" 0', '"dlig", "smcp", "dlig" 0');
 </script>
 </body>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html
index 9ed4b5c..d35771c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-feature-settings-valid.html
@@ -22,6 +22,11 @@
 
 test_valid_value('font-feature-settings', '"PKRN"');
 
+// A feature tag may contain any byte in 0x20-0x7E per OpenType; quotes and
+// backslashes must be escaped on serialization.
+test_valid_value('font-feature-settings', '"\\22\\7d\\2f\\2a"', '"\\"}/*"');
+test_valid_value('font-feature-settings', '"\\22\\22\\22\\22"', '"\\"\\"\\"\\""');
+
 test_valid_value('font-feature-settings', '"dlig" 1, "smcp" on, "dlig" 0', '"dlig", "smcp", "dlig" 0');
 </script>
 </body>
Loading diff…

Original Bug Report

reported by [email protected]

CSS Injection / mXSS via unescaped quotes in CSSFontFeatureValue::CustomCSSText()

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: The CSSFontFeatureValue::CustomCSSText() method fails to escape double quotes when serializing the font-feature-settings property. An attacker can use CSS hex escapes to inject a tag containing quotes, which is valid per OpenType specs. When the element’s cssText is serialized and reused in a <style> block, it can lead to arbitrary CSS injection.

Affected files:

  • third_party/blink/renderer/core/css/css_font_feature_value.cc
  • third_party/blink/renderer/core/css/properties/css_parsing_utils.cc

Estimated timestamp from git blame: 2018-01-29

Summary

A serialization bug in CSSFontFeatureValue::CustomCSSText() allows for potential CSS injection and mutation XSS (mXSS). The method manually serializes the font feature tag_ member by wrapping it in double quotes without escaping internal characters. Because the CSS parser correctly allows characters in the range [0x20, 0x7E] for OpenType tags (including quotes and braces), an attacker can craft a 4-character tag that breaks out of the CSS string context upon serialization.

Root Cause

In third_party/blink/renderer/core/css/properties/css_parsing_utils.cc (ConsumeFontSettingsTagAndValue), the parser validates the font feature tag. It ensures the tag is exactly 4 characters long and that each character is between 0x20 and 0x7E. This range intentionally includes characters like " (0x22), \ (0x5C), and } (0x7D).

However, in third_party/blink/renderer/core/css/css_font_feature_value.cc, the CustomCSSText() function manually concatenates the tag value during serialization:

String CSSFontFeatureValue::CustomCSSText() const {
  StringBuilder builder;
  builder.Append('"');
  builder.Append(tag_);
  builder.Append('"');
  // ...

Because tag_ is not properly escaped, an attacker can inject quotes that prematurely close the serialized string.

Potential Exploitation Scenario

Note: These are suggested steps based on code analysis; a working proof of concept has not been executed.

An attacker can exploit this if a web application or DOM sanitizer reads an element’s cssText and places it into a <style> block (a common pattern for CSP compliance or shadow DOM styling).

  1. The attacker controls an inline style and injects a 4-character payload using CSS hex escapes:
// Inject the tag `"}/*`
element.style.fontFeatureSettings = '"\\22\\7d\\2f\\2a"'; 

// Inject a custom property to hold the rest of the payload
element.style.setProperty('--payload', '*/ body { background: red; } /*');
  1. The browser parses these successfully. When the application reads element.style.cssText, Blink’s StylePropertySerializer serializes the properties.

  2. Due to the lack of escaping, the resulting cssText becomes: font-feature-settings: ""}/*"; --payload: */ body { background: red; } /*;

  3. The application inserts this into a <style> block, e.g.: <style>.user-rule { font-feature-settings: ""}/*"; --payload: */ body { background: red; } /*; }</style>

  4. When the browser parses this new <style> block:

    • "" completes the font-feature-settings string.
    • } prematurely closes the .user-rule CSS block.
    • /* starts a CSS comment that swallows the remainder of the first line.
    • */ (from the custom property) closes the comment.
    • The attacker’s injected CSS body { background: red; } is evaluated as active CSS in the global scope.

Impact

This vulnerability can be leveraged to bypass application-level CSS sanitizers that rely on the browser’s CSS serialization. This can lead to CSS-based data exfiltration or UI spoofing.

Suggested Fix

In third_party/blink/renderer/core/css/css_font_feature_value.cc, use SerializeString(tag_, builder) (available via third_party/blink/renderer/core/css/css_markup.h) instead of manually appending quotes. This ensures the tag is properly escaped, matching the correct behavior already implemented in CSSFontVariationValue::CustomCSSText().

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


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