Chrome · SVG
CVE-2026-17963
Logic Error in SVG
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
third_party/blink/renderer/core/css/properties/longhands/longhands_custom.ccthird_party/blink/web_tests/wpt_internal/svg/painting/visited-link-flood-color-alpha.htmlthird_party/blink/web_tests/wpt_internal/svg/painting/visited-link-stop-color-alpha.html
Patch
From 64e9fccd20038c43e6976cd2398612e1b8caca4a Mon Sep 17 00:00:00 2001 From: Divyansh Mangal <[email protected]> Date: Mon, 08 Jun 2026 23:17:57 -0700 Subject: [PATCH] Fix :visited alpha clamp bypass in SVG stop-color, flood-color, lighting-color, and -webkit-tap-highlight-color `StopColor::ColorIncludingFallback`, `FloodColor::ColorIncludingFallback`, `LightingColor::ColorIncludingFallback`, and `WebkitTapHighlightColor::ColorIncludingFallback` ignored their `visited_link` parameter and delegated to `ComputedStyle::ResolvedColor()`, which dynamically queries `InsideLink()` to select the current color basis. Inside a visited link, both the `visited_link=false` and `visited_link=true` calls in `VisitedDependentColor()` resolved to the visited color, making the unvisited-alpha clamp a no-op. This allowed `:visited` styles to change the rendered alpha of SVG gradient stops and flood fills, leaking one bit of browsing history into visible paint. In this CL we fix this by resolving `currentColor` explicitly based on the `visited_link` parameter in each override, bypassing `ResolvedColor()`. Bug: 517759257 Change-Id: I9fed29e8d063d4117ed459ec8f60fb80b40de2ed Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7893575 Reviewed-by: Stefan Zager <[email protected]> Reviewed-by: Vinay Singh <[email protected]> Commit-Queue: Divyansh Mangal <[email protected]> Cr-Commit-Position: refs/heads/main@{#1643718} --- diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc index 62ffc67..c99dabe 100644 --- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc +++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc @@ -4431,7 +4431,11 @@ if (style.ShouldForceColor(flood_color)) { return style.GetInternalForcedCurrentColor(is_current_color); } - return style.ResolvedColor(flood_color, is_current_color); + blink::Color current_color = visited_link + ? style.GetInternalVisitedCurrentColor() + : style.GetCurrentColor(); + return flood_color.Resolve(current_color, style.UsedColorScheme(), + is_current_color); } const CSSValue* FloodColor::CSSValueFromComputedStyleInternal( @@ -6366,7 +6370,11 @@ if (style.ShouldForceColor(lighting_color)) { return style.GetInternalForcedCurrentColor(is_current_color); } - return style.ResolvedColor(lighting_color, is_current_color); + blink::Color current_color = visited_link + ? style.GetInternalVisitedCurrentColor() + : style.GetCurrentColor(); + return lighting_color.Resolve(current_color, style.UsedColorScheme(), + is_current_color); } const CSSValue* LightingColor::CSSValueFromComputedStyleInternal( @@ -9427,7 +9435,11 @@ if (style.ShouldForceColor(stop_color)) { return style.GetInternalForcedCurrentColor(is_current_color); } - return style.ResolvedColor(stop_color, is_current_color); + blink::Color current_color = visited_link + ? style.GetInternalVisitedCurrentColor() + : style.GetCurrentColor(); + return stop_color.Resolve(current_color, style.UsedColorScheme(), + is_current_color); } const CSSValue* StopColor::CSSValueFromComputedStyleInternal( @@ -11577,7 +11589,11 @@ ? style.GetInternalForcedVisitedCurrentColor(is_current_color) : style.GetInternalForcedCurrentColor(is_current_color); } - return style.ResolvedColor(style.TapHighlightColor(), is_current_color); + blink::Color current_color = visited_link + ? style.GetInternalVisitedCurrentColor() + : style.GetCurrentColor(); + return highlight_color.Resolve(current_color, style.UsedColorScheme(), + is_current_color); } const CSSValue* WebkitTapHighlightColor::CSSValueFromComputedStyleInternal( diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-flood-color-alpha.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-flood-color-alpha.html new file mode 100644 index 0000000..2225be3 --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-flood-color-alpha.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>:visited cannot change alpha of SVG flood-color</title> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#link"> +<link rel="help" href="https://drafts.fxtf.org/filter-effects/#FloodColorProperty"> +<link rel="match" href="visited-link-context-alpha-ref.html"> +<!-- +Test that :visited styles cannot change the alpha of SVG flood-color +when it resolves currentColor. +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 +flood-color: currentColor. +--> +<style> + body { margin: 0; } + a:link { color: rgba(0, 128, 0, 1); } + a:visited { color: rgba(0, 128, 0, 0); } +</style> +<a href=""> + <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <defs> + <filter id="f" x="0" y="0" width="100%" height="100%"> + <feFlood flood-color="currentColor" result="flood"/> + </filter> + </defs> + <rect width="100" height="100" filter="url(#f)"/> + </svg> +</a> diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-stop-color-alpha.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-stop-color-alpha.html new file mode 100644 index 0000000..29921bd --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-stop-color-alpha.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>:visited cannot change alpha of SVG stop-color</title> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#link"> +<link rel="help" href="https://svgwg.org/svg2-draft/painting.html#StopColorProperty"> +<link rel="match" href="visited-link-context-alpha-ref.html"> +<!-- +Test that :visited styles cannot change the alpha of SVG stop-color +when it resolves currentColor. +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 +stop-color: currentColor. +--> +<style> + body { margin: 0;} + a:link { color: rgba(0, 128, 0, 1); } + a:visited { color: rgba(0, 128, 0, 0); } +</style> +<a href=""> + <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <defs> + <linearGradient id="g"> + <stop offset="0" stop-color="currentColor"/> + <stop offset="1" stop-color="currentColor"/> + </linearGradient> + </defs> + <rect width="100" height="100" fill="url(#g)"/> + </svg> +</a>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-flood-color-alpha.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-flood-color-alpha.html
new file mode 100644
index 0000000..2225be3
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-flood-color-alpha.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>:visited cannot change alpha of SVG flood-color</title>
+<link rel="help" href="https://drafts.csswg.org/selectors-4/#link">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#FloodColorProperty">
+<link rel="match" href="visited-link-context-alpha-ref.html">
+<!--
+Test that :visited styles cannot change the alpha of SVG flood-color
+when it resolves currentColor.
+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
+flood-color: currentColor.
+-->
+<style>
+ body { margin: 0; }
+ a:link { color: rgba(0, 128, 0, 1); }
+ a:visited { color: rgba(0, 128, 0, 0); }
+</style>
+<a href="">
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+ <defs>
+ <filter id="f" x="0" y="0" width="100%" height="100%">
+ <feFlood flood-color="currentColor" result="flood"/>
+ </filter>
+ </defs>
+ <rect width="100" height="100" filter="url(#f)"/>
+ </svg>
+</a>
diff --git a/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-stop-color-alpha.html b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-stop-color-alpha.html
new file mode 100644
index 0000000..29921bd
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/svg/painting/visited-link-stop-color-alpha.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>:visited cannot change alpha of SVG stop-color</title>
+<link rel="help" href="https://drafts.csswg.org/selectors-4/#link">
+<link rel="help" href="https://svgwg.org/svg2-draft/painting.html#StopColorProperty">
+<link rel="match" href="visited-link-context-alpha-ref.html">
+<!--
+Test that :visited styles cannot change the alpha of SVG stop-color
+when it resolves currentColor.
+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
+stop-color: currentColor.
+-->
+<style>
+ body { margin: 0;}
+ a:link { color: rgba(0, 128, 0, 1); }
+ a:visited { color: rgba(0, 128, 0, 0); }
+</style>
+<a href="">
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+ <defs>
+ <linearGradient id="g">
+ <stop offset="0" stop-color="currentColor"/>
+ <stop offset="1" stop-color="currentColor"/>
+ </linearGradient>
+ </defs>
+ <rect width="100" height="100" fill="url(#g)"/>
+ </svg>
+</a>
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