Medium watch CVSS 4.3 chrome Authorization Error 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in Viz
DescriptionMissing authorization in Viz
ComponentViz
Sub-componentContent
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker502232151
Fix commita8d1167fe626 (chromium/src) +64/-5
CWECWE-862
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-13 (per vendor advisory)
Fix landed2026-07-17 (public git)
Disclosed2026-08-25
Patch gap39 days — fix public in source before the advisory

Background

Back/forward cache (BFCache)
A browser-process feature that keeps a fully constructed, but inactive, document (its RenderFrameHost) alive after the user navigates away, so a back/forward navigation can restore it instantly.
`LocalMainFrameHost::SetWindowRect`
A Mojo IPC exposed to the renderer’s main frame that asks the browser to move/resize the window hosting that frame, ultimately reaching WebContentsDelegate::SetContentsBounds.
`RenderFrameHostImpl`
The browser-side object backing a document; an instance can be the active primary main frame, a child/inner frame, or an inactive document sitting in BFCache.
`ValidateOutermostMainFrameWindowChange`
A helper (pre-existing, used elsewhere for window-affecting IPCs) that rejects window changes from documents that are not the active outermost main frame and evicts an inactive page from BFCache.

Root Cause Analysis

The pre-patch RenderFrameHostImpl::SetWindowRect only rejected the IPC when GetParentOrOuterDocument() was non-null (i.e. from a child/inner frame); it had no check for whether the calling document was still active. A document that had entered the back/forward cache is neither a child nor an outer-document frame, so it passed that guard and reached delegate_->SetWindowRect(bounds), letting a cached (background) renderer move or resize the window that is now displaying a completely different primary main frame.

The fix replaces the child-frame-only check with ValidateOutermostMainFrameWindowChange("SetWindowRect"), which returns false for an inactive/cached document and additionally evicts it from BFCache so the renderer’s window-bounds state cannot desync on restore. It also wraps the callback in a base::ScopedClosureRunner so the renderer is always acked (and can clear its pending window rect) even on the new early-return paths, whereas previously the ack (std::move(callback).Run()) only ran on the success path.

Key insight
The core mistake was an authorization check that only excluded sub-frames, not inactive/BFCached main-frame documents, so a cached renderer retained the ability to reposition the active window; the fix routes the IPC through ValidateOutermostMainFrameWindowChange, which enforces that only the active outermost main frame may change window bounds and evicts stale cached pages.

Attack Path

  1. Cache a document A page A is loaded, then the user (or script) navigates to page B, causing A’s RenderFrameHostImpl to be stored in the back/forward cache while B becomes the active primary main frame.
  2. Cached renderer sends `SetWindowRect` The still-alive, backgrounded renderer for A issues LocalMainFrameHost::SetWindowRect with attacker-chosen bounds.
  3. Old guard passes Because A is a cached main-frame document (not a child/outer frame), GetParentOrOuterDocument() is null, so the pre-patch code skips the bad-message rejection and calls delegate_->SetWindowRect(bounds).
  4. Window of the active page is moved/resized The browser applies A’s requested bounds to the window that is now showing B, allowing a background document to manipulate the geometry of the foreground page’s window without authorization.

Impact Assessment

An attacker controlling a page that has been placed in BFCache can move or resize the browser window currently displaying an unrelated foreground page, a missing-authorization flaw acted on in the browser process on behalf of an inactive renderer. The advisory classifies it as missing authorization in Viz with no stated exploited-in-the-wild status; the patch demonstrates unauthorized window bounds manipulation and potential browser/renderer window-state desync, but no memory-safety or further escalation is shown. (inferred) Preconditions are that BFCache is enabled and the attacker’s page can reach the cache and emit the IPC while backgrounded.

Changed Functions

FunctionChangeNotes
BoundsTrackingDelegate
content/browser/back_forward_cache_internal_browsertest.cc
modified

Files Changed

  • content/browser/renderer_host/render_frame_host_impl.cc
  • content/browser/back_forward_cache_internal_browsertest.cc

