CVE-2026-87483
Overview
Background
- `ChromeContentBrowserClient::CanCreateWindow()`
- the browser-process embedder hook Chrome consults to decide whether a renderer’s
window.open()request may create a new window; the commit message names it as the function edited (its name is absent from the@@hunk header, so its identity comes from the commit message, not the diff context). - `WindowContainerType::BACKGROUND`
- the window container type requested when web content calls
window.open(url, name, 'background'), historically an extensions-only feature for opening non-visible background windows. - `ENABLE_EXTENSIONS_CORE`
- a
BUILDFLAGthat is true on builds with extension support (Desktop) and false on non-extension builds such as Chrome for Android, gating the extension-permission logic in this code path. - Popup blocker
- the browser mechanism that normally rejects programmatic window creation, which per the added test’s comment does not run for a
'background'request made with transient user activation, leavingCanCreateWindow()as the deciding authority.
Root Cause Analysis
On non-extension builds (ENABLE_EXTENSIONS_CORE false), the WindowContainerType::BACKGROUND branch of CanCreateWindow() fell through to return true unconditionally, as the removed #endif placement and the surviving return true show. The commit message states this let ordinary web content open a background window via window.open(url, name, 'background'), bypassing popup blocking. The invariant violated is that a BACKGROUND window is an extension-gated capability: on Desktop, web content lacking the extension background permission is rejected, but on non-extension builds there is no extension to check, so the code granted the request by default instead of denying it.
The fix moves the #endif and adds an #else arm that returns false, so when ENABLE_EXTENSIONS_CORE is false the BACKGROUND request is now explicitly refused. This works because it converts a default-allow into a default-deny for the exact configuration that had no permission check to fall back on.
false in the #else branch so a missing extension check means denial rather than approval.Attack Path
- Reach the vulnerable configuration
The victim runs a non-extension Chrome build (e.g. Chrome for Android) where
ENABLE_EXTENSIONS_COREis false, so theBACKGROUNDbranch ofCanCreateWindow()has no extension to consult. - Obtain user activation
Ordinary web content secures transient user activation (a click), which per the test’s comment is required so the standard popup blocker does not reject the call before it reaches
CanCreateWindow(). - Request a background window
The page calls
window.open('/some.html', '', 'background'); the request maps toWindowContainerType::BACKGROUNDand, pre-patch,CanCreateWindow()returnstrue, so a newWebContents/window is created despite the page having no extension background permission — the popup-blocking bypass the commit message describes. - No proven escalation beyond window creation
The diff only shows creation of an otherwise-blocked window; any further abuse (background/off-screen navigation, resource use, or user deception) is not demonstrated by the patch.
(inferred)
Impact Assessment
BACKGROUND window from ordinary web content in the renderer/web context, with the only precondition being transient user activation. The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 6.5. Any consequence beyond the unauthorized window creation the patch prevents — such as user deception or resource abuse — is (inferred) and not established by the diff.Changed Functions
| Function | Change | Notes |
|---|---|---|
ChromeContentBrowserClientPlatformBrowserTestchrome/browser/chrome_content_browser_client_platform_browsertest.cc |
modified |
Files Changed
chrome/browser/chrome_content_browser_client_platform_browsertest.ccchrome/test/BUILD.gnchrome/browser/chrome_content_browser_client.cc
Audit Directions
- Default-allow on compiled-out permission checksSearch for embedder hooks that gate a capability on an extension/feature permission inside a
BUILDFLAGguard andreturn true(or otherwise allow) in the fall-through when that flag is false; the missing check should mean deny, not allow. - `#if`/`#else`/`#endif` fall-through returnsReview authorization functions where a security decision sits just after a preprocessor block, since a misplaced
#endifcan silently let a permissivereturncover the disabled-feature configuration. - Window/container type gating in `CanCreateWindow` and siblingsAudit other
WindowContainerTypevalues and othercontent::ContentBrowserClientauthorization overrides on non-extension builds to confirm each privileged type is explicitly denied to ordinary web content rather than allowed by default.
Patch
From c2ca57044b1fe69ab46bff64e914f50a389e59d6 Mon Sep 17 00:00:00 2001 From: Aman Verma <[email protected]> Date: Thu, 30 Jul 2026 10:12:19 -0700 Subject: [PATCH] Disallow background window container type on non-extension builds ChromeContentBrowserClient::CanCreateWindow() previously returned true unconditionally for WindowContainerType::BACKGROUND on non-extension builds (such as Chrome for Android) where ENABLE_EXTENSIONS_CORE is false. This allowed ordinary web content to bypass popup blocking when calling window.open(url, name, 'background'). This change explicitly returns false for WindowContainerType::BACKGROUND when ENABLE_EXTENSIONS_CORE is false, matching the behavior on Desktop where ordinary web content lacking the extension background permission is rejected. A PlatformBrowserTest is added to platform_browser_tests (included in both browser_tests and android_browsertests) to assert that calling window.open(..., 'background') from ordinary web content returns null, even when executed with transient user activation. Furthermore, it verifies that no WebContents is created and includes a positive control assertion that a standard popup without 'background' succeeds. Bug: 520201931 Change-Id: I7b532a41121c6e2fb3eec984cfd88a5e37149d51 Fixed: 520201931 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8176283 Commit-Queue: Aman Verma <[email protected]> Reviewed-by: Dana Fried <[email protected]> Cr-Commit-Position: refs/heads/main@{#1671184} --- diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index b121c619..dd11264 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -4518,9 +4518,11 @@ if (extension && !extensions::BackgroundInfo::AllowJSAccess(extension)) { *no_javascript_access = true; } -#endif return true; +#else + return false; +#endif } #if BUILDFLAG(ENABLE_EXTENSIONS_CORE) && BUILDFLAG(ENABLE_GUEST_VIEW) diff --git a/chrome/browser/chrome_content_browser_client_platform_browsertest.cc b/chrome/browser/chrome_content_browser_client_platform_browsertest.cc new file mode 100644 index 0000000..f183e34a --- /dev/null +++ b/chrome/browser/chrome_content_browser_client_platform_browsertest.cc @@ -0,0 +1,69 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/test/base/chrome_test_utils.h" +#include "chrome/test/base/platform_browser_test.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace { + +class ChromeContentBrowserClientPlatformBrowserTest + : public PlatformBrowserTest { + public: + void SetUpOnMainThread() override { + PlatformBrowserTest::SetUpOnMainThread(); + host_resolver()->AddRule("*", "127.0.0.1"); + ASSERT_TRUE(embedded_test_server()->Start()); + } + + content::WebContents* web_contents() { + return chrome_test_utils::GetActiveWebContents(this); + } +}; + +// window.open() with the 'background' window feature from ordinary web content +// must not be allowed to open a new window, even with transient user +// activation. +IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientPlatformBrowserTest, + BackgroundFeatureFromOrdinaryWebContentIsBlocked) { + ASSERT_TRUE(content::NavigateToURL( + web_contents(), embedded_test_server()->GetURL("/title1.html"))); + + const size_t initial_web_contents_count = content::GetAllWebContents().size(); + + // 1. Attempt to open a window with the 'background' feature. + // Using default EvalJs options executes with user_gesture = true, which is + // critical: without a user gesture, the standard popup blocker would reject + // the call before reaching ChromeContentBrowserClient::CanCreateWindow(). + // Even with user activation, ordinary web content lacks extension background + // permissions and must be refused when requesting 'background' window + // features. + EXPECT_EQ(true, + content::EvalJs( + web_contents(), + "window.open('/title2.html', '', 'background') === null")); + + // Verify browser-side state: no new WebContents or tab was created. + EXPECT_EQ(initial_web_contents_count, content::GetAllWebContents().size()); + + // 2. Control assertion: verify that an ordinary popup without 'background' + // succeeds with transient user activation, proving that the failure above + // was specifically due to the 'background' window feature and not because + // popups are globally disabled in the test environment. + EXPECT_EQ(true, + content::EvalJs(web_contents(), + "window.open('/title2.html', '', '') !== null")); + + // Verify that exactly one new WebContents was created by the control popup. + EXPECT_EQ(initial_web_contents_count + 1, + content::GetAllWebContents().size()); +} + +} // namespace diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index c56e0ea..24995b9 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn @@ -1021,6 +1021,7 @@ defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] sources = [ + "../browser/chrome_content_browser_client_platform_browsertest.cc", "../browser/loadtimes_extension_bindings_browsertest.cc", "../browser/no_best_effort_tasks_browsertest.cc", "../browser/safe_browsing/client_side_detection_service_browsertest.cc",
Regression Test / PoC
diff --git a/chrome/browser/chrome_content_browser_client_platform_browsertest.cc b/chrome/browser/chrome_content_browser_client_platform_browsertest.cc
new file mode 100644
index 0000000..f183e34a
--- /dev/null
+++ b/chrome/browser/chrome_content_browser_client_platform_browsertest.cc
@@ -0,0 +1,69 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/platform_browser_test.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+class ChromeContentBrowserClientPlatformBrowserTest
+ : public PlatformBrowserTest {
+ public:
+ void SetUpOnMainThread() override {
+ PlatformBrowserTest::SetUpOnMainThread();
+ host_resolver()->AddRule("*", "127.0.0.1");
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ content::WebContents* web_contents() {
+ return chrome_test_utils::GetActiveWebContents(this);
+ }
+};
+
+// window.open() with the 'background' window feature from ordinary web content
+// must not be allowed to open a new window, even with transient user
+// activation.
+IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientPlatformBrowserTest,
+ BackgroundFeatureFromOrdinaryWebContentIsBlocked) {
+ ASSERT_TRUE(content::NavigateToURL(
+ web_contents(), embedded_test_server()->GetURL("/title1.html")));
+
+ const size_t initial_web_contents_count = content::GetAllWebContents().size();
+
+ // 1. Attempt to open a window with the 'background' feature.
+ // Using default EvalJs options executes with user_gesture = true, which is
+ // critical: without a user gesture, the standard popup blocker would reject
+ // the call before reaching ChromeContentBrowserClient::CanCreateWindow().
+ // Even with user activation, ordinary web content lacks extension background
+ // permissions and must be refused when requesting 'background' window
+ // features.
+ EXPECT_EQ(true,
+ content::EvalJs(
+ web_contents(),
+ "window.open('/title2.html', '', 'background') === null"));
+
+ // Verify browser-side state: no new WebContents or tab was created.
+ EXPECT_EQ(initial_web_contents_count, content::GetAllWebContents().size());
+
+ // 2. Control assertion: verify that an ordinary popup without 'background'
+ // succeeds with transient user activation, proving that the failure above
+ // was specifically due to the 'background' window feature and not because
+ // popups are globally disabled in the test environment.
+ EXPECT_EQ(true,
+ content::EvalJs(web_contents(),
+ "window.open('/title2.html', '', '') !== null"));
+
+ // Verify that exactly one new WebContents was created by the control popup.
+ EXPECT_EQ(initial_web_contents_count + 1,
+ content::GetAllWebContents().size());
+}
+
+} // namespace
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index c56e0ea..24995b9 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1021,6 +1021,7 @@
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
sources = [
+ "../browser/chrome_content_browser_client_platform_browsertest.cc",
"../browser/loadtimes_extension_bindings_browsertest.cc",
"../browser/no_best_effort_tasks_browsertest.cc",
"../browser/safe_browsing/client_side_detection_service_browsertest.cc",
Original Bug Report
Potential popup blocker bypass on Chrome for Android via window.open background feature
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential vulnerability in Chrome for Android allows arbitrary web pages to bypass the popup blocker and programmatically open new tabs without user activation. This occurs because the extension-only checks inside the browser client are compiled out on Android, leaving an unconditional early return when handling the ‘background’ window container type. Consequently, the popup blocking logic is bypassed, allowing automated popup spam or tab creation.
Affected files:
chrome/browser/chrome_content_browser_client.ccthird_party/blink/renderer/core/page/create_window.cccontent/renderer/render_frame_impl.cccontent/browser/renderer_host/ipc_utils.cccomponents/embedder_support/android/delegate/web_contents_delegate_android.cc
Estimated timestamp from git blame: 2014-07-16
Potential Popup Blocker Bypass on Chrome for Android via window.open('background')
Summary
We have identified a potential security vulnerability in Chrome for Android where the browser’s popup blocker can be bypassed without requiring a compromised renderer or user activation. By calling window.open(url, '_blank', 'background'), any web page can programmatically open new tabs, leading to potential popup spam, clickjacking, and phishing abuse.
Vulnerability Mechanics
When a page calls window.open(..., 'background'), the window opening flow executes as follows:
- Feature Parsing: In
third_party/blink/renderer/core/page/create_window.ccaround line 210, the renderer unconditionally parses the'background'keyword and setswindow_features.background = true. - Parameters Mapping: In
content/renderer/render_frame_impl.cc:6862,WindowFeaturesToContainerTypemaps this feature tocontent::mojom::WindowContainerType::BACKGROUNDinsideparams->window_container_type. - IPC Transfer: The renderer sends these parameters via the synchronous
CreateNewWindowIPC interface to the browser process (content/browser/renderer_host/render_frame_host_impl.cc:10274). - Browser Policy Evaluation: The browser process delegates the request validation to the content embedder’s
CanCreateWindowinterface. Inchrome/browser/chrome_content_browser_client.ccaround lines 4529–4553:Because Chrome for Android compiles without extension support (if (container_type == content::mojom::WindowContainerType::BACKGROUND) { #if BUILDFLAG(ENABLE_EXTENSIONS_CORE) // Extension-specific background permission checks ... #endif return true; }ENABLE_EXTENSIONS_COREis false), the compiler strips the permission check inside the#ifblock. This results in an unconditional earlyreturn true;for any page requesting aBACKGROUNDcontainer type. - Blocker Bypass: Due to this early return, the execution path never reaches the end of the
CanCreateWindowfunction whereConsiderForPopupBlockingandMaybeBlockPopupare invoked to block un-activated window creations. As a result, the request is unconditionally approved and processed as a normal tab creation on Android.
Potential Attack Vector
An attacker could potentially trigger this bypass from any untrusted web context by serving a page with the following snippet:
<script>
// Potential trigger vector: opens a background tab without user gesture
window.open('https://example.com/', '_blank', 'background');
</script>
Note: These steps represent a potential trigger flow. Our tooling does not currently have the capacity to dynamically execute and verify the POC on Chrome for Android.
Recommended Mitigation
We suggest the following structural changes to resolve this potential issue:
- Move the
return true;statement atchrome/browser/chrome_content_browser_client.cc:4552inside the#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)block. In environments without extensions support, the handler should fall through to standard popup blocking validation. - In
content/browser/renderer_host/ipc_utils.cc:VerifyCreateNewWindowParams, add defense-in-depth checks to validate and reject requests specifyingWindowContainerType::BACKGROUNDorPERSISTENTfrom non-privileged/non-extension renderer processes.
Evaluated with Chrome root at commit: d8b226a3be7c9c1ac9240c09e14698866c82e4ac
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.