Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in SVG
DescriptionInappropriate implementation in SVG
ComponentSVG
Bug ClassLogic Error
Tracker517114175
Fix commit6edfe8111eb8 (chromium/src) +44/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc
  • third_party/blink/web_tests/external/wpt/intersection-observer/v2/svg-drop-shadow-occlusion.html
From 6edfe8111eb83953226e4fe540ecb0cc971cb3e3 Mon Sep 17 00:00:00 2001
From: Philip Rogers <pdr@chromium.org>
Date: Wed, 27 May 2026 20:10:32 -0700
Subject: [PATCH] Skip overflow clip for SVG viewport containers doing occlusion tests

This patch skips overflow clips when hit testing SVG viewport
containers for occlusion. This ensures that we consider visual overflow
for these occlusion hit tests.

Fixed: 517114175
Change-Id: I6394e4b6dcbd4535eb70c695f46b98f942f8c39c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7880272
Reviewed-by: Stefan Zager <szager@chromium.org>
Commit-Queue: Philip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1637426}
---

diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc
index 4ae24a3..aabf187f 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.h"
 
 #include "third_party/blink/renderer/core/layout/hit_test_location.h"
+#include "third_party/blink/renderer/core/layout/hit_test_result.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_info.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
 #include "third_party/blink/renderer/core/layout/svg/transform_helper.h"
@@ -95,7 +96,8 @@
     HitTestPhase phase) {
   NOT_DESTROYED();
   // Respect the viewport clip which is in parent coordinates.
-  if (SVGLayoutSupport::IsOverflowHidden(*this)) {
+  if (SVGLayoutSupport::IsOverflowHidden(*this) &&
+      !result.GetHitTestRequest().IsHitTestVisualOverflow()) {
     TransformedHitTestLocation local_transformed_hit_location(
         hit_test_location, LocalSVGTransform());
 
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/v2/svg-drop-shadow-occlusion.html b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/svg-drop-shadow-occlusion.html
new file mode 100644
index 0000000..57ae9fa
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/svg-drop-shadow-occlusion.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #target {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100px;
+    height: 40px;
+    background: lime;
+  }
+  #outer {
+    position: fixed;
+    left: 0;
+    top: 0;
+    width: 300px;
+    height: 100px;
+    visibility: hidden;
+  }
+  #inner {
+    visibility: visible;
+    filter: drop-shadow(-200px 0 0 black);
+  }
+</style>
+<div id="target"></div>
+<svg id="outer">
+  <svg id="inner" x="200" y="0" width="100" height="40">
+    <rect width="100%" height="100%" fill="black"/>
+  </svg>
+</svg>
+
+<script>
+  setup({ single_test: true });
+  const target = document.getElementById("target");
+  new IntersectionObserver(entries => {
+    assert_false(entries[0].isVisible, "Target should be occluded by SVG drop-shadow");
+    done();
+  }, {trackVisibility: true, delay: 100}).observe(target);
+</script>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/v2/svg-drop-shadow-occlusion.html b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/svg-drop-shadow-occlusion.html
new file mode 100644
index 0000000..57ae9fa
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/svg-drop-shadow-occlusion.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #target {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100px;
+    height: 40px;
+    background: lime;
+  }
+  #outer {
+    position: fixed;
+    left: 0;
+    top: 0;
+    width: 300px;
+    height: 100px;
+    visibility: hidden;
+  }
+  #inner {
+    visibility: visible;
+    filter: drop-shadow(-200px 0 0 black);
+  }
+</style>
+<div id="target"></div>
+<svg id="outer">
+  <svg id="inner" x="200" y="0" width="100" height="40">
+    <rect width="100%" height="100%" fill="black"/>
+  </svg>
+</svg>
+
+<script>
+  setup({ single_test: true });
+  const target = document.getElementById("target");
+  new IntersectionObserver(entries => {
+    assert_false(entries[0].isVisible, "Target should be occluded by SVG drop-shadow");
+    done();
+  }, {trackVisibility: true, delay: 100}).observe(target);
+</script>
Loading diff…

Original Bug Report

reported by vm...@google.com

Clickjacking bypass via nested SVG viewport hit-test omission of visual overflow

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 logic flaw in LayoutSVGViewportContainer::NodeAtPoint allows attackers to bypass clickjacking defenses such as IntersectionObserver V2 and Page-Embedded Permission Control (PEPC). When overflow: hidden is active on a nested SVG, hit-test queries outside its viewport are rejected without verifying if they seek visual overflow. Consequently, opaque visual overflow (e.g., from a CSS drop-shadow filter) can obscure target elements while remaining completely invisible to the browser’s occlusion tests.

