CVE-2026-13822
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
registry_for_active_window_chrome/browser/extensions/extension_commands_global_registry.cc |
modified | |
shortcut_handling_suspended_chrome/browser/extensions/extension_keybinding_registry.cc |
modified | |
ifchrome/browser/extensions/extension_keybinding_registry.cc |
modified | |
TabListInterfacechrome/browser/extensions/extension_keybinding_registry.h |
modified | |
BrowserContextchrome/browser/extensions/extension_keybinding_registry.h |
modified | |
WebContentschrome/browser/extensions/extension_keybinding_registry.h |
modified | |
Delegatechrome/browser/extensions/extension_keybinding_registry.h |
modified | |
ExtensionKeybindingRegistryDelegateAndroidchrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc |
modified | |
toolbar_view_model_chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc |
modified |
Files Changed
chrome/browser/extensions/extension_commands_global_registry.ccchrome/browser/extensions/extension_keybinding_registry.ccchrome/browser/extensions/extension_keybinding_registry.hchrome/browser/ui/android/extensions/extension_keybinding_registry_android.ccchrome/browser/ui/android/extensions/extension_keybinding_registry_android.h
Patch
From f08221cc7cb21bd45158ef18cc17bc1e82e6403f Mon Sep 17 00:00:00 2001 From: Hidehiko Abe <[email protected]> Date: Sun, 17 May 2026 19:54:43 -0700 Subject: [PATCH] Remove ExtensionKeybingindRegistry::Delegate. The delegate was designed to inject active tab, which needed different operations depending on platforms. Now, we have TabListInterface, abstracting the platform gap, so we can simply rely on the code. BUG=513148038 TEST=Tryjob Change-Id: I3e09b8171d0291960f2a859a51b97863b68be466 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7851696 Reviewed-by: James Cook <[email protected]> Commit-Queue: Hidehiko Abe <[email protected]> Reviewed-by: Masa Fujita <[email protected]> Cr-Commit-Position: refs/heads/main@{#1631947} --- diff --git a/chrome/browser/extensions/extension_commands_global_registry.cc b/chrome/browser/extensions/extension_commands_global_registry.cc index cd22521f..bc6a5f1 100644 --- a/chrome/browser/extensions/extension_commands_global_registry.cc +++ b/chrome/browser/extensions/extension_commands_global_registry.cc @@ -67,8 +67,8 @@ ExtensionCommandsGlobalRegistry::ExtensionCommandsGlobalRegistry( content::BrowserContext* context) : ExtensionKeybindingRegistry(context, - ExtensionKeybindingRegistry::ALL_EXTENSIONS, - nullptr), + /*tab_list_interface=*/nullptr, + ExtensionKeybindingRegistry::ALL_EXTENSIONS), browser_context_(context), registry_for_active_window_(nullptr) { Init(); diff --git a/chrome/browser/extensions/extension_keybinding_registry.cc b/chrome/browser/extensions/extension_keybinding_registry.cc index 99308a1..4fec35c 100644 --- a/chrome/browser/extensions/extension_keybinding_registry.cc +++ b/chrome/browser/extensions/extension_keybinding_registry.cc @@ -12,6 +12,7 @@ #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/tab_list/tab_list_interface.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/media_keys_listener_manager.h" #include "content/public/browser/web_contents.h" @@ -40,11 +41,11 @@ ExtensionKeybindingRegistry::ExtensionKeybindingRegistry( content::BrowserContext* context, - ExtensionFilter extension_filter, - std::unique_ptr<Delegate> delegate) + TabListInterface* tab_list_interface, + ExtensionFilter extension_filter) : browser_context_(context), + tab_list_interface_(tab_list_interface), extension_filter_(extension_filter), - delegate_(std::move(delegate)), shortcut_handling_suspended_(false) { extension_registry_observation_.Observe( ExtensionRegistry::Get(browser_context_)); @@ -185,12 +186,13 @@ args.Append(command); base::Value tab_value; - if (delegate_) { + if (tab_list_interface_) { + auto* active_tab = tab_list_interface_->GetActiveTab(); content::WebContents* web_contents = - delegate_->GetWebContentsForExtension(); + active_tab ? active_tab->GetContents() : nullptr; // Grant before sending the event so that the permission is granted before // the extension acts on the command. NOTE: The Global Commands handler does - // not set the delegate as it deals only with named commands (not + // not set the TabListInterface as it deals only with named commands (not // page/browser actions that are associated with the current page directly). ActiveTabPermissionGranter* granter = web_contents ? ActiveTabPermissionGranter::FromWebContents(web_contents) diff --git a/chrome/browser/extensions/extension_keybinding_registry.h b/chrome/browser/extensions/extension_keybinding_registry.h index 00a91f6..aa7068c8 100644 --- a/chrome/browser/extensions/extension_keybinding_registry.h +++ b/chrome/browser/extensions/extension_keybinding_registry.h @@ -21,9 +21,10 @@ static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE)); +class TabListInterface; + namespace content { class BrowserContext; -class WebContents; } namespace ui { @@ -46,19 +47,13 @@ PLATFORM_APPS_ONLY }; - class Delegate { - public: - virtual ~Delegate() = default; - - // Returns the currently active WebContents, or nullptr if there is none. - virtual content::WebContents* GetWebContentsForExtension() = 0; - }; - // If `extension_filter` is not ALL_EXTENSIONS, only keybindings by // by extensions that match the filter will be registered. + // `tab_list_interface` is the one for the connected browser window + // instance. It must outlive this instance. ExtensionKeybindingRegistry(content::BrowserContext* context, - ExtensionFilter extension_filter, - std::unique_ptr<Delegate> delegate); + TabListInterface* tab_list_interface, + ExtensionFilter extension_filter); ExtensionKeybindingRegistry(const ExtensionKeybindingRegistry&) = delete; ExtensionKeybindingRegistry& operator=(const ExtensionKeybindingRegistry&) = @@ -176,11 +171,11 @@ raw_ptr<content::BrowserContext> browser_context_; + const raw_ptr<TabListInterface> tab_list_interface_; + // What extensions to register keybindings for. ExtensionFilter extension_filter_; - std::unique_ptr<Delegate> delegate_; - // Maps an accelerator to a list of string pairs (extension id, command name) // for commands that have been registered. This keeps track of the targets for // the keybinding event (which named command to call in which extension). On diff --git a/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc b/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc index 6807b43d4..d26f431 100644 --- a/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc +++ b/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc @@ -21,49 +21,14 @@ #include "ui/events/platform_event.h" namespace extensions { -namespace { - -class ExtensionKeybindingRegistryDelegateAndroid - : public ExtensionKeybindingRegistry::Delegate { - public: - explicit ExtensionKeybindingRegistryDelegateAndroid( - content::BrowserContext* context) - : context_(context) {} - - ExtensionKeybindingRegistryDelegateAndroid( - const ExtensionKeybindingRegistryDelegateAndroid& other) = delete; - ExtensionKeybindingRegistryDelegateAndroid& operator=( - const ExtensionKeybindingRegistryDelegateAndroid& other) = delete; - - ~ExtensionKeybindingRegistryDelegateAndroid() override = default; - - content::WebContents* GetWebContentsForExtension() override { - for (const TabModel* model : TabModelList::models()) { - if (model->GetProfile() != context_) { - continue; - } - if (model->IsActiveModel()) { - return model->GetActiveWebContents(); - } - } - - return nullptr; - } - - private: - const raw_ptr<content::BrowserContext> context_; -}; - -} // namespace ExtensionKeybindingRegistryAndroid::ExtensionKeybindingRegistryAndroid( content::BrowserContext* context, + TabListInterface* tab_list_interface, ExtensionsToolbarViewModel* toolbar_view_model) - : ExtensionKeybindingRegistry( - context, - ExtensionFilter::ALL_EXTENSIONS, - std::make_unique<ExtensionKeybindingRegistryDelegateAndroid>( - context)), + : ExtensionKeybindingRegistry(context, + tab_list_interface, + ExtensionFilter::ALL_EXTENSIONS), toolbar_view_model_(toolbar_view_model) { Init(); } diff --git a/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h b/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h index f1a41e44..7fa61ee6 100644 --- a/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h +++ b/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h @@ -9,6 +9,8 @@ #include "chrome/browser/ui/extensions/extensions_toolbar_view_model.h" #include "ui/base/accelerators/accelerator.h"
Original Bug Report
Potential activeTab permission misrouting in Desktop-Android multi-instance scenarios
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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: In experimental Desktop-Android builds, extension keyboard shortcuts resolve the active tab using a global list, ignoring window boundaries. This may allow an extension to gain host access to a sensitive site in a background window when the user interacts with the extension in a separate foreground window.
Affected files:
chrome/browser/ui/android/extensions/extension_keybinding_registry_android.ccchrome/browser/ui/android/extensions/extensions_toolbar_android.cc
Estimated timestamp from git blame: 2026-03-05
Summary
In the experimental Desktop-Android implementation of Chrome (enabled via the GN argument is_desktop_android = true), extension keyboard shortcuts (commands) may incorrectly resolve the target WebContents by iterating through a process-global TabModelList. In multi-instance or split-screen scenarios, this logic fails to account for multiple windows each having its own ‘active’ tab model. This potentially allows an extension to be granted the activeTab permission for a sensitive site open in a background window, even when the user intended to trigger the shortcut on a different foreground window.
Root Cause Analysis
In chrome/browser/ui/android/extensions/extensions_toolbar_android.cc, the ExtensionsToolbarAndroid instance is window-scoped, but it initializes its keybinding registry delegate without a reference to its parent window:
keybinding_registry_(std::make_unique<ExtensionKeybindingRegistryAndroid>(
browser_->GetProfile(),
toolbar_view_model_.get())),
The registry’s delegate, ExtensionKeybindingRegistryDelegateAndroid, implements GetWebContentsForExtension in chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc by searching through all available tab models in the process:
content::WebContents* GetWebContentsForExtension() override {
for (const TabModel* model : TabModelList::models()) {
if (model->GetProfile() != context_) {
continue;
}
if (model->IsActiveModel()) {
return model->GetActiveWebContents();
}
}
return nullptr;
}
TabModelList::models() is a process-global singleton containing TabModel instances from all open windows. The TabModel::IsActiveModel() property indicates whether a model is currently selected (e.g., regular vs. incognito) within its respective window’s TabModelSelector.
In a multi-window environment where two regular-profile windows are open, both regular TabModel instances will return true for IsActiveModel(). Because the delegate iterates through the global list in insertion order, it will deterministically return the WebContents from the window that was opened first, regardless of which window actually received the keyboard event.
Security Impact
The activeTab permission model ensures that an extension only receives host access to the specific tab the user is actively targeting via a deliberate interaction. This logic flaw breaks that security boundary by misrouting the permission grant to a background window. A malicious extension with a configured keyboard shortcut and activeTab permission could gain cross-origin scripting access and read sensitive data from a high-value site in a background window when the user intended to invoke the shortcut on a benign site in the foreground.
Potential Reproduction Steps
Note: These steps are based on source code analysis of the experimental Desktop-Android feature.
- Build Chrome for Android with the GN argument
is_desktop_android = true. - Install an extension that requests the
activeTabpermission and defines a keyboard command (e.g.,Ctrl+Shift+Y) in its manifest. - Open Window A and navigate its active tab to a sensitive site (e.g.,
https://bank.example). - Using Android multi-instance features (e.g., split-screen), open Window B and navigate its active tab to a benign site (e.g.,
https://news.example). - Focus Window B and press the extension’s keyboard shortcut on a hardware keyboard.
- The extension’s command listener may receive the
tabobject for the site in Window A instead of the intended site in Window B, effectively granting the extension unauthorized access to Window A.
Suggested Fix
The ExtensionKeybindingRegistryDelegateAndroid should be scoped to its specific window. Instead of searching the global TabModelList, the delegate should be initialized with a reference to the BrowserWindowInterface (available in ExtensionsToolbarAndroid) and use that reference to resolve the active WebContents within the correct window context.
Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e
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.