CVE-2026-5273
Overview
Background
- Lazy style attributes
- Blink defers materializing an element’s inline
styleattribute into its real attribute storage until something reads it, synchronizing (and thus mutating) the attribute collection on demand. - `AtomicString`
- an interned, reference-counted string; a
const AtomicString&returned bygetAttributepoints into the element’s live attribute storage and is only valid while that storage is unchanged. - `InputRules` bucketing
RuleSetindexes selectors that match<input>elements by the element’stypevalue, so rule matching looks up the lowercasedtypestring to find candidate rules.- `CollectMatchingRulesForList`
- the per-rule-set matcher that evaluates candidate selectors and, as a side effect, can read
[style]and trigger lazy-attribute synchronization.
Root Cause Analysis
The vulnerable code in element_rule_collector.cc bound input_type as a const AtomicString& reference into the element’s attribute storage via element.getAttribute(html_names::kTypeAttr), then entered a loop over match_request.RuleSetsWithInputRules(), calling input_type.ToAsciiLower() on each iteration. Inside that loop CollectMatchingRulesForList<stop_at_first_match>() can evaluate a [style] selector, which synchronizes the element’s lazy style attribute and appends a new attribute — reallocating or mutating the attribute storage and leaving the previously obtained input_type reference dangling. On a subsequent iteration, dereferencing that dangling reference to call ToAsciiLower() is a use-after-free read.
The fix snapshots the value once, before the loop, into an independent local AtomicString input_type_lower = input_type.ToAsciiLower(); and passes input_type_lower into InputRules(), so no reference into the attribute storage survives across the side-effecting calls. The invariant restored is that a value read from attribute storage must be copied out before any operation that may mutate that storage.
type string into a stable local before the loop removes the dangling-reference window.Attack Path
- Craft the markup
The page contains an
<input type="text">plus two stylesheets whose selectors (e.g.input[style][type="text"]) bucket oninputtype and also reference[style], so matching will touch the lazystyleattribute. - Force lazy style
Script sets
target.style.color = 'red', establishing inline style that has not yet been synchronized into the element’s real attribute storage. - Begin rule collection
Style resolution reads
input_typeas a reference into attribute storage, then iterates the input-rule rule sets. - Trigger synchronization
While matching the first rule set,
CollectMatchingRulesForList()evaluates[style], synchronizing the lazy attribute and reallocating the attribute storage, which invalidates the still-heldinput_typereference. - Use-after-free read
A later loop iteration calls
input_type.ToAsciiLower()on the freed/moved storage, reading dangling memory; further consequences beyond this read are not shown by the diff (inferred).
Impact Assessment
<input> element, two matching stylesheets, and a script touch of style. The vendor advisory rates this high. Any escalation from the dangling read to information disclosure or memory corruption is not demonstrated by the patch and is (inferred).Files Changed
third_party/blink/renderer/core/css/element_rule_collector.ccthird_party/blink/web_tests/external/wpt/css/css-values/crashtests/chrome-bug-493952652.html
Audit Directions
- References across side-effecting callsFlag any
const AtomicString&or raw reference/pointer obtained fromgetAttribute/attribute storage that is dereferenced after a call that can synchronize lazy attributes or mutate the attribute collection. - Loops over rule setsReview other
CollectMatchingRulesFor*call sites that read an element value once and reuse it per iteration; snapshot such values before iterating. - Lazy synchronization triggersAudit code paths where selector matching evaluates
[style]or other presentation attributes, since these can mutate node state mid-traversal and invalidate earlier reads.
Patch
From d8b01057f740d3bb0ec880b34372da63147c2521 Mon Sep 17 00:00:00 2001 From: Steinar H. Gunderson <[email protected]> Date: Fri, 20 Mar 2026 07:22:02 -0700 Subject: [PATCH] Fix another use-after-free with lazy style attributes. This is a similar problem as regular attribute checks, just for the special case of input type="" (which is a similar but separate path). Style perftest and Speedometer3 are neutral. Fixed: 493952652 Change-Id: I264503545c345325e6d21afa0726f524bb9394b8 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7686835 Reviewed-by: Anders Hartvoll Ruud <[email protected]> Commit-Queue: Steinar H Gunderson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1602570} --- diff --git a/third_party/blink/renderer/core/css/element_rule_collector.cc b/third_party/blink/renderer/core/css/element_rule_collector.cc index 901a258..64198a33 100644 --- a/third_party/blink/renderer/core/css/element_rule_collector.cc +++ b/third_party/blink/renderer/core/css/element_rule_collector.cc @@ -957,11 +957,14 @@ if (const AtomicString& input_type = element.getAttribute(html_names::kTypeAttr); !input_type.IsNull()) { + // Do not use input_type in the loop; the reference + // may be dangling if CollectMatchingRulesForList() + // adds lazy attributes. + AtomicString input_type_lower = input_type.ToAsciiLower(); for (const auto bundle : match_request.RuleSetsWithInputRules()) { if (CollectMatchingRulesForList<stop_at_first_match>( - bundle.rule_set->InputRules(input_type.ToAsciiLower()), - match_request, bundle.rule_set, bundle.style_sheet_index, - checker, context) && + bundle.rule_set->InputRules(input_type_lower), match_request, + bundle.rule_set, bundle.style_sheet_index, checker, context) && stop_at_first_match) { return true; } diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/crashtests/chrome-bug-493952652.html b/third_party/blink/web_tests/external/wpt/css/css-values/crashtests/chrome-bug-493952652.html new file mode 100644 index 0000000..a9c96ec5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-values/crashtests/chrome-bug-493952652.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<head> + <link rel="help" href="https://crbug.com/493952652"> + <style> + /* + Evaluation of [style] will add a new attribute to the element, + as lazy style gets synchronized. However, the bucketing will be + on input type, so we do not synchronize the attributes _before_ + iteration. + */ + input[style][type="text"] {} + </style> + <style> + /* + For the second stylesheet, when checking the type="" value string, + we must not use a reference into the old attributes. + */ + input[style][type="text"] {} + </style> +</head> +<body> + <input id="target" type="text"></input> + <script> + document.getElementById('target').style.color = 'red'; + </script> +</body>
Regression Test / PoC
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/crashtests/chrome-bug-493952652.html b/third_party/blink/web_tests/external/wpt/css/css-values/crashtests/chrome-bug-493952652.html
new file mode 100644
index 0000000..a9c96ec5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/crashtests/chrome-bug-493952652.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<head>
+ <link rel="help" href="https://crbug.com/493952652">
+ <style>
+ /*
+ Evaluation of [style] will add a new attribute to the element,
+ as lazy style gets synchronized. However, the bucketing will be
+ on input type, so we do not synchronize the attributes _before_
+ iteration.
+ */
+ input[style][type="text"] {}
+ </style>
+ <style>
+ /*
+ For the second stylesheet, when checking the type="" value string,
+ we must not use a reference into the old attributes.
+ */
+ input[style][type="text"] {}
+ </style>
+</head>
+<body>
+ <input id="target" type="text"></input>
+ <script>
+ document.getElementById('target').style.color = 'red';
+ </script>
+</body>
Original Bug Report
heap-use-after-free READ in ElementRuleCollector InputRules path, page load, MiraclePtr unprotected
Steps to reproduce the problem
- Save the attached poc.html to disk
- Open poc.html in Chrome (no flags required, works with file:// protocol)
- Renderer crashes on page load ? no user interaction required
Tested on:
- Chrome 146.0.7680.x stable (Linux x64) ? ASAN build crashes
- Chrome 147.0.7722.0 ? ASAN build crashes
- Current main/ToT vulnerable code unchanged
The PoC requires two <style> elements with input[style][type=“text”] selectors
(creating two RuleSet bundles) and JavaScript that sets an inline style then
forces style resolution via getComputedStyle().
Problem Description
heap-use-after-free READ of size 8 in blink::AtomicString::LowerASCII(), called
from ElementRuleCollector::CollectMatchingRulesInternal() on the InputRules path.
Root cause: element_rule_collector.cc (~line 952) takes a const AtomicString&
reference into Vector<Attribute> heap storage. During CSS rule matching, the
[style] selector triggers SynchronizeAttributeHinted(kStyleAttr), which appends
the lazy style attribute via AppendAttributeInternal(). This grows the vector,
calling ReallocateBuffer() which frees the old buffer. The reference is now
dangling. The next bundle iteration calls input_type.LowerASCII() ? reading
StringImpl* from offset 24 of the freed 32-byte region.
MiraclePtr Status: NOT PROTECTED. No raw_ptr<T> wraps this allocation.
The freed buffer is in PartitionAlloc’s 32-byte bucket. The dangling read
dereferences a StringImpl* at offset 24. If the slot is reclaimed via heap
spray, the attacker controls what LowerASCII() reads, influencing which CSS
InputRules are matched. Additionally, LowerASCII(AtomicString source) receives
source by value ? the copy constructor calls AddRef() on the attacker-controlled
StringImpl*, producing a 4-byte write (refcount increment) at an
attacker-influenced address.
Fix: change const AtomicString& to const AtomicString (value copy) at line ~952.
Additional Comments
Attachments:
- poc.html: minimized 10-line PoC, crashes on page load, no flags
- asan_trace.txt: full symbolized ASAN trace from Chrome 146 stable
- controlled_uaf_demo.cc: standalone C++ demonstrating controlled read/write
- README.md: full root cause analysis with memory layout and exploitability
Summary
heap-use-after-free READ in ElementRuleCollector InputRules path, page load, MiraclePtr unprotected
Custom Questions
Type of crash:
Renderer (tab) crash
Crash state:
ERROR: AddressSanitizer: heap-use-after-free on address 0x7b795a082658
READ of size 8 at 0x7b795a082658 thread T0 (chrome)
Crash stack:
#0 blink::AtomicString::LowerASCII() const
#1 blink::ElementRuleCollector::CollectMatchingRulesInternal<false>()
#2 blink::ScopedStyleResolver::CollectMatchingElementScopeRules()
#3 blink::StyleResolver::MatchAuthorRules()
#4 blink::StyleResolver::MatchAllRules()
#5 blink::StyleResolver::ApplyBaseStyleNoCache()
#6 blink::StyleResolver::ResolveStyle()
#7 blink::Element::OriginalStyleForLayoutObject()
Freed by:
#0 free
#1 blink::Vector<blink::Attribute, 4u>::ReallocateBuffer()
#2 blink::Vector<blink::Attribute, 4u>::ExpandCapacity()
#3 blink::Vector<blink::Attribute, 4u>::AppendSlowCaseblink::Attribute()
#4 blink::MutableAttributeCollection::Append()
#5 blink::Element::AppendAttributeInternal()
#6 blink::Element::SetSynchronizedLazyAttribute()
#7 blink::Element::SynchronizeStyleAttributeInternal() const
#8 blink::Element::SynchronizeAttributeHinted() const
#9 blink::ElementRuleCollector::CollectMatchingRulesForListInternal<false, false>()
0x7b795a082658 is located 24 bytes inside of 32-byte region [0x7b795a082640,0x7b795a082660)
MiraclePtr Status: NOT PROTECTED
No raw_ptr<T> access to this region was detected prior to this crash.
This crash is still exploitable with MiraclePtr.
Full symbolized ASAN trace attached as asan_trace.txt.
Reporter credit:
Anonymous
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \