Chrome · Frames
CVE-2025-0439
Race in Frames
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/frame/frame_view.cc |
modified | |
async_testthird_party/blink/web_tests/external/wpt/intersection-observer/v2/nested-cross-origin.sub.html |
modified |
Files Changed
third_party/blink/renderer/core/frame/frame_view.ccthird_party/blink/renderer/core/intersection_observer/intersection_observation.ccthird_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-midframe.sub.htmlthird_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.htmlthird_party/blink/web_tests/external/wpt/intersection-observer/v2/nested-cross-origin.sub.html
Patch
From 7b2e3f7ff30d5dcf17cd5c00f0554a44eec2c2a1 Mon Sep 17 00:00:00 2001
From: Stefan Zager <szager@chromium.org>
Date: Thu, 24 Oct 2024 02:35:39 +0000
Subject: [PATCH] IntersectionObserver -- properly handle "unknown" occlusion state
If we most recently reported a target as "guaranteed visible", then in
the interest of avoiding false positives we must transition to "not
guaranteed visible" if the frame occlusion state becomes "unknown".
This CL also makes a child frame inherit its parent's "not visible"
occlusion state rather than calling it "unknown", which is technically
more correct.
Bug: chromium:371247941
Change-Id: I4d721dd252d013deac14a12f1f2922830ef2a8a4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5950965
Reviewed-by: Xianzhu Wang <wangxianzhu@chromium.org>
Commit-Queue: Stefan Zager <szager@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1373093}
---
diff --git a/third_party/blink/renderer/core/frame/frame_view.cc b/third_party/blink/renderer/core/frame/frame_view.cc
index 3d6d9a2..29981ce52 100644
--- a/third_party/blink/renderer/core/frame/frame_view.cc
+++ b/third_party/blink/renderer/core/frame/frame_view.cc
@@ -77,15 +77,20 @@
gfx::Transform main_frame_transform_matrix;
DocumentLifecycle::LifecycleState parent_lifecycle_state =
owner_document.Lifecycle().GetState();
+
+ bool should_compute_occlusion = false;
mojom::blink::FrameOcclusionState occlusion_state =
owner_document.GetFrame()->GetOcclusionState();
- bool should_compute_occlusion =
- needs_occlusion_tracking &&
- occlusion_state ==
- mojom::blink::FrameOcclusionState::kGuaranteedNotOccluded &&
- parent_lifecycle_state >= DocumentLifecycle::kPrePaintClean;
- if (!should_compute_occlusion) {
+ if (occlusion_state ==
+ mojom::blink::FrameOcclusionState::kGuaranteedNotOccluded) {
+ // We can't propagate kGuaranteedNotOccluded from the parent without testing
+ // occlusion of this frame. If we don't ultimately do an occlusion test on
+ // this frame, then we should propagate "unknown".
occlusion_state = mojom::blink::FrameOcclusionState::kUnknown;
+ if (needs_occlusion_tracking &&
+ parent_lifecycle_state >= DocumentLifecycle::kPrePaintClean) {
+ should_compute_occlusion = true;
+ }
}
LayoutEmbeddedContent* owner_layout_object =
@@ -159,8 +164,12 @@
rect_in_parent_stable_since_for_iov2_ = base::TimeTicks::Now();
}
}
- if (should_compute_occlusion && !geometry.IsVisible())
- occlusion_state = mojom::blink::FrameOcclusionState::kPossiblyOccluded;
+ if (should_compute_occlusion) {
+ occlusion_state =
+ geometry.IsVisible()
+ ? mojom::blink::FrameOcclusionState::kGuaranteedNotOccluded
+ : mojom::blink::FrameOcclusionState::kPossiblyOccluded;
+ }
// Generate matrix to transform from the space of the containing document
// to the space of the iframe's contents.
@@ -248,11 +257,6 @@
}
main_frame_transform_matrix =
child_frame_to_root_frame.AccumulatedTransform();
- } else if (occlusion_state ==
- mojom::blink::FrameOcclusionState::kGuaranteedNotOccluded) {
- // If the parent LocalFrameView is throttled and out-of-date, then we can't
- // get any useful information.
- occlusion_state = mojom::blink::FrameOcclusionState::kUnknown;
}
// An iframe's content is always pixel-snapped, even if the iframe element has
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_observation.cc b/third_party/blink/renderer/core/intersection_observer/intersection_observation.cc
index a05620d..160254b6 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_observation.cc
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_observation.cc
@@ -179,11 +179,14 @@
Observer()->trackVisibility()) {
mojom::blink::FrameOcclusionState occlusion_state =
target_->GetDocument().GetFrame()->GetOcclusionState();
- // If we're tracking visibility, and we don't have occlusion information
- // from our parent frame, then postpone computing intersections until a
- // later lifecycle when the occlusion information is known.
- if (occlusion_state == mojom::blink::FrameOcclusionState::kUnknown)
+ // If we're tracking visibility, and we aren't currently reporting the
+ // target visible, and we don't have occlusion information from our parent
+ // frame, then postpone computing intersections until a later lifecycle when
+ // the occlusion information is known.
+ if (!last_is_visible_ &&
+ occlusion_state == mojom::blink::FrameOcclusionState::kUnknown) {
return false;
+ }
}
return true;
}
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-midframe.sub.html b/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-midframe.sub.html
new file mode 100644
index 0000000..b9b055b6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-midframe.sub.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<style>
+iframe {
+ width: 300px;
+ height: 150px;
+ border: none;
+}
+</style>
+
+<iframe src="http://{{domains[www2]}}:{{ports[http][0]}}/intersection-observer/resources/v2-subframe.html"></iframe>
+
+<script>
+window.addEventListener("message", event => {
+ requestAnimationFrame(() => setTimeout(() => {
+ document.querySelector('iframe').contentWindow.postMessage(event.data, "*");
+ }));
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.html b/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.html
index 295bbf04..cfc82646 100644
--- a/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.html
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.html
@@ -16,7 +16,7 @@
window.addEventListener("message", event => {
waitForNotification(() => {
- window.parent.postMessage(results.map(e => e.isVisible), "*");
+ window.top.postMessage(results.map(e => e.isVisible), "*");
results = [];
});
});
@@ -27,6 +27,6 @@
results = entries;
}, {trackVisibility: true, delay: delay});
observer.observe(document.getElementById("target"));
- window.parent.postMessage("", "*");
+ window.top.postMessage("", "*");
};
</script>
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/v2/nested-cross-origin.sub.html b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/nested-cross-origin.sub.html
new file mode 100644
index 0000000..42a69bf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/nested-cross-origin.sub.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/intersection-observer-test-utils.js"></script>
+
+<style>
+pre, #log {
+ position: absolute;
+ top: 0;
+ left: 200px;
+}
+iframe {
+ width: 300px;
+ height: 150px;
+ border: none;
+}
+</style>
+
+<iframe src="http://{{domains[www1]}}:{{ports[http][0]}}/intersection-observer/resources/v2-midframe.sub.html"></iframe>
+
+<script>
+async_test(function(t) {
+ let iframe = document.querySelector("iframe");
+
+ function step0(event) {
+ assert_equals(event.data,"");
+ }
+
+ function step1(event) {
+ assert_equals(JSON.stringify(event.data),
+ JSON.stringify([true]));
+ iframe.style.opacity = "0.9";
+ }
+
+ function step2(event) {
+ assert_equals(JSON.stringify(event.data),
+ JSON.stringify([false]));
+ iframe.style.opacity = "";
+ }
+
+ function step3(event) {
+ assert_equals(JSON.stringify(event.data),
+ JSON.stringify([true]));
+ }
+
+ let steps = [step0, step1, step2, step3];
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-midframe.sub.html b/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-midframe.sub.html
new file mode 100644
index 0000000..b9b055b6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-midframe.sub.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<style>
+iframe {
+ width: 300px;
+ height: 150px;
+ border: none;
+}
+</style>
+
+<iframe src="http://{{domains[www2]}}:{{ports[http][0]}}/intersection-observer/resources/v2-subframe.html"></iframe>
+
+<script>
+window.addEventListener("message", event => {
+ requestAnimationFrame(() => setTimeout(() => {
+ document.querySelector('iframe').contentWindow.postMessage(event.data, "*");
+ }));
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.html b/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.html
index 295bbf04..cfc82646 100644
--- a/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.html
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/resources/v2-subframe.html
@@ -16,7 +16,7 @@
window.addEventListener("message", event => {
waitForNotification(() => {
- window.parent.postMessage(results.map(e => e.isVisible), "*");
+ window.top.postMessage(results.map(e => e.isVisible), "*");
results = [];
});
});
@@ -27,6 +27,6 @@
results = entries;
}, {trackVisibility: true, delay: delay});
observer.observe(document.getElementById("target"));
- window.parent.postMessage("", "*");
+ window.top.postMessage("", "*");
};
</script>
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/v2/nested-cross-origin.sub.html b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/nested-cross-origin.sub.html
new file mode 100644
index 0000000..42a69bf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/nested-cross-origin.sub.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/intersection-observer-test-utils.js"></script>
+
+<style>
+pre, #log {
+ position: absolute;
+ top: 0;
+ left: 200px;
+}
+iframe {
+ width: 300px;
+ height: 150px;
+ border: none;
+}
+</style>
+
+<iframe src="http://{{domains[www1]}}:{{ports[http][0]}}/intersection-observer/resources/v2-midframe.sub.html"></iframe>
+
+<script>
+async_test(function(t) {
+ let iframe = document.querySelector("iframe");
+
+ function step0(event) {
+ assert_equals(event.data,"");
+ }
+
+ function step1(event) {
+ assert_equals(JSON.stringify(event.data),
+ JSON.stringify([true]));
+ iframe.style.opacity = "0.9";
+ }
+
+ function step2(event) {
+ assert_equals(JSON.stringify(event.data),
+ JSON.stringify([false]));
+ iframe.style.opacity = "";
+ }
+
+ function step3(event) {
+ assert_equals(JSON.stringify(event.data),
+ JSON.stringify([true]));
+ }
+
+ let steps = [step0, step1, step2, step3];
+
+ window.addEventListener("message", event => {
+ if (steps.length) {
+ t.step(steps.shift(), t, event);
+ waitForFrame(t, () => {
+ iframe.contentWindow.postMessage("", "*");
+ });
+ } else {
+ t.done();
+ }
+ });
+
+}, "Intersection observer V2 test with nested cross-origin iframes.");
+</script>
Loading diff…
Original Bug Report
reported by sa...@gmail.com
Bypass of https://issues.chromium.org/issues/333708039
VULNERABILITY DETAILS
This vulnerability is similar to https://issues.chromium.org/issues/333708039, in this bug when the cursor focus on google one tap button after that the opacity the frame set 0 (obscured by “click me see funny cats” button) the focus still to google tap button lead to click jacking
VERSION Chrome Version 131.0.6755.0 (Official Build) canary (64-bit) Operating System: Windows 10
REPRODUCTION CASE
- open https://thundering-unruly-windflower.glitch.me/spoofh.html
- click on “click me see funny cats” button
CREDIT INFORMATION Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: Hafiizh (https://www.linkedin.com/in/hafiizh-7aa6bb31/)
References
On This Page