Chrome · Geometry
CVE-2026-87458
Logic Error in Geometry
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fthird_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc |
modified |
Files Changed
third_party/blink/renderer/core/intersection_observer/intersection_observer_test.ccthird_party/blink/renderer/core/paint/paint_layer.cc
Patch
From a0ceaf275d46280067394f9f8e61c4a90df61f6f Mon Sep 17 00:00:00 2001 From: Stefan Zager <[email protected]> Date: Tue, 28 Jul 2026 17:45:46 -0700 Subject: [PATCH] Fix hit testing depth order when testing children This CL: https://chromium-review.googlesource.com/c/chromium/src/+/7442380 ... missed one location for using container_transform_state instead of local_transform_state. Bug: 464173566,517072005 Change-Id: Ia33ad813f3f89b35562f0fcd3c154675c2801163 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8128190 Reviewed-by: Philip Rogers <[email protected]> Commit-Queue: Philip Rogers <[email protected]> Cr-Commit-Position: refs/heads/main@{#1669908} --- diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc b/third_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc index c7f4d5a..339a52f 100644 --- a/third_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc +++ b/third_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc @@ -2871,6 +2871,71 @@ EXPECT_FALSE(observer_delegate->LastEntry()->isVisible()); } +TEST_F(IntersectionObserverV2Test, Preserve3DOcclusion) { + WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600)); + SimRequest main_resource("https://example.com/", "text/html"); + LoadURL("https://example.com/"); + main_resource.Complete(R"HTML( + <style> + #GP { + transform: translateX(0); + transform-style: preserve-3d; + } + #TC { + position: absolute; + width: 1px; height: 1px; + transform: translateZ(5px); + transform-style: preserve-3d; + } + #L { + position: absolute; + width: 1px; height: 1px; + isolation: isolate; + } + #occluder, #target { + position: absolute; + left: 100px; top: 100px; + width: 100; height: 100; + } + </style> + <div id="GP"> + <div id="TC"> + <div> + <div id="L"> + <div id="occluder"></div> + </div> + </div> + </div> + <div id="target"></div> + </div> + )HTML"); + Compositor().BeginFrame(); + + IntersectionObserverInit* observer_init = IntersectionObserverInit::Create(); + observer_init->setTrackVisibility(true); + observer_init->setDelay(100); + DummyExceptionStateForTesting exception_state; + TestIntersectionObserverDelegate* observer_delegate = + MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument()); + IntersectionObserver* observer = IntersectionObserver::Create( + observer_init, *observer_delegate, + LocalFrameUkmAggregator::kJavascriptIntersectionObserver, + exception_state); + ASSERT_FALSE(exception_state.HadException()); + Element* target = GetDocument().getElementById(AtomicString("target")); + ASSERT_TRUE(target); + observer->observe(target); + + Compositor().BeginFrame(); + test::RunPendingTasks(); + ASSERT_FALSE(Compositor().NeedsBeginFrame()); + EXPECT_EQ(observer_delegate->CallCount(), 1); + EXPECT_EQ(observer_delegate->EntryCount(), 1); + EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting()); + // #target is occluded by #occluder which is in front of it (z=5 vs z=0). + EXPECT_FALSE(observer_delegate->LastEntry()->isVisible()); +} + TEST_F(IntersectionObserverV2Test, TableCellOcclusion) { WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600)); SimRequest main_resource("https://example.com/", "text/html"); diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc index 3b4d36c..597eada 100644 --- a/third_party/blink/renderer/core/paint/paint_layer.cc +++ b/third_party/blink/renderer/core/paint/paint_layer.cc @@ -1976,7 +1976,11 @@ } if (IsHitCandidateForDepthOrder( - hit_layer, depth_sort_descendants, z_offset, local_transform_state, + hit_layer, depth_sort_descendants, z_offset, + RuntimeEnabledFeatures:: + HitTestContainerTransformStateForPreserve3dEnabled() + ? container_transform_state + : local_transform_state, result.GetHitTestRequest().IsHitTestVisualOverflow())) { result_layer = hit_layer; if (!result.GetHitTestRequest().ListBased())
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc b/third_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc
index c7f4d5a..339a52f 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_observer_test.cc
@@ -2871,6 +2871,71 @@
EXPECT_FALSE(observer_delegate->LastEntry()->isVisible());
}
+TEST_F(IntersectionObserverV2Test, Preserve3DOcclusion) {
+ WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
+ SimRequest main_resource("https://example.com/", "text/html");
+ LoadURL("https://example.com/");
+ main_resource.Complete(R"HTML(
+ <style>
+ #GP {
+ transform: translateX(0);
+ transform-style: preserve-3d;
+ }
+ #TC {
+ position: absolute;
+ width: 1px; height: 1px;
+ transform: translateZ(5px);
+ transform-style: preserve-3d;
+ }
+ #L {
+ position: absolute;
+ width: 1px; height: 1px;
+ isolation: isolate;
+ }
+ #occluder, #target {
+ position: absolute;
+ left: 100px; top: 100px;
+ width: 100; height: 100;
+ }
+ </style>
+ <div id="GP">
+ <div id="TC">
+ <div>
+ <div id="L">
+ <div id="occluder"></div>
+ </div>
+ </div>
+ </div>
+ <div id="target"></div>
+ </div>
+ )HTML");
+ Compositor().BeginFrame();
+
+ IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
+ observer_init->setTrackVisibility(true);
+ observer_init->setDelay(100);
+ DummyExceptionStateForTesting exception_state;
+ TestIntersectionObserverDelegate* observer_delegate =
+ MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
+ IntersectionObserver* observer = IntersectionObserver::Create(
+ observer_init, *observer_delegate,
+ LocalFrameUkmAggregator::kJavascriptIntersectionObserver,
+ exception_state);
+ ASSERT_FALSE(exception_state.HadException());
+ Element* target = GetDocument().getElementById(AtomicString("target"));
+ ASSERT_TRUE(target);
+ observer->observe(target);
+
+ Compositor().BeginFrame();
+ test::RunPendingTasks();
+ ASSERT_FALSE(Compositor().NeedsBeginFrame());
+ EXPECT_EQ(observer_delegate->CallCount(), 1);
+ EXPECT_EQ(observer_delegate->EntryCount(), 1);
+ EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
+ // #target is occluded by #occluder which is in front of it (z=5 vs z=0).
+ EXPECT_FALSE(observer_delegate->LastEntry()->isVisible());
+}
+
TEST_F(IntersectionObserverV2Test, TableCellOcclusion) {
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
SimRequest main_resource("https://example.com/", "text/html");
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