Audit Directions

  • Window/geometry IPCs missing active-document checks
    Audit sibling handlers such as MoveWindowTo and other LocalMainFrameHost/window-affecting Mojo methods to confirm each validates that the caller is the active outermost main frame, not merely “not a subframe.”
  • BFCache-inactive documents reaching side-effecting delegates
    Search browser-side IPC handlers that call into delegate_/WebContentsDelegate for cases that check frame hierarchy (GetParentOrOuterDocument) but omit the inactive/prerender/BFCache state, since a cached renderer can still send IPCs.
  • Ack/callback paths on early return
    Review handlers that only run their Mojo response callback on the success path; a dropped ack can leave the renderer with permanently pending state and mask incorrect early-return refactors.
From a8d1167fe626a0a8b6876f1de94bf56d08e928bc Mon Sep 17 00:00:00 2001
From: Tzarial <[email protected]>
Date: Fri, 17 Jul 2026 04:28:20 -0700
Subject: [PATCH] [agy][content] Ignore SetWindowRect in BFCache

LocalMainFrameHost::SetWindowRect arriving for a document that has
entered the back/forward cache must not move or resize the window,
which is now displaying a different primary main frame.

Fixed: 502232151
Test: BackForwardCacheBrowserTest.SetWindowRectIgnoredForCachedFrame
Change-Id: Id4e872b4c4de6cc7b43cb3ae4b4cab1a89a26b44
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8087302
Commit-Queue: Tzarial <[email protected]>
Reviewed-by: Rakina Zata Amni <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1663804}
---
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 08e2dcb..27e1428 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -7841,15 +7841,22 @@
         "SetWindowRect called during prerendering.");
     return;
   }
-  // Throw out SetWindowRects that are not from the outermost document.
-  if (GetParentOrOuterDocument()) {
-    local_main_frame_host_receiver_.ReportBadMessage(
-        "SetWindowRect called from child frame.");
+
+  // Always ack the renderer so it can clear its pending window rect, even on
+  // the early-return paths below.
+  base::ScopedClosureRunner ack(std::move(callback));
+
+  // An inactive document (e.g. one that has entered the back/forward cache)
+  // must not move or resize the window, which now hosts a different primary
+  // main frame. We use ValidateOutermostMainFrameWindowChange to evict the
+  // page from BFCache if this happens, to prevent the renderer from getting
+  // out of sync with the browser's window bounds if the page is later
+  // restored. See https://crbug.com/502232151.
+  if (!ValidateOutermostMainFrameWindowChange("SetWindowRect")) {
     return;
   }
 
   delegate_->SetWindowRect(bounds);
-  std::move(callback).Run();
 }
 
 void RenderFrameHostImpl::MoveWindowTo(const gfx::Point& origin,
diff --git a/content/browser/back_forward_cache_internal_browsertest.cc b/content/browser/back_forward_cache_internal_browsertest.cc
index 926210f..96975c30 100644
--- a/content/browser/back_forward_cache_internal_browsertest.cc
+++ b/content/browser/back_forward_cache_internal_browsertest.cc
@@ -26,6 +26,7 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/disallow_activation_reason.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents_delegate.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
@@ -2022,6 +2023,57 @@
                     {reason}, FROM_HERE);
 }
 
