Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in CSS
DescriptionInsufficient policy enforcement in CSS
ComponentCSS
Bug ClassLogic Error
Tracker502035074
Fix commit092175216a8a (chromium/src) +20/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • third_party/blink/renderer/core/css/resolver/style_cascade.cc
  • third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html
From 092175216a8aebc9c214aa860e30bc78e3dbfff2 Mon Sep 17 00:00:00 2001
From: moonira <moonira@google.com>
Date: Mon, 13 Apr 2026 04:48:20 -0700
Subject: [PATCH] Propagate attr() taint ranges when resolving shorthands

When resolving pending substitutions for shorthand properties, the
original text is reparsed into a new CSSParserTokenStream. Previously,
the attr() taint ranges from the CSSVariableData were not passed along
to the new stream. This allowed values from attr() functions to bypass
security checks when used within shorthand properties.

This patch ensures that `GetAttrTaintedRanges()` is passed to the new
`CSSParserTokenStream` when reparsing the shorthand value, correctly
enforcing attr() security restrictions.

Fixed: 502035074
Change-Id: I6dff0854f29937988a757ded902b6f6c47802773
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7747847
Commit-Queue: Munira Tursunova <moonira@google.com>
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1613629}
---

diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index 4f21c67..065e3b521 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -1443,7 +1443,8 @@
 
     // NOTE: We don't actually need the original text to be comment-stripped,
     // since we're not storing it in a custom property anywhere.
-    CSSParserTokenStream stream2(sequence.OriginalText());
+    CSSParserTokenStream stream2(sequence.OriginalText(),
+                                 sequence.GetAttrTaintedRanges());
     if (!CSSPropertyParser::ParseValue(
             shorthand_property_id, /*allow_important_annotation=*/false,
             stream2, shorthand_value->ParserContext(), parsed_properties,
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html b/third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html
index 8a6bed9..6444c99 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html
@@ -47,6 +47,19 @@
         elem.style.setProperty(property, null);
     }
 
+    function test_attr_shorthand(shorthand, longhand, attrString, attrValue, expectedValue) {
+        var elem = document.getElementById("attr");
+        elem.setAttribute("data-foo", attrValue);
+        elem.style.setProperty(shorthand, attrString);
+
+        test(() => {
+            assert_equals(window.getComputedStyle(elem).getPropertyValue(longhand),
+                          expectedValue);
+        }, `'${shorthand}: ${attrString}' with data-foo="${attrValue}"`);
+
+        elem.style.setProperty(shorthand, null);
+    }
+
     function test_registered_custom_property(customPropertyName, customPropertySyntax, customPropertyInitialValue,
                                              attrValue, expectedValue) {
       window.CSS.registerProperty({
@@ -78,6 +91,11 @@
               `image-set(attr(data-foo))`,
               url,
               'none');
+    test_attr_shorthand('background',
+                        'background-image',
+                        `image-set(attr(data-foo))`,
+                        url,
+                        'none');
     test_attr('background-image',
               `image-set("${url}")`,
               url,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html b/third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html
index 8a6bed9..6444c99 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/attr-security.html
@@ -47,6 +47,19 @@
         elem.style.setProperty(property, null);
     }
 
+    function test_attr_shorthand(shorthand, longhand, attrString, attrValue, expectedValue) {
+        var elem = document.getElementById("attr");
+        elem.setAttribute("data-foo", attrValue);
+        elem.style.setProperty(shorthand, attrString);
+
+        test(() => {
+            assert_equals(window.getComputedStyle(elem).getPropertyValue(longhand),
+                          expectedValue);
+        }, `'${shorthand}: ${attrString}' with data-foo="${attrValue}"`);
+
+        elem.style.setProperty(shorthand, null);
+    }
+
     function test_registered_custom_property(customPropertyName, customPropertySyntax, customPropertyInitialValue,
                                              attrValue, expectedValue) {
       window.CSS.registerProperty({
@@ -78,6 +91,11 @@
               `image-set(attr(data-foo))`,
               url,
               'none');
+    test_attr_shorthand('background',
+                        'background-image',
+                        `image-set(attr(data-foo))`,
+                        url,
+                        'none');
     test_attr('background-image',
               `image-set("${url}")`,
               url,
Loading diff…

Original Bug Report

reported by vm...@google.com

Attr-taint bypass in shorthand properties allows data exfiltration

Project Fortify, 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 without the Chrome Security team.

Overview: A potential vulnerability exists in Blink’s CSS engine where the attr-taint security mechanism is bypassed when using shorthand properties. Taint ranges are dropped during the re-parsing of resolved substitutions, stripping the security context. An attacker with CSS injection could exploit this to exfiltrate sensitive DOM attributes.

Affected files:

  • third_party/blink/renderer/core/css/resolver/style_cascade.cc
  • third_party/blink/renderer/core/css/parser/css_parser_token_stream.h

Estimated timestamp from git blame: 2024-09-04

Description

Initial CSS parsing logic and cascade resolution parameters for shorthand properties are validated. When encountering unparsed variable or attr() functions within a shorthand, the engine correctly handles the CSSPendingSubstitutionValue and initiates token resolution via ResolveTokensInto. Standard processing is applied for DOM attribute extraction and token sequence generation, successfully recording the attr() taint tracking metadata internally.

However, for the final resolution, the logic leaps directly to a flawed transformation. In third_party/blink/renderer/core/css/resolver/style_cascade.cc, StyleCascade::ResolvePendingSubstitution instantiates the parser stream used for longhand expansion as follows:

// style_cascade.cc:1446
CSSParserTokenStream stream2(sequence.OriginalText());

This constructor invocation omits the taint ranges. Unlike the longhand equivalent (ResolveVariableReference), which correctly passes sequence.GetAttrTaintedRanges(), this omission strips all taint tracking from the resulting stream. Consequently, subsequent security checks, such as stream.IsAttrTainted() in ConsumeImage, evaluate to false, neutralizing the CSS Values Level 5 attr() security mechanism.

Potential Reproduction Steps

Note: These are suggested/potential steps as our tooling agent does not yet have the ability to run code.

  1. Standard target initialization: A webpage containing a sensitive DOM attribute (e.g., <input name='csrf' value='SECRET123'>).
  2. General CSS injection payload delivery: The attacker injects a stylesheet containing a shorthand property utilizing attr(), such as input[name=csrf] { background: image-set(attr(value)); }.
  3. Standard style resolution cascade execution applied by the browser.
  4. Execution jumps to the untainted CSSParserTokenStream creation, dropping the attr() security context and directly executing the network fetch to the attacker’s origin using the attribute’s value.

Suggested Fix

Update StyleCascade::ResolvePendingSubstitution to preserve taint ranges during stream construction:

CSSParserTokenStream stream2(sequence.OriginalText(), sequence.GetAttrTaintedRanges());

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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