CVE-2026-12467
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/extensions/api/developer_private/developer_private_functions.cc |
modified |
Files Changed
chrome/browser/extensions/api/developer_private/developer_private_functions.cc
Patch
From af2cf051f9ddf3eb37ea5b0f675ef7deef8ea4e1 Mon Sep 17 00:00:00 2001 From: Andrea Orru <[email protected]> Date: Fri, 05 Jun 2026 17:10:49 -0700 Subject: [PATCH] [Extensions] Fix UAF in developerPrivate.updateExtensionConfiguration Modifying file_access or incognito_access synchronously reloads the extension, destroying the old Extension object. Subsequent operations in DeveloperPrivateUpdateExtensionConfigurationFunction::Run() would dereference the dangling pointer. This fix: 1. Replaces extension->id() with update.extension_id to avoid unnecessary dereferences and prevent UAF during reload helper calls. 2. Re-fetches the extension pointer from ExtensionRegistry after potential reload operations and adds defensive null checks. Fixed: 520202726 Change-Id: I4138c3f1bdd5f857c4c4729ff165f0f3d1d5a062 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7904810 Reviewed-by: Devlin Cronin <[email protected]> Commit-Queue: Andrea Orru <[email protected]> Cr-Commit-Position: refs/heads/main@{#1642683} --- diff --git a/chrome/browser/extensions/api/developer_private/developer_private_functions.cc b/chrome/browser/extensions/api/developer_private/developer_private_functions.cc index 9c834fc..046288f 100644 --- a/chrome/browser/extensions/api/developer_private/developer_private_functions.cc +++ b/chrome/browser/extensions/api/developer_private/developer_private_functions.cc @@ -632,22 +632,35 @@ } if (update.file_access) { - util::SetAllowFileAccess(extension->id(), browser_context(), + // `util::SetAllowFileAccess()` can synchronously reload the extension, + // invalidating the `extension` pointer. We pass `update.extension_id` + // to ensure the ID reference stays alive, and re-fetch the pointer after. + util::SetAllowFileAccess(update.extension_id, browser_context(), *update.file_access); + extension = GetExtensionById(update.extension_id); + if (!extension) { + return RespondNow(LogNoSuchExtensionFoundAndReturn()); + } } if (update.incognito_access) { - util::SetIsIncognitoEnabled(extension->id(), browser_context(), + // `util::SetIsIncognitoEnabled()` can also synchronously reload the + // extension. + util::SetIsIncognitoEnabled(update.extension_id, browser_context(), *update.incognito_access); + extension = GetExtensionById(update.extension_id); + if (!extension) { + return RespondNow(LogNoSuchExtensionFoundAndReturn()); + } } if (update.user_scripts_access) { ExtensionSystem::Get(browser_context()) ->user_script_manager() - ->SetUserScriptPrefEnabled(extension->id(), + ->SetUserScriptPrefEnabled(update.extension_id, *update.user_scripts_access); } if (update.error_collection) { ErrorConsole::Get(browser_context()) - ->SetReportingAllForExtension(extension->id(), + ->SetReportingAllForExtension(update.extension_id, *update.error_collection); } if (update.host_access != developer::HostAccess::kNone) { @@ -679,31 +692,31 @@ developer_private::SafetyCheckWarningReason::kNone) { ExtensionPrefs::Get(browser_context()) ->SetIntegerPref( - extension->id(), kPrefAcknowledgeSafetyCheckWarningReason, + update.extension_id, kPrefAcknowledgeSafetyCheckWarningReason, static_cast<int>(update.acknowledge_safety_check_warning_reason)); DeveloperPrivateEventRouter* event_router = DeveloperPrivateAPI::Get(browser_context()) ->developer_private_event_router(); if (event_router) { - event_router->OnExtensionConfigurationChanged(extension->id()); + event_router->OnExtensionConfigurationChanged(update.extension_id); } } if (update.show_access_requests_in_toolbar) { SitePermissionsHelper(Profile::FromBrowserContext(browser_context())) .SetShowAccessRequestsInToolbar( - extension->id(), *update.show_access_requests_in_toolbar); + update.extension_id, *update.show_access_requests_in_toolbar); } if (update.pinned_to_toolbar) { ToolbarActionsModel* toolbar_actions_model = ToolbarActionsModel::Get( Profile::FromBrowserContext(browser_context())); - if (!toolbar_actions_model->HasAction(extension->id())) { + if (!toolbar_actions_model->HasAction(update.extension_id)) { return RespondNow(Error(kCannotSetPinnedWithoutAction)); } bool is_action_pinned = - toolbar_actions_model->IsActionPinned(extension->id()); + toolbar_actions_model->IsActionPinned(update.extension_id); if (is_action_pinned != *update.pinned_to_toolbar) { - toolbar_actions_model->SetActionVisibility(extension->id(), + toolbar_actions_model->SetActionVisibility(update.extension_id, !is_action_pinned); } }
Original Bug Report
Use-After-Free in developerPrivate.updateExtensionConfiguration due to synchronous reload
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 Use-After-Free (UAF) vulnerability exists in the browser process of Chromium within the developerPrivate.updateExtensionConfiguration API. Modifying certain configuration options (like file access or incognito access) synchronously reloads the extension, destroying the heap-allocated Extension object. Subsequent operations in the same execution path then dereference the now-dangling raw pointer.
Affected files:
chrome/browser/extensions/api/developer_private/developer_private_functions.cc
Estimated timestamp from git blame: 2015-03-19
Detailed Description
Within DeveloperPrivateUpdateExtensionConfigurationFunction::Run() (chrome/browser/extensions/api/developer_private/developer_private_functions.cc), a raw pointer to the target Extension is obtained and stored on the stack:
const Extension* extension = GetExtensionById(update.extension_id);
No scoped_refptr is taken to keep the Extension object alive during the function’s execution.
When the configuration update contains a change to file_access (line 634) or incognito_access (line 638), the function invokes helper methods that synchronously trigger an extension reload:
util::SetAllowFileAccess(...)->ReloadExtension(...)util::SetIsIncognitoEnabled(...)->ReloadExtensionIfEnabled(...)
For a packed extension (e.g., a Web Store CRX), the reload flow is entirely synchronous:
ExtensionRegistrar::DoReloadExtensiondisables the extension, moving thescoped_refptrreference to thedisabled_extensions_set.ChromeExtensionRegistrarDelegate::LoadExtensionForReloadcallsInstalledLoader(profile_).Load(...)synchronously using cached in-memory manifest data.- This constructs a brand-new
Extensionobject on the heap and adds it viaExtensionRegistrar::AddExtension. ReplaceReloadedExtensionexecutesregistry_->RemoveDisabled(extension->id()), which drops the final reference to the oldExtensionobject, destroying it synchronously.
When control returns to DeveloperPrivateUpdateExtensionConfigurationFunction::Run(), the stack-local extension pointer is dangling. Any subsequent configuration fields present in the same update request (such as user_scripts_access, error_collection, or host_access) will dereference this freed pointer. Most notably, constructing the ScriptingPermissionsModifier on line 659:
ScriptingPermissionsModifier modifier(browser_context(), extension);
creates a temporary scoped_refptr from the dangling raw pointer. This results in an AddRef() operation on freed memory, followed by a Release() on scope exit, potentially leading to a double-free or arbitrary destructor execution.
Potential Steps to Trigger the Vulnerability
Note: Our tooling does not currently have the capability to execute code or run dynamic proofs of concept. The following steps are theoretical and based on manual analysis of the static code flow.
- A victim user must have one of seven deprecated platform apps (such as the Chrome Dev Editor, ID
AE27D69DBE571F4B1694F05C89B710C646792231) and any packed extension installed. - An attacker compromises the renderer process hosting the allowlisted developer app.
- The compromised renderer dispatches a
LocalFrameHost.RequestMojo message callingdeveloperPrivate.updateExtensionConfigurationwithuser_gesture = true, targetextension_id, and bothfile_accessandincognito_accesstoggles set to values that differ from current settings. - In the browser process,
Run()executes, reloads the extension synchronously via the first configuration handler, and frees the oldExtensionheap allocation. - The execution proceeds to the next handlers within the same task, dereferencing the dangling pointer and triggering the Use-After-Free condition.
Limitations on Exploitation
- Task Boundary Constraints: Because the reload and subsequent dereferences occur entirely synchronously within a single UI thread task, the browser does not return to the message loop. Consequently, Mojo message queues cannot be processed in the interim, making it extremely difficult to reliably race and reclaim the freed heap allocation with controlled bytes before the UAF occurs.
- Strict Preconditions: The API is restricted to
chrome://extensions(WebUI, which is trusted) and seven hard-coded developer platform apps which are deprecated and generally inactive.
Suggested Fix
To prevent the UAF, copy the ExtensionId of the target extension and hold a scoped_refptr<const Extension> on the stack, or re-fetch the pointer from the ExtensionRegistry if any operation that can trigger an extension reload has been executed.
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.