Affected files:

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

Estimated timestamp from git blame: 2018-09-28

Potential Clickjacking Bypass via Nested SVG Viewport Container

Description of the Issue

In Chromium’s rendering engine (Blink), elements that implement click-gating protections (like the Page-Embedded Permission Control / PEPC elements or IntersectionObserver V2 visibility tracking) utilize occlusion hit-testing to verify that they are not covered by any opaque overlays. This occlusion checking is done via LayoutObject::HitTestForOcclusion (third_party/blink/renderer/core/layout/layout_object.cc), which populates the HitTestRequest with the HitTestRequest::kHitTestVisualOverflow flag.

However, there is a potential logic flaw in how nested SVGs handle hit-testing queries. In third_party/blink/renderer/core/layout/svg/layout_svg_viewport_container.cc, LayoutSVGViewportContainer::NodeAtPoint immediately returns false if overflow: hidden is active on the element and the hit-test location is outside its layout viewport_ rectangle:

bool LayoutSVGViewportContainer::NodeAtPoint(
    HitTestResult& result,
    const HitTestLocation& hit_test_location,
    const PhysicalOffset& accumulated_offset,
    HitTestPhase phase) {
  NOT_DESTROYED();
  // Respect the viewport clip which is in parent coordinates.
  if (SVGLayoutSupport::IsOverflowHidden(*this)) {
    TransformedHitTestLocation local_transformed_hit_location(
        hit_test_location, LocalSVGTransform());

    if (!local_transformed_hit_location ||
        !local_transformed_hit_location->Intersects(viewport_)) {
      return false;
    }
  }
  return LayoutSVGTransformableContainer::NodeAtPoint(
      result, hit_test_location, accumulated_offset, phase);
}

Unlike other layout elements (such as those checked in BoxFragmentPainter::NodeAtPoint), this function fails to check if the incoming HitTestRequest specifies IsHitTestVisualOverflow(). This omission enables visual overflow, such as a CSS filter: drop-shadow, to paint opaque, attacker-controlled pixels outside of the viewport boundary, obscuring a target element while keeping the target visible and active from the browser’s occlusion hit-testing perspective.

Potential Step-by-Step Attack Scenario

Since our tooling does not currently have the capability to run code, the following sequence represents a potential/suggested scenario to trigger the issue:

  1. Setup Target: A sensitive target (such as a <permission type="camera"> element) is placed at coordinates (0,0,200,40) and initiates its clickjacking visibility monitoring via IntersectionObserver and HTMLCapabilityElementBase.
  2. Deploy Nested SVG Overlay: An attacker overlays a nested <svg> container (SVGViewportContainerElement) positioned at x="500" y="0" width="200" height="40" with default overflow: hidden properties.
  3. Apply Visual Filter: The nested SVG container is styled with visibility: visible and filter: drop-shadow(-500px 0 0 black). This renders an opaque copy of the SVG content at offset (0, 0, 200, 40), visually masking the target element under a black box.
  4. Occlusion Check Omission: The browser performs HitTestForOcclusion at the target coordinate (0, 0). When the traversal reaches the nested SVG, LayoutSVGViewportContainer::NodeAtPoint rejects the hit-test coordinate early because (0,0) does not intersect its parent layout viewport_ (500, 0, 200, 40).
  5. State Exploitation: The target element erroneously concludes it is fully visible and clickable. A user clicking the decoy black box instead registers a click on the sensitive permission element, potentially granting camera access.

Suggested Fix

To resolve this issue, LayoutSVGViewportContainer::NodeAtPoint should check if the hit-test request targets visual overflow. If IsHitTestVisualOverflow() is active, the early return for out-of-viewport locations should be bypassed, allowing the hit-test engine to evaluate child and visual filter bounds correctly:

  // Respect the viewport clip which is in parent coordinates.
  if (SVGLayoutSupport::IsOverflowHidden(*this) &&
      !result.GetHitTestRequest().IsHitTestVisualOverflow()) {
    TransformedHitTestLocation local_transformed_hit_location(
        hit_test_location, LocalSVGTransform());

    if (!local_transformed_hit_location ||
        !local_transformed_hit_location->Intersects(viewport_)) {
      return false;
    }
  }

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