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 <[email protected]> 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 <[email protected]> Commit-Queue: Stefan Zager <[email protected]> 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 [email protected]
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