CVE-2026-11162
Overview
Files Changed
third_party/blink/renderer/core/css/resolver/style_cascade.ccthird_party/blink/web_tests/external/wpt/css/css-values/attr-security.html
Patch
From 092175216a8aebc9c214aa860e30bc78e3dbfff2 Mon Sep 17 00:00:00 2001 From: moonira <[email protected]> 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 <[email protected]> Reviewed-by: Anders Hartvoll Ruud <[email protected]> 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,
Regression Test / PoC
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,
Original Bug Report
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.ccthird_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.
- Standard target initialization: A webpage containing a sensitive DOM attribute (e.g.,
<input name='csrf' value='SECRET123'>). - General CSS injection payload delivery: The attacker injects a stylesheet containing a shorthand property utilizing
attr(), such asinput[name=csrf] { background: image-set(attr(value)); }. - Standard style resolution cascade execution applied by the browser.
- Execution jumps to the untainted
CSSParserTokenStreamcreation, dropping theattr()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.