Chrome · Autofill › Mac
CVE-2026-113312
Memory Corruption in Autofill (Mac)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/media/webrtc/media_stream_focus_delegate.cc |
modified | |
ifchrome/browser/permissions/BUILD.gn |
modified |
Files Changed
chrome/browser/permissions/permissions_security_model_interactive_uitest.cccontent/browser/renderer_host/render_widget_host_view_mac.mmchrome/browser/media/webrtc/media_stream_focus_delegate.ccchrome/browser/media/webrtc/media_stream_focus_delegate.hchrome/test/base/interactive_test_utils_aura.ccchrome/browser/permissions/BUILD.gn
Patch
From 54157e0d7a49f477f16d6bbbe703f691e9d056ac Mon Sep 17 00:00:00 2001
From: Keren Zhu <kerenzhu@chromium.org>
Date: Tue, 22 Sep 2026 08:50:51 -0700
Subject: [PATCH] mac: return false from RWHVMac::HasFocus() when the window is not key
This CL changes HasFocus() to return false if the window is non-key.
This aligns the behavior with Aura & Blink (that being focused implies
being in the active window).
This fixes a security issue in autofill that the autofill popup in an
inactive window sometimes occludes the active window
(crbug.com/439873332). Previously, the autofill popup closes itself iff
RWHV::HasFocus() is false, but HasFocus() does not check checks window
key status. This means the popup won't be close if the window goes
inactive but NSView does not resign first responder.
A few tests are updated to show and activate the browser window before
running the rest of the test. They were depending on HasFocus()
returning true when the window is inactive. Some tests are excluded on
Wayland because Wayland has poor support for activating background
window (https://crbug.com/40863331) and the test times out.
The change is under a kill switch,
kRWHVMacHasFocusReturnsFalseForNonKeyWindow.
Bug: 441089142, 439873332
Include-Ci-Only-Tests: chromium.mac:mac26-arm64-rel-tests|browser_tests
Change-Id: I9683d3a328d58f6a5cf76d10f53a7e170898d052
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7085807
Reviewed-by: Avi Drissman <avi@chromium.org>
Commit-Queue: Keren Zhu <kerenzhu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1702667}
---
diff --git a/chrome/browser/media/webrtc/media_stream_focus_delegate.cc b/chrome/browser/media/webrtc/media_stream_focus_delegate.cc
index 04d4a8df..c7cf97a 100644
--- a/chrome/browser/media/webrtc/media_stream_focus_delegate.cc
+++ b/chrome/browser/media/webrtc/media_stream_focus_delegate.cc
@@ -85,8 +85,8 @@
return; // Window of opportunity to change focus now closed - we're done.
}
- if (!IsWidgetFocused()) {
- // Capturing window not focused - likely the user has alt-tabbed away.
+ if (!IsBrowserActive()) {
+ // Capturing window not active - likely the user has alt-tabbed away.
return;
}
@@ -105,17 +105,20 @@
focus_window_of_opportunity_open_ = false;
}
-bool MediaStreamFocusDelegate::IsWidgetFocused() const {
+bool MediaStreamFocusDelegate::IsBrowserActive() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(capturing_web_contents_); // Tested by caller.
- content::RenderWidgetHostView* const rwhv =
- capturing_web_contents_->GetRenderWidgetHostView();
- if (!rwhv) {
- return false;
- }
-
- return rwhv->HasFocus();
+ // Use BrowserWindowInterface::IsActive() rather than
+ // RenderWidgetHostView::HasFocus(). When the screen picker dialog is accepted
+ // and closing (e.g. during the asynchronous modal close animation on macOS),
+ // the child modal dialog window may still be the OS key window when
+ // SetFocus() runs, causing RenderWidgetHostView::HasFocus() to return false
+ // even though the browser window is still logically active.
+ BrowserWindowInterface* const browser =
+ GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+ capturing_web_contents_.get());
+ return browser && browser->IsActive();
}
void MediaStreamFocusDelegate::FocusTab(
diff --git a/chrome/browser/media/webrtc/media_stream_focus_delegate.h b/chrome/browser/media/webrtc/media_stream_focus_delegate.h
index b6b79d0..024d1c8 100644
--- a/chrome/browser/media/webrtc/media_stream_focus_delegate.h
+++ b/chrome/browser/media/webrtc/media_stream_focus_delegate.h
@@ -53,7 +53,7 @@
void ResetExplicitDecisionForTesting() { explicit_decision_ = false; }
private:
- bool IsWidgetFocused() const;
+ bool IsBrowserActive() const;
void FocusTab(const content::DesktopMediaID& media_id);
void FocusWindow(const content::DesktopMediaID& media_id);
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 31561fd..3086ae7 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -115,6 +115,11 @@
// resize.
BASE_FEATURE(kThrottleResizeIpc, base::FEATURE_ENABLED_BY_DEFAULT);
+// If enabled, RenderWidgetHostViewMac::HasFocus() returns false if it is in
+// a non-key NSWindow. This aligns with Aura behavior. crbug.com/441089142.
+BASE_FEATURE(kRWHVMacHasFocusReturnsFalseForNonKeyWindow,
+ base::FEATURE_ENABLED_BY_DEFAULT);
+
// Extract any events in `visible_time_request` that should go to the
// DelegatedFrameHost and sends them to `delegated_frame_host`. Modifies
// `visible_time_request` in place.
@@ -683,6 +688,12 @@
}
bool RenderWidgetHostViewMac::HasFocus() {
+ if (base::FeatureList::IsEnabled(
+ kRWHVMacHasFocusReturnsFalseForNonKeyWindow)) {
+ // In headless mode the NSView is not in a NSWindow.
+ return is_first_responder_ && (IsHeadless() || is_window_key_);
+ }
+
return is_first_responder_;
}
diff --git a/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc b/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
index 15b55ed7..53f5410 100644
--- a/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
+++ b/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
@@ -22,6 +22,7 @@
#include "chrome/test/base/chrome_test_path_utils.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "chrome/test/permissions/permission_request_manager_test_api.h"
@@ -60,6 +61,10 @@
#include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
#include "url/gurl.h"
+#if BUILDFLAG(IS_OZONE)
+#include "ui/ozone/public/ozone_platform.h"
+#endif
+
namespace {
// Helper for CreateBlobURL() and CreateFilesystemURL().
@@ -173,6 +178,7 @@
contents, content::JsReplace("window.open($1, '', '[]');", url));
BrowserWindowInterface* popup = ui_test_utils::WaitForBrowserToOpen();
EXPECT_NE(popup, browser);
+ EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup));
content::WebContents* popup_contents =
popup->GetTabStripModel()->GetActiveWebContents();
EXPECT_TRUE(WaitForRenderFrameReady(popup_contents->GetPrimaryMainFrame()));
@@ -307,6 +313,10 @@
permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
// Move the web contents to the foreground.
+ BrowserWindowInterface* opener_browser =
+ GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+ opener_or_embedder_contents);
+ ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(opener_browser));
opener_rfh->GetView()->Focus();
ASSERT_TRUE(opener_rfh->GetView()->HasFocus());
// Request permission on the opener or embedder contents.
@@ -323,6 +333,10 @@
EXPECT_EQ(true, content::EvalJs(test_rfh, check_permission_script));
// Request permission on the test RFH.
+ BrowserWindowInterface* test_browser =
+ GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+ content::WebContents::FromRenderFrameHost(test_rfh));
+ ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(test_browser));
test_rfh->GetView()->Focus();
ASSERT_TRUE(test_rfh->GetView()->HasFocus());
EXPECT_EQ("granted", content::EvalJs(test_rfh, request_permission_script));
@@ -350,6 +364,10 @@
permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
// Move the web contents to the foreground.
+ BrowserWindowInterface* popup_browser =
+ GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+ popup_contents);
+ ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup_browser));
popup_rfh->GetView()->Focus();
ASSERT_TRUE(popup_rfh->GetView()->HasFocus());
// Request permission on the popup RenderFrameHost.
@@ -482,6 +500,11 @@
IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
WindowOpenAboutBlank) {
+#if BUILDFLAG(IS_OZONE)
+ if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+ GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+ }
+#endif
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url(embedded_test_server()->GetURL("/empty.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
@@ -500,6 +523,11 @@
IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
WindowOpenAboutBlankToUseQuiet) {
+#if BUILDFLAG(IS_OZONE)
+ if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+ GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+ }
+#endif
browser()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kEnableQuietNotificationPermissionUi, true);
ASSERT_TRUE(embedded_test_server()->Start());
@@ -562,6 +590,11 @@
IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
WindowOpenBlob) {
+#if BUILDFLAG(IS_OZONE)
+ if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+ GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+ }
+#endif
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url(embedded_test_server()->GetURL("/empty.html"));
content::RenderFrameHost* main_rfh =
@@ -605,6 +638,11 @@
IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
WindowOpenFileSystemBrowserNavigation) {
+#if BUILDFLAG(IS_OZONE)
+ if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+ GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+ }
+#endif
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url(embedded_test_server()->GetURL("/empty.html"));
content::RenderFrameHost* main_rfh =
diff --git a/chrome/test/base/interactive_test_utils_aura.cc b/chrome/test/base/interactive_test_utils_aura.cc
index 624a7a2..fb5f7ee 100644
--- a/chrome/test/base/interactive_test_utils_aura.cc
+++ b/chrome/test/base/interactive_test_utils_aura.cc
@@ -7,6 +7,7 @@
#include "build/build_config.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
namespace ui_test_utils {
@@ -26,6 +27,9 @@
bool ShowAndFocusNativeWindowAura(gfx::NativeWindow window) {
window->Show();
+ if (views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window)) {
+ widget->Activate();
+ }
window->Focus();
return true;
}
diff --git a/chrome/browser/permissions/BUILD.gn b/chrome/browser/permissions/BUILD.gn
index b49f7df..9152679 100644
--- a/chrome/browser/permissions/BUILD.gn
+++ b/chrome/browser/permissions/BUILD.gn
@@ -445,5 +445,9 @@
deps += [ "//chrome/browser/ui:browser_window_header" ]
sources += [ "system/system_permission_settings_interactive_uitest.cc" ]
}
+
+ if (use_ozone) {
+ deps += [ "//ui/ozone" ]
+ }
}
}
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc b/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
index 15b55ed7..53f5410 100644
--- a/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
+++ b/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
@@ -22,6 +22,7 @@
#include "chrome/test/base/chrome_test_path_utils.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "chrome/test/permissions/permission_request_manager_test_api.h"
@@ -60,6 +61,10 @@
#include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
#include "url/gurl.h"
+#if BUILDFLAG(IS_OZONE)
+#include "ui/ozone/public/ozone_platform.h"
+#endif
+
namespace {
// Helper for CreateBlobURL() and CreateFilesystemURL().
@@ -173,6 +178,7 @@
contents, content::JsReplace("window.open($1, '', '[]');", url));
BrowserWindowInterface* popup = ui_test_utils::WaitForBrowserToOpen();
EXPECT_NE(popup, browser);
+ EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup));
content::WebContents* popup_contents =
popup->GetTabStripModel()->GetActiveWebContents();
EXPECT_TRUE(WaitForRenderFrameReady(popup_contents->GetPrimaryMainFrame()));
@@ -307,6 +313,10 @@
permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
// Move the web contents to the foreground.
+ BrowserWindowInterface* opener_browser =
+ GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+ opener_or_embedder_contents);
+ ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(opener_browser));
opener_rfh->GetView()->Focus();
ASSERT_TRUE(opener_rfh->GetView()->HasFocus());
// Request permission on the opener or embedder contents.
@@ -323,6 +333,10 @@
EXPECT_EQ(true, content::EvalJs(test_rfh, check_permission_script));
// Request permission on the test RFH.
+ BrowserWindowInterface* test_browser =
+ GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+ content::WebContents::FromRenderFrameHost(test_rfh));
+ ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(test_browser));
test_rfh->GetView()->Focus();
ASSERT_TRUE(test_rfh->GetView()->HasFocus());
EXPECT_EQ("granted", content::EvalJs(test_rfh, request_permission_script));
@@ -350,6 +364,10 @@
permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
// Move the web contents to the foreground.
+ BrowserWindowInterface* popup_browser =
+ GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+ popup_contents);
+ ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup_browser));
popup_rfh->GetView()->Focus();
ASSERT_TRUE(popup_rfh->GetView()->HasFocus());
// Request permission on the popup RenderFrameHost.
@@ -482,6 +500,11 @@
IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
WindowOpenAboutBlank) {
+#if BUILDFLAG(IS_OZONE)
+ if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+ GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+ }
+#endif
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url(embedded_test_server()->GetURL("/empty.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
@@ -500,6 +523,11 @@
IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
WindowOpenAboutBlankToUseQuiet) {
+#if BUILDFLAG(IS_OZONE)
+ if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+ GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+ }
+#endif
browser()->GetProfile()->GetPrefs()->SetBoolean(
prefs::kEnableQuietNotificationPermissionUi, true);
ASSERT_TRUE(embedded_test_server()->Start());
@@ -562,6 +590,11 @@
IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
WindowOpenBlob) {
+#if BUILDFLAG(IS_OZONE)
+ if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+ GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+ }
+#endif
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url(embedded_test_server()->GetURL("/empty.html"));
content::RenderFrameHost* main_rfh =
@@ -605,6 +638,11 @@
IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
WindowOpenFileSystemBrowserNavigation) {
+#if BUILDFLAG(IS_OZONE)
+ if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+ GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+ }
+#endif
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url(embedded_test_server()->GetURL("/empty.html"));
content::RenderFrameHost* main_rfh =
diff --git a/chrome/test/base/interactive_test_utils_aura.cc b/chrome/test/base/interactive_test_utils_aura.cc
index 624a7a2..fb5f7ee 100644
--- a/chrome/test/base/interactive_test_utils_aura.cc
+++ b/chrome/test/base/interactive_test_utils_aura.cc
@@ -7,6 +7,7 @@
#include "build/build_config.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
namespace ui_test_utils {
@@ -26,6 +27,9 @@
bool ShowAndFocusNativeWindowAura(gfx::NativeWindow window) {
window->Show();
+ if (views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window)) {
+ widget->Activate();
+ }
window->Focus();
return true;
}
Loading diff…
Original Bug Report
reported by bu...@gmail.com
Autofill Prompt Spoofing via Cross-Origin Rendering in Chrome 139.0.7258.139 on macOS 15.6
Steps to reproduce the problem
Reproduction Steps:
> Prerequisites: > Ensure the following data is stored in Chrome:
- At least one address entry with an email address:
chrome://settings/addresses - At least one credit card:
chrome://settings/payments
PoC 1 – Address Autofill (Multiple Fields)
- Navigate to: https://attacker.hackerbro.net/exploit.html
- Click the “Verify Info” button.
- Click on the autofill suggestion that appears.
- Observe the data being injected into an overlayed form from a different origin.
PoC 2 – Credit Card Autofill (Multiple Fields)
- Navigate to: https://attacker.hackerbro.net/exploit.html?creditcard
- Click the “Verify Info” button.
- Click on the autofill suggestion that appears.
- Observe the data being injected into an overlayed form from a different origin.
PoC 3 – Name Autofill (Single Field)
- Navigate to: https://attacker.hackerbro.net/exploit.html?single
- Click the “Verify Info” button.
- Click on the autofill suggestion that appears.
- Observe the data being injected into an overlayed form from a different origin.
Reference:
https://issues.chromium.org/u/1/issues/40056880?pli=1
Expected Behavior:
- Autofill prompts should only appear for input elements that are in the same origin as the page requesting the autofill.
- The browser should verify and bind the autofill UI to the correct origin context, preventing cross-origin spoofing.
Actual Behavior:
- Autofill prompts can be triggered or rendered in a cross-origin context, allowing spoofing of the trusted origin.
- There is no reliable origin validation in the autofill UI layer.
Recommendation:
- Enforce strict same-origin checks before displaying autofill UI.
- Autofill prompts should never be rendered in an iframe or any cross-origin element.
- Any UI trigger must be tied to same-origin input events with visual trust indicators.
Problem Description
- In Chrome version 139.0.7258.139 on macOS 15.6, the autofill prompt can be rendered over a different origin, allowing malicious sites to spoof the origin context of the autofill UI. This can lead to a serious phishing risk where a user is tricked into autofilling sensitive data (e.g., usernames, passwords, credit card details) into a form controlled by a malicious domain.
Environment:
- Chrome Version: 139.0.7258.139
- OS: macOS Sonoma 15.6
Impact:
- Phishing: Users can be tricked into submitting sensitive information to attackers.
- Credential theft: Autofill may reveal stored usernames and passwords.
- Loss of trust: Users assume autofill prompts come only from trusted origins.
Summary
Autofill Prompt Spoofing via Cross-Origin Rendering in Chrome 139.0.7258.139 on macOS 15.6
Additional Data
Category: Security
Chrome Channel: Stable
Regression: Yes \
References
On This Page