Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Extensions
DescriptionUse after free in Extensions
ComponentExtensions
Bug ClassUAF
Tracker513467993
Fix commit000042caf489 (chromium/src) +19/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
TEST_F
extensions/renderer/bindings/api_bindings_system_unittest.cc
modified

Files Changed

  • extensions/renderer/bindings/api_bindings_system.cc
  • extensions/renderer/bindings/api_bindings_system_unittest.cc
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
Loading diff…

Regression Test / PoC

shipped with the fix
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
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • extensions/renderer/bindings/api_binding.cc
  • extensions/renderer/bindings/api_binding_js_util.cc
  • extensions/renderer/bindings/api_type_reference_map.cc
  • extensions/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:

  1. Obtain a reference to a cached API method (e.g., const staleFunc = chrome.runtime.sendMessage;). This V8 function now holds a v8::External with a raw pointer to internal C++ structures.
  2. Trigger a call to InitializeType for the already-loaded “runtime” API using a non-existent member name (e.g., via bindingUtil.validateType('runtime.bogus', {})). This causes the destruction of the original APIBinding for the “runtime” API.
  3. 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.
  4. Invoke the cached API method (staleFunc()). This triggers RunAPIBindingHandlerCallback, 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.

View on issue tracker