+// LocalMainFrameHost::SetWindowRect arriving for a document that has entered
+// the back/forward cache must not move or resize the window, which is now
+// displaying a different primary main frame. See https://crbug.com/502232151.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
+                       SetWindowRectIgnoredForCachedFrame) {
+  // A WebContentsDelegate that records SetContentsBounds() calls.
+  class BoundsTrackingDelegate : public WebContentsDelegate {
+   public:
+    void SetContentsBounds(WebContents* source,
+                           const gfx::Rect& bounds) override {
+      ++set_contents_bounds_call_count_;
+    }
+    int set_contents_bounds_call_count() const {
+      return set_contents_bounds_call_count_;
+    }
+
+   private:
+    int set_contents_bounds_call_count_ = 0;
+  };
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
+
+  // 1) Navigate to A.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a));
+  RenderFrameHostImplWrapper rfh_a(current_frame_host());
+
+  // 2) Navigate to B. A is stored in the back/forward cache.
+  EXPECT_TRUE(NavigateToURL(shell(), url_b));
+  ASSERT_TRUE(rfh_a->IsInBackForwardCache());
+
+  BoundsTrackingDelegate tracking_delegate;
+  WebContentsDelegate* old_delegate = web_contents()->GetDelegate();
+  web_contents()->SetDelegate(&tracking_delegate);
+
+  // 3) Simulate the cached renderer sending SetWindowRect. The request must be
+  // dropped without forwarding new bounds to the WebContentsDelegate.
+  bool callback_ran = false;
+  rfh_a->SetWindowRect(
+      gfx::Rect(10, 10, 300, 200),
+      base::BindLambdaForTesting([&] { callback_ran = true; }));
+  EXPECT_TRUE(callback_ran);
+  EXPECT_EQ(0, tracking_delegate.set_contents_bounds_call_count());
+
+  // The page must be evicted from the back/forward cache.
+  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
+
+  web_contents()->SetDelegate(old_delegate);
+}
+
 // Test scenarios where the "BackForwardCache" content flag is enabled but
 // the command line flag "DisableBackForwardCache" is turned on, resulting in
 // the feature being disabled.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/back_forward_cache_internal_browsertest.cc b/content/browser/back_forward_cache_internal_browsertest.cc
index 926210f..96975c30 100644
--- a/content/browser/back_forward_cache_internal_browsertest.cc
+++ b/content/browser/back_forward_cache_internal_browsertest.cc
@@ -26,6 +26,7 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/disallow_activation_reason.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents_delegate.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
@@ -2022,6 +2023,57 @@
                     {reason}, FROM_HERE);
 }
 
+// LocalMainFrameHost::SetWindowRect arriving for a document that has entered
+// the back/forward cache must not move or resize the window, which is now
+// displaying a different primary main frame. See https://crbug.com/502232151.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
+                       SetWindowRectIgnoredForCachedFrame) {
+  // A WebContentsDelegate that records SetContentsBounds() calls.
+  class BoundsTrackingDelegate : public WebContentsDelegate {
+   public:
+    void SetContentsBounds(WebContents* source,
+                           const gfx::Rect& bounds) override {
+      ++set_contents_bounds_call_count_;
+    }
+    int set_contents_bounds_call_count() const {
+      return set_contents_bounds_call_count_;
+    }
+
+   private:
+    int set_contents_bounds_call_count_ = 0;
+  };
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
+
+  // 1) Navigate to A.
+  EXPECT_TRUE(NavigateToURL(shell(), url_a));
+  RenderFrameHostImplWrapper rfh_a(current_frame_host());
+
+  // 2) Navigate to B. A is stored in the back/forward cache.
+  EXPECT_TRUE(NavigateToURL(shell(), url_b));
+  ASSERT_TRUE(rfh_a->IsInBackForwardCache());
+
+  BoundsTrackingDelegate tracking_delegate;
+  WebContentsDelegate* old_delegate = web_contents()->GetDelegate();
+  web_contents()->SetDelegate(&tracking_delegate);
+
+  // 3) Simulate the cached renderer sending SetWindowRect. The request must be
+  // dropped without forwarding new bounds to the WebContentsDelegate.
+  bool callback_ran = false;
+  rfh_a->SetWindowRect(
+      gfx::Rect(10, 10, 300, 200),
+      base::BindLambdaForTesting([&] { callback_ran = true; }));
+  EXPECT_TRUE(callback_ran);
+  EXPECT_EQ(0, tracking_delegate.set_contents_bounds_call_count());
+
+  // The page must be evicted from the back/forward cache.
+  ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
+
+  web_contents()->SetDelegate(old_delegate);
+}
+
 // Test scenarios where the "BackForwardCache" content flag is enabled but
 // the command line flag "DisableBackForwardCache" is turned on, resulting in
 // the feature being disabled.
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.