Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in SVG
DescriptionInsufficient policy enforcement in SVG
ComponentSVG
Bug ClassLogic Error
Tracker517234388
Fix commit9ca010e55c2c (chromium/src) +138/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • third_party/blink/renderer/core/layout/svg/layout_svg_filter_primitive.cc
  • third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-004.html
  • third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-001.html
  • third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-002.html
  • third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic.html
  • third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fedropshadow-004.html
From 9ca010e55c2c3508416223d1190a28fcf9ec89a1 Mon Sep 17 00:00:00 2001
From: Fredrik Söderquist <fs@opera.com>
Date: Thu, 28 May 2026 06:42:48 -0700
Subject: [PATCH] Fix 'currentcolor' invalidation for SVG filter primitives w/ color

We need to use DependsOnCurrentColor() rather than IsCurrentColor() to
catch cases where a color function may reference 'currentcolor'.

Fixed: 517234388
Change-Id: I7a99ea50c7a3c0cc7dcc04cf8a393536ae480ae6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7879951
Auto-Submit: Fredrik Söderquist <fs@opera.com>
Commit-Queue: Philip Rogers <pdr@chromium.org>
Reviewed-by: Philip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1637663}
---

diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_filter_primitive.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_filter_primitive.cc
index 69046a51..24506793 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_filter_primitive.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_filter_primitive.cc
@@ -41,7 +41,7 @@
     : LayoutObject(filter_primitive_element) {}
 
 static bool CurrentColorChanged(StyleDifference diff, const StyleColor& color) {
-  return diff.text_decoration_or_color_changed && color.IsCurrentColor();
+  return diff.text_decoration_or_color_changed && color.DependsOnCurrentColor();
 }
 
 static void CheckForColorChange(SVGFilterPrimitiveStandardAttributes& element,
@@ -49,10 +49,10 @@
                                 StyleDifference diff,
                                 const StyleColor& old_color,
                                 const StyleColor& new_color) {
-  // If the <color> change from/to 'currentcolor' then invalidate the filter
-  // chain so that it is rebuilt. (Makes sure the 'tainted' flag is
+  // If the <color> change from/to depending on 'currentcolor' then invalidate
+  // the filter chain so that it is rebuilt. (Makes sure the 'tainted' flag is
   // propagated.)
-  if (new_color.IsCurrentColor() != old_color.IsCurrentColor()) {
+  if (new_color.DependsOnCurrentColor() != old_color.DependsOnCurrentColor()) {
     element.Invalidate();
     return;
   }
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-004.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-004.html
new file mode 100644
index 0000000..de537683
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-004.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>feDiffuseLighting: 'currentcolor' lighting-color taints the primitive</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<svg>
+  <filter id="diffuse_currentcolor" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood/>
+    <feDiffuseLighting lighting-color="color-mix(in srgb, currentcolor 99.9%, black)"
+                       style="color: rgb(0%, 100%, 50%)">
+      <feDistantLight elevation="90"/>
+    </feDiffuseLighting>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green"
+        filter="url(#diffuse_currentcolor)"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-001.html
new file mode 100644
index 0000000..8967409
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>feDiffuseLighting: tainting state changes dynamically</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<script src="/common/reftest-wait.js"></script>
+<svg>
+  <filter id="diffuse" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood/>
+    <feDiffuseLighting lighting-color="rgb(0%, 100%, 50%)"
+                       style="color: rgb(0%, 100%, 50%)">
+      <feDistantLight elevation="90"/>
+    </feDiffuseLighting>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100" x="0" y="0" width="100" height="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green" filter="url(#diffuse)"/>
+</svg>
+<script>
+  requestAnimationFrame(() => {
+    requestAnimationFrame(() => {
+      document.querySelector('feDiffuseLighting').style.lightingColor = 'currentcolor';
+      takeScreenshot();
+    });
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-002.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-002.html
new file mode 100644
index 0000000..d5c75d0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-002.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>feDiffuseLighting: tainting state changes dynamically</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<script src="/common/reftest-wait.js"></script>
+<svg>
+  <filter id="diffuse" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood/>
+    <feDiffuseLighting lighting-color="rgb(0%, 100%, 50%)"
+                       style="color: rgb(0%, 100%, 50%)">
+      <feDistantLight elevation="90"/>
+    </feDiffuseLighting>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100" x="0" y="0" width="100" height="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green" filter="url(#diffuse)"/>
+</svg>
+<script>
+  requestAnimationFrame(() => {
+    requestAnimationFrame(() => {
+      document.querySelector('feDiffuseLighting').style.lightingColor = 'color-mix(in srgb, currentcolor 99.9%, black)';
+      takeScreenshot();
+    });
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic.html
deleted file mode 100644
index 8967409..0000000
--- a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<title>feDiffuseLighting: tainting state changes dynamically</title>
-<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
-<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
-<link rel="match" href="reference/green-100x100.html">
-<script src="/common/reftest-wait.js"></script>
-<svg>
-  <filter id="diffuse" color-interpolation-filters="sRGB"
-          filterUnits="userSpaceOnUse">
-    <feFlood/>
-    <feDiffuseLighting lighting-color="rgb(0%, 100%, 50%)"
-                       style="color: rgb(0%, 100%, 50%)">
-      <feDistantLight elevation="90"/>
-    </feDiffuseLighting>
-    <feDisplacementMap in="SourceGraphic"
-                       xChannelSelector="G" yChannelSelector="B"
-                       scale="100" x="0" y="0" width="100" height="100"/>
-  </filter>
-  <rect width="100" height="100" fill="red"/>
-  <rect width="100" height="100" fill="green" filter="url(#diffuse)"/>
-</svg>
-<script>
-  requestAnimationFrame(() => {
-    requestAnimationFrame(() => {
-      document.querySelector('feDiffuseLighting').style.lightingColor = 'currentcolor';
-      takeScreenshot();
-    });
-  });
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fedropshadow-004.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fedropshadow-004.html
new file mode 100644
index 0000000..eca97d92
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fedropshadow-004.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>feDropShadow: 'currentcolor' flood-color taints the primitive</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<svg>
+  <filter id="dropshdw_currentcolor" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood x="0" y="0" width="100" height="100"/>
+    <feDropShadow width="100%" flood-color="color-mix(in srgb, currentcolor 99.9%, black)"
+                  stdDeviation="0" dx="100" dy="0" style="color: rgb(0%, 100%, 50%)"/>
+    <feOffset dx="-100"/>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100" x="0" y="0" width="100" height="100"/>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-004.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-004.html
new file mode 100644
index 0000000..de537683
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-004.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>feDiffuseLighting: 'currentcolor' lighting-color taints the primitive</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<svg>
+  <filter id="diffuse_currentcolor" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood/>
+    <feDiffuseLighting lighting-color="color-mix(in srgb, currentcolor 99.9%, black)"
+                       style="color: rgb(0%, 100%, 50%)">
+      <feDistantLight elevation="90"/>
+    </feDiffuseLighting>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green"
+        filter="url(#diffuse_currentcolor)"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-001.html
new file mode 100644
index 0000000..8967409
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>feDiffuseLighting: tainting state changes dynamically</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<script src="/common/reftest-wait.js"></script>
+<svg>
+  <filter id="diffuse" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood/>
+    <feDiffuseLighting lighting-color="rgb(0%, 100%, 50%)"
+                       style="color: rgb(0%, 100%, 50%)">
+      <feDistantLight elevation="90"/>
+    </feDiffuseLighting>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100" x="0" y="0" width="100" height="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green" filter="url(#diffuse)"/>
+</svg>
+<script>
+  requestAnimationFrame(() => {
+    requestAnimationFrame(() => {
+      document.querySelector('feDiffuseLighting').style.lightingColor = 'currentcolor';
+      takeScreenshot();
+    });
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-002.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-002.html
new file mode 100644
index 0000000..d5c75d0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic-002.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>feDiffuseLighting: tainting state changes dynamically</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<script src="/common/reftest-wait.js"></script>
+<svg>
+  <filter id="diffuse" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood/>
+    <feDiffuseLighting lighting-color="rgb(0%, 100%, 50%)"
+                       style="color: rgb(0%, 100%, 50%)">
+      <feDistantLight elevation="90"/>
+    </feDiffuseLighting>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100" x="0" y="0" width="100" height="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green" filter="url(#diffuse)"/>
+</svg>
+<script>
+  requestAnimationFrame(() => {
+    requestAnimationFrame(() => {
+      document.querySelector('feDiffuseLighting').style.lightingColor = 'color-mix(in srgb, currentcolor 99.9%, black)';
+      takeScreenshot();
+    });
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic.html
deleted file mode 100644
index 8967409..0000000
--- a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fediffuselighting-dynamic.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<title>feDiffuseLighting: tainting state changes dynamically</title>
-<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
-<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
-<link rel="match" href="reference/green-100x100.html">
-<script src="/common/reftest-wait.js"></script>
-<svg>
-  <filter id="diffuse" color-interpolation-filters="sRGB"
-          filterUnits="userSpaceOnUse">
-    <feFlood/>
-    <feDiffuseLighting lighting-color="rgb(0%, 100%, 50%)"
-                       style="color: rgb(0%, 100%, 50%)">
-      <feDistantLight elevation="90"/>
-    </feDiffuseLighting>
-    <feDisplacementMap in="SourceGraphic"
-                       xChannelSelector="G" yChannelSelector="B"
-                       scale="100" x="0" y="0" width="100" height="100"/>
-  </filter>
-  <rect width="100" height="100" fill="red"/>
-  <rect width="100" height="100" fill="green" filter="url(#diffuse)"/>
-</svg>
-<script>
-  requestAnimationFrame(() => {
-    requestAnimationFrame(() => {
-      document.querySelector('feDiffuseLighting').style.lightingColor = 'currentcolor';
-      takeScreenshot();
-    });
-  });
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fedropshadow-004.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fedropshadow-004.html
new file mode 100644
index 0000000..eca97d92
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fedropshadow-004.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>feDropShadow: 'currentcolor' flood-color taints the primitive</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<svg>
+  <filter id="dropshdw_currentcolor" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood x="0" y="0" width="100" height="100"/>
+    <feDropShadow width="100%" flood-color="color-mix(in srgb, currentcolor 99.9%, black)"
+                  stdDeviation="0" dx="100" dy="0" style="color: rgb(0%, 100%, 50%)"/>
+    <feOffset dx="-100"/>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100" x="0" y="0" width="100" height="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green"
+        filter="url(#dropshdw_currentcolor)"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-003.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-003.html
new file mode 100644
index 0000000..fa03c83
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-003.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>feFlood: color-mix(currentcolor) flood-color taints the primitive</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<svg>
+  <filter id="flood_currentcolor" color-interpolation-filters="sRGB">
+    <feFlood flood-color="color-mix(in srgb, currentcolor 99.9%, black)"
+             style="color: rgb(0%, 100%, 50%)"/>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green"
+        filter="url(#flood_currentcolor)"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic-001.html
new file mode 100644
index 0000000..fc9b335
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic-001.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>feFlood: tainting state changes dynamically</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<script src="/common/reftest-wait.js"></script>
+<svg>
+  <filter id="flood" color-interpolation-filters="sRGB">
+    <feFlood flood-color="rgb(0%, 100%, 50%)" style="color: rgb(0%, 100%, 50%)"/>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green" filter="url(#flood)"/>
+</svg>
+<script>
+  requestAnimationFrame(() => {
+    requestAnimationFrame(() => {
+      document.querySelector('feFlood').style.floodColor = 'currentcolor';
+      takeScreenshot();
+    });
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic-002.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic-002.html
new file mode 100644
index 0000000..421c5a6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic-002.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>feFlood: tainting state changes dynamically</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<script src="/common/reftest-wait.js"></script>
+<svg>
+  <filter id="flood" color-interpolation-filters="sRGB">
+    <feFlood flood-color="rgb(0%, 100%, 50%)" style="color: rgb(0%, 100%, 50%)"/>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100"/>
+  </filter>
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green" filter="url(#flood)"/>
+</svg>
+<script>
+  requestAnimationFrame(() => {
+    requestAnimationFrame(() => {
+      document.querySelector('feFlood').style.floodColor = 'color-mix(in srgb, currentcolor 99.9%, black)';
+      takeScreenshot();
+    });
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic.html
deleted file mode 100644
index fc9b335..0000000
--- a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-feflood-dynamic.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<title>feFlood: tainting state changes dynamically</title>
-<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
-<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
-<link rel="match" href="reference/green-100x100.html">
-<script src="/common/reftest-wait.js"></script>
-<svg>
-  <filter id="flood" color-interpolation-filters="sRGB">
-    <feFlood flood-color="rgb(0%, 100%, 50%)" style="color: rgb(0%, 100%, 50%)"/>
-    <feDisplacementMap in="SourceGraphic"
-                       xChannelSelector="G" yChannelSelector="B"
-                       scale="100"/>
-  </filter>
-  <rect width="100" height="100" fill="red"/>
-  <rect width="100" height="100" fill="green" filter="url(#flood)"/>
-</svg>
-<script>
-  requestAnimationFrame(() => {
-    requestAnimationFrame(() => {
-      document.querySelector('feFlood').style.floodColor = 'currentcolor';
-      takeScreenshot();
-    });
-  });
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fespecularlighting-004.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fespecularlighting-004.html
new file mode 100644
index 0000000..fef3d74
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/tainting-fespecularlighting-004.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>feSpecularLighting: 'currentcolor' lighting-color taints the primitive</title>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#tainted-filter-primitives">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#fedisplacemnentmap-restrictions">
+<link rel="match" href="reference/green-100x100.html">
+<svg>
+  <filter id="specular_currentcolor" color-interpolation-filters="sRGB"
+          filterUnits="userSpaceOnUse">
+    <feFlood/>
+    <feSpecularLighting lighting-color="color-mix(in srgb, currentcolor 99.9%, black)"
+                        style="color: rgb(0%, 100%, 50%)">
+      <feDistantLight elevation="90"/>
+    </feSpecularLighting>
+    <feDisplacementMap in="SourceGraphic"
+                       xChannelSelector="G" yChannelSelector="B"
+                       scale="100"/>
+  </filter>
... (truncated)
Loading diff…

Original Bug Report

reported by vm...@google.com

History leak: SVG filter fast-path skips taint propagation for unresolved color functions

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential vulnerability in Blink’s SVG filter invalidation logic allows visited-dependent color changes via unresolved color functions to bypass origin-taint checks. This occurs because transitions between absolute colors and unresolved color functions are handled via an in-place mutation fast path, which fails to re-evaluate the filter’s origin-taint status. Consequently, an attacker could potentially render visited-dependent SVG filters onto a canvas element and extract user browsing history via standard pixel readback APIs.

Affected files:

  • third_party/blink/renderer/core/layout/svg/layout_svg_filter_primitive.cc

Estimated timestamp from git blame: 2019-03-07

Root Cause Analysis

In third_party/blink/renderer/core/layout/svg/layout_svg_filter_primitive.cc, the helper function CheckForColorChange is designed to determine if a dynamic change in an SVG filter primitive’s color requires a full filter-chain rebuild (which propagates the origin_tainted_ flag) or if it can safely take a fast path via PrimitiveAttributeChanged:

static void CheckForColorChange(SVGFilterPrimitiveStandardAttributes& element,
                                const QualifiedName& attr_name,
                                StyleDifference diff,
                                const StyleColor& old_color,
                                const StyleColor& new_color) {
  // If the <color> change from/to 'currentcolor' then invalidate the filter
  // chain so that it is rebuilt. (Makes sure the 'tainted' flag is propagated.)
  if (new_color.IsCurrentColor() != old_color.IsCurrentColor()) {
    element.Invalidate();
    return;
  }
  if (new_color != old_color || CurrentColorChanged(diff, new_color))
    element.PrimitiveAttributeChanged(attr_name);
}

The issue is that StyleColor::IsCurrentColor() only returns true for the literal currentcolor keyword. However, unresolved color functions (e.g., color-mix(in srgb, currentColor 99%, black)) also depend on currentColor (making StyleColor::DependsOnCurrentColor() return true) but return false for IsCurrentColor() (see third_party/blink/renderer/core/css/style_color.h line 253).

As a result, a transition from an absolute color (like red) to an unresolved color function depending on currentColor evaluates new_color.IsCurrentColor() != old_color.IsCurrentColor() as false != false (which is false), bypassing the full invalidation branch (element.Invalidate()). Instead, it executes PrimitiveAttributeChanged, which performs an in-place mutation of the existing filter effect’s color attribute without rebuilding the filter chain or re-evaluating TaintsOrigin(). This leaves the cached FilterData with a stale OriginTainted() == false flag.

Potential Impact and Bypass Mechanism

Because the cached FilterData retains a stale OriginTainted() == false status, it successfully bypasses both security gates designed to prevent visited-color exposure onto a canvas subtree:

  1. Canvas Subtree Filter Disallow Gate in paint_property_tree_builder.cc (line 2422) where filter_info.operations.OriginTainted() is checked.
  2. Privacy-Preserving Paint Gate in scoped_svg_paint_state.cc (line 147) where filter->Filter()->OriginTainted() is checked under PaintFlag::kPrivacyPreserving.

Suggested Attack Steps

The following are potential steps an attacker might follow to trigger this vulnerability (note that our tooling agent does not currently have the ability to execute code, so these are analytical steps):

  1. Create a page with a target visited/unvisited link <a> and style its visited and unvisited states with different colors.
  2. Define an SVG filter containing a primitive such as <feFlood> or <feDropShadow> initially styled with an absolute color (e.g., flood-color: red;).
  3. Cause the layout engine to render the filter onto an element (like a <rect>) positioned inside a canvas subtree, establishing an initial cached FilterData with OriginTainted() == false.
  4. Dynamically update the primitive’s style to an unresolved color function referencing currentColor (e.g., flood-color: color-mix(in srgb, currentColor 99%, black);), which inherits from the link.
  5. The fast path CheckForColorChange mutates the color in-place based on the visited state of the link but fails to mark the filter’s operations as origin-tainted.
  6. Paint the element onto a canvas. Since the filter is not stripped due to the stale taint status, the visited-dependent filtered pixels are drawn.
  7. Call canvasContext.getImageData() to read the pixel values and determine if the link has been visited.

Suggested Fix

To fix this issue, CheckForColorChange should check if either the old or new color depends on currentColor by using DependsOnCurrentColor() rather than IsCurrentColor():

static void CheckForColorChange(SVGFilterPrimitiveStandardAttributes& element,
                                const QualifiedName& attr_name,
                                StyleDifference diff,
                                const StyleColor& old_color,
                                const StyleColor& new_color) {
  // If the <color> dependency on 'currentcolor' changes, then invalidate the filter
  // chain so that it is rebuilt. (Makes sure the 'tainted' flag is propagated.)
  if (new_color.DependsOnCurrentColor() != old_color.DependsOnCurrentColor()) {
    element.Invalidate();
    return;
  }
  if (new_color != old_color || CurrentColorChanged(diff, new_color))
    element.PrimitiveAttributeChanged(attr_name);
}

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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