Chrome · CSS
CVE-2026-14098
Logic Error in CSS
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
third_party/blink/renderer/core/style/computed_style.ccthird_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-alpha-ref.htmlthird_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-fill-alpha.htmlthird_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-stroke-alpha.html
Patch
From bb0351bff33b0883d305f03ba1d83b814290481d Mon Sep 17 00:00:00 2001 From: Divyansh Mangal <[email protected]> Date: Thu, 21 May 2026 07:11:55 -0700 Subject: [PATCH] Clamp :visited alpha in VisitedDependentContextPaint `VisitedDependentContextPaint()` returned the `:visited` color with its own alpha channel, bypassing the history-sniffing mitigation that requires `:visited` styles to never change element transparency. The standard resolver `VisitedDependentColor()` correctly constructs the return value using visited RGB with the unvisited alpha. This CL makes `VisitedDependentContextPaint()` apply the same alpha clamping for both the forced-colors and normal code paths. Bug: 513375767 Change-Id: I6014f1d11821be93f92addd5d35c6e7c40626641 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7859132 Reviewed-by: Rune Lillesveen <[email protected]> Commit-Queue: Divyansh Mangal <[email protected]> Reviewed-by: Virali Purbey <[email protected]> Cr-Commit-Position: refs/heads/main@{#1634245} --- diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc index 8929685..1e808218 100644 --- a/third_party/blink/renderer/core/style/computed_style.cc +++ b/third_party/blink/renderer/core/style/computed_style.cc @@ -2639,11 +2639,20 @@ if (!context_visited_paint.HasColor()) { return unvisited_color; } + blink::Color visited_color; if (ShouldForceColor(context_visited_paint.GetColor())) { - return GetInternalForcedVisitedCurrentColor(nullptr); + visited_color = GetInternalForcedVisitedCurrentColor(nullptr); + } else { + visited_color = context_visited_paint.GetColor().Resolve( + GetInternalVisitedCurrentColor(), UsedColorScheme(), nullptr); } - return context_visited_paint.GetColor().Resolve( - GetInternalVisitedCurrentColor(), UsedColorScheme(), nullptr); + // Take the RGB from the visited color, but clamp alpha to the unvisited + // color's alpha. This prevents :visited from changing transparency, which + // would allow history sniffing via pixel-based side channels. + return Color::FromColorSpace(visited_color.GetColorSpace(), + visited_color.Param0(), visited_color.Param1(), + visited_color.Param2(), + unvisited_color.Alpha()); } blink::Color ComputedStyle::ResolvedColor(const StyleColor& color, diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-alpha-ref.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-alpha-ref.html new file mode 100644 index 0000000..a0b91a16 --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-alpha-ref.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<style>body { margin: 0; }</style> +<div style="width: 100px; height: 100px; background: rgb(0, 128, 0);"></div> diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-fill-alpha.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-fill-alpha.html new file mode 100644 index 0000000..7e9ff8e --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-fill-alpha.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>:visited cannot change alpha of SVG context-fill</title> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#link"> +<link rel="help" href="https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint"> +<link rel="match" href="visited-link-context-alpha-ref.html"> +<!-- +Test that :visited styles cannot change the alpha of SVG context-fill. +The :visited privacy mitigation requires that the alpha channel of a +resolved :visited color is clamped to the unvisited alpha. + +If this test passes, one should see a 100x100 green square. + +If the bug is present, the square would be transparent (showing the +white background) because the visited alpha (0) leaked through context-fill. +--> +<style>body { margin: 0; }</style> +<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <style> + a .ctx { fill: rgba(0, 128, 0, 1); } + a:visited .ctx { fill: rgba(0, 128, 0, 0); } + </style> + <defs> + <symbol id="sq"> + <rect width="100" height="100" fill="context-fill"/> + </symbol> + </defs> + <a href=""> + <use class="ctx" href="#sq"/> + </a> +</svg> diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-stroke-alpha.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-stroke-alpha.html new file mode 100644 index 0000000..6a20516 --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-stroke-alpha.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>:visited cannot change alpha of SVG context-stroke</title> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#link"> +<link rel="help" href="https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint"> +<link rel="match" href="visited-link-context-alpha-ref.html"> +<!-- +Test that :visited styles cannot change the alpha of SVG context-stroke. +The :visited privacy mitigation requires that the alpha channel of a +resolved :visited color is clamped to the unvisited alpha. + +If this test passes, one should see a 100x100 green square. + +If the bug is present, the square would be transparent (showing the +white background) because the visited alpha (0) leaked through context-stroke. +--> +<style>body { margin: 0; }</style> +<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <style> + a .ctx { stroke: rgba(0, 128, 0, 1); } + a:visited .ctx { stroke: rgba(0, 128, 0, 0); } + </style> + <defs> + <symbol id="sq"> + <line x1="50" y1="0" x2="50" y2="100" + fill="none" stroke="context-stroke" stroke-width="100"/> + </symbol> + </defs> + <a href=""> + <use class="ctx" href="#sq"/> + </a> +</svg>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-alpha-ref.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-alpha-ref.html
new file mode 100644
index 0000000..a0b91a16
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-alpha-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<style>body { margin: 0; }</style>
+<div style="width: 100px; height: 100px; background: rgb(0, 128, 0);"></div>
diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-fill-alpha.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-fill-alpha.html
new file mode 100644
index 0000000..7e9ff8e
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-fill-alpha.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>:visited cannot change alpha of SVG context-fill</title>
+<link rel="help" href="https://drafts.csswg.org/selectors-4/#link">
+<link rel="help" href="https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint">
+<link rel="match" href="visited-link-context-alpha-ref.html">
+<!--
+Test that :visited styles cannot change the alpha of SVG context-fill.
+The :visited privacy mitigation requires that the alpha channel of a
+resolved :visited color is clamped to the unvisited alpha.
+
+If this test passes, one should see a 100x100 green square.
+
+If the bug is present, the square would be transparent (showing the
+white background) because the visited alpha (0) leaked through context-fill.
+-->
+<style>body { margin: 0; }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+ <style>
+ a .ctx { fill: rgba(0, 128, 0, 1); }
+ a:visited .ctx { fill: rgba(0, 128, 0, 0); }
+ </style>
+ <defs>
+ <symbol id="sq">
+ <rect width="100" height="100" fill="context-fill"/>
+ </symbol>
+ </defs>
+ <a href="">
+ <use class="ctx" href="#sq"/>
+ </a>
+</svg>
diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-stroke-alpha.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-stroke-alpha.html
new file mode 100644
index 0000000..6a20516
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-context-stroke-alpha.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>:visited cannot change alpha of SVG context-stroke</title>
+<link rel="help" href="https://drafts.csswg.org/selectors-4/#link">
+<link rel="help" href="https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint">
+<link rel="match" href="visited-link-context-alpha-ref.html">
+<!--
+Test that :visited styles cannot change the alpha of SVG context-stroke.
+The :visited privacy mitigation requires that the alpha channel of a
+resolved :visited color is clamped to the unvisited alpha.
+
+If this test passes, one should see a 100x100 green square.
+
+If the bug is present, the square would be transparent (showing the
+white background) because the visited alpha (0) leaked through context-stroke.
+-->
+<style>body { margin: 0; }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+ <style>
+ a .ctx { stroke: rgba(0, 128, 0, 1); }
+ a:visited .ctx { stroke: rgba(0, 128, 0, 0); }
+ </style>
+ <defs>
+ <symbol id="sq">
+ <line x1="50" y1="0" x2="50" y2="100"
+ fill="none" stroke="context-stroke" stroke-width="100"/>
+ </symbol>
+ </defs>
+ <a href="">
+ <use class="ctx" href="#sq"/>
+ </a>
+</svg>
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