CVE-2026-17778
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fextensions/renderer/bindings/api_bindings_system_unittest.cc |
modified |
Files Changed
extensions/renderer/bindings/api_bindings_system.ccextensions/renderer/bindings/api_bindings_system_unittest.cc
Patch
From 000042caf489a901791f4fd73927ca5e727ba0a7 Mon Sep 17 00:00:00 2001 From: Tim Judkins <[email protected]> Date: Wed, 24 Jun 2026 11:11:33 -0700 Subject: [PATCH] Reland "[Extensions] Upgrade APIBindingsSystem::InitializeType DCHECK to CHECK" This is a reland of commit e4ad067e9374dd36120f04a75639e86523e20409 The reland changes the EXPECT_DEATH_IF_SUPPORTED call to instead use EXPECT_CHECK_DEATH. The original CL had to be reverted due to the previous test failing on a chromium-os uprev, as the crash message gets stripped for the ChromeOS release build so it was crashing but not with the expected error. Original change's description: > [Extensions] Upgrade APIBindingsSystem::InitializeType DCHECK to CHECK > > This CL updates a DCHECK to a CHECK in APIBindingsSystem::InitializeType > so if the requested API is already present in the api_bindings_ map, the > renderer is killed. > > This prevents an issue where requesting an unknown type for an already > instantiated API would unconditionally overwrite and destroy the > existing APIBinding object. Destroying the binding could cause a > Use-After-Free if any v8::Functions created by it (which hold raw > pointers to the binding's internal data) were subsequently invoked. > > An expect-death test has also been added to document this. > > Fixed: 513467993 > Change-Id: I676a60ba207daa76cbdc67e5d2aca2db4d6a3a30 > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7934732 > Reviewed-by: Devlin Cronin <[email protected]> > Commit-Queue: Tim <[email protected]> > Cr-Commit-Position: refs/heads/main@{#1648609} Fixed: 513467993, 525219560 Change-Id: I2d0bbbf23b2752af87a43f5d140ae9742f73b0f5 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7981019 Commit-Queue: Tim <[email protected]> Reviewed-by: Devlin Cronin <[email protected]> Cr-Commit-Position: refs/heads/main@{#1651840} --- diff --git a/extensions/renderer/bindings/api_bindings_system.cc b/extensions/renderer/bindings/api_bindings_system.cc index c9ca2f4..81f305e 100644 --- a/extensions/renderer/bindings/api_bindings_system.cc +++ b/extensions/renderer/bindings/api_bindings_system.cc @@ -111,7 +111,7 @@ std::string api_name = type_name.substr(0, dot); // If we've already instantiated the binding, the type should have been in // there. - DCHECK(!api_bindings_.contains(api_name)) << api_name; + CHECK(!api_bindings_.contains(api_name)) << api_name; api_bindings_[api_name] = CreateNewAPIBinding(api_name); } diff --git a/extensions/renderer/bindings/api_bindings_system_unittest.cc b/extensions/renderer/bindings/api_bindings_system_unittest.cc index 6d652fe..a1fc914 100644 --- a/extensions/renderer/bindings/api_bindings_system_unittest.cc +++ b/extensions/renderer/bindings/api_bindings_system_unittest.cc @@ -7,6 +7,7 @@ #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/strings/stringprintf.h" +#include "base/test/gtest_util.h" #include "extensions/common/mojom/event_dispatcher.mojom.h" #include "extensions/renderer/bindings/api_binding.h" #include "extensions/renderer/bindings/api_binding_hooks.h" @@ -612,4 +613,21 @@ EXPECT_NE(event, other_event); } +// Tests that requesting an unknown type for an already-instantiated API +// crashes the renderer. In theory this could be triggered by a compromised +// renderer mishandling bindingUtil, so we just kill the renderer if it happens. +TEST_F(APIBindingsSystemTest, DeathOnUnknownTypeInitialize) { + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = MainContext(); + + // Instantiate the 'alpha' API. + v8::Local<v8::Object> alpha_api = + bindings_system()->CreateAPIInstance(kAlphaAPIName, context, nullptr); + ASSERT_FALSE(alpha_api.IsEmpty()); + + // Trigger InitializeType for the 'alpha' API with an unknown type. + EXPECT_CHECK_DEATH( + bindings_system()->type_reference_map()->GetSpec("alpha.doesNotExist")); +} + } // namespace extensions
Regression Test / PoC
diff --git a/extensions/renderer/bindings/api_bindings_system_unittest.cc b/extensions/renderer/bindings/api_bindings_system_unittest.cc
index 6d652fe..a1fc914 100644
--- a/extensions/renderer/bindings/api_bindings_system_unittest.cc
+++ b/extensions/renderer/bindings/api_bindings_system_unittest.cc
@@ -7,6 +7,7 @@
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/stringprintf.h"
+#include "base/test/gtest_util.h"
#include "extensions/common/mojom/event_dispatcher.mojom.h"
#include "extensions/renderer/bindings/api_binding.h"
#include "extensions/renderer/bindings/api_binding_hooks.h"
@@ -612,4 +613,21 @@
EXPECT_NE(event, other_event);
}
+// Tests that requesting an unknown type for an already-instantiated API
+// crashes the renderer. In theory this could be triggered by a compromised
+// renderer mishandling bindingUtil, so we just kill the renderer if it happens.
+TEST_F(APIBindingsSystemTest, DeathOnUnknownTypeInitialize) {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ // Instantiate the 'alpha' API.
+ v8::Local<v8::Object> alpha_api =
+ bindings_system()->CreateAPIInstance(kAlphaAPIName, context, nullptr);
+ ASSERT_FALSE(alpha_api.IsEmpty());
+
+ // Trigger InitializeType for the 'alpha' API with an unknown type.
+ EXPECT_CHECK_DEATH(
+ bindings_system()->type_reference_map()->GetSpec("alpha.doesNotExist"));
+}
+
} // namespace extensions
Original Bug Report
Potential Use-After-Free in APIBindingsSystem::InitializeType via unsafe re-initialization
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 logic error in the extension binding system’s lazy-initialization path allows a live APIBinding object to be destroyed while V8 still holds raw pointers to its internal structures. This leads to a potential Use-After-Free (UAF) vulnerability in the renderer process.
Affected files:
extensions/renderer/bindings/api_bindings_system.ccextensions/renderer/bindings/api_binding.ccextensions/renderer/bindings/api_binding_js_util.ccextensions/renderer/bindings/api_type_reference_map.ccextensions/renderer/native_extension_bindings_system.cc
Estimated timestamp from git blame: 2017-02-10
Description
A potential Use-After-Free (UAF) vulnerability exists in the extension renderer’s API binding system. The issue is located in APIBindingsSystem::InitializeType, which is responsible for the lazy initialization of extension APIs. The function unconditionally replaces existing APIBinding objects in its internal map, leading to the destruction of objects that V8 still references via raw pointers.
Root Cause Analysis
In extensions/renderer/bindings/api_bindings_system.cc, the InitializeType function performs the following logic:
void APIBindingsSystem::InitializeType(const std::string& type_name) {
// ...
std::string api_name = type_name.substr(0, dot);
// If we've already instantiated the binding, the type should have been in there.
DCHECK(!api_bindings_.contains(api_name)) << api_name; // Stripped in release builds
api_bindings_[api_name] = CreateNewAPIBinding(api_name); // Unconditional overwrite
}
In release builds, the DCHECK is removed. If InitializeType is triggered for an API that is already loaded (for example, by requesting a non-existent method or type name with a valid API prefix), the move-assignment destroys the existing unique_ptr<APIBinding>.
When an APIBinding is created, it initializes V8 functions for its methods. These functions use v8::External objects to store raw pointers to MethodData::callback members, which are owned by the APIBinding object. Although the APIBinding is destroyed, the V8 functions may persist in V8’s per-context cache. When these functions are later called, the system attempts to dereference the dangling pointer stored in the v8::External to execute the callback.
Potential Exploitation Path
While we do not have a working proof-of-concept, an attacker could potentially follow these steps within an extension renderer process:
- Obtain a reference to a cached API method (e.g.,
const staleFunc = chrome.runtime.sendMessage;). This V8 function now holds av8::Externalwith a raw pointer to internal C++ structures. - Trigger a call to
InitializeTypefor the already-loaded “runtime” API using a non-existent member name (e.g., viabindingUtil.validateType('runtime.bogus', {})). This causes the destruction of the originalAPIBindingfor the “runtime” API. - Use a JavaScript reentrancy window (such as a getter on an object passed to the initialization call) to perform heap grooming and reclaim the memory previously occupied by the freed internal structures.
- Invoke the cached API method (
staleFunc()). This triggersRunAPIBindingHandlerCallback, which dereferences the dangling pointer and executes the callback. If the attacker successfully reclaimed the memory, they could achieve a controlled indirect call and arbitrary code execution.
Note: The bindingUtil object is primarily used by internal Chrome modules, but any path that allows an attacker to influence the strings passed to the binding system’s initialization logic would make this reachable.
Suggested Fix
Replace the DCHECK in APIBindingsSystem::InitializeType with a runtime check and an early return if the API is already initialized:
void APIBindingsSystem::InitializeType(const std::string& type_name) {
// ... parsing api_name ...
if (api_bindings_.contains(api_name)) {
return;
}
api_bindings_[api_name] = CreateNewAPIBinding(api_name);
}
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.