Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebView
DescriptionUse after free in WebView
ComponentWebView
Bug ClassUAF
Tracker503889643
Fix commita677b82cf06e (chromium/src) +12/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-04-28

Changed Functions

FunctionChangeNotes
JsBinding
components/js_injection/renderer/js_binding.h
modified

Files Changed

  • components/js_injection/renderer/js_binding.cc
  • components/js_injection/renderer/js_binding.h
From a677b82cf06e1243940d0ac4c8d5b6c97d1a0ff1 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
Date: Tue, 21 Apr 2026 09:10:02 -0700
Subject: [PATCH] js_injection: Fix Use-After-Free in JsBinding

JsBinding is an Oilpan-managed object but used a raw Mojo receiver. This
allowed IPC messages to be dispatched to a logically dead object during
the lazy sweeping phase. Synchronous JS execution during the IPC handler
could then trigger a nested GC that frees the object while it is still
on the stack.

This CL fixes the issue by adding a pre-finalizer to JsBinding that
explicitly resets the Mojo receiver during the GC's atomic pause.

Fixed: 503889643
Change-Id: I3468fce74c5c8c3fa24d33ee1b35398df2e08b2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7780011
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Reviewed-by: Bo Liu <boliu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1618238}
---

diff --git a/components/js_injection/renderer/js_binding.cc b/components/js_injection/renderer/js_binding.cc
index 835c772..d93325c2 100644
--- a/components/js_injection/renderer/js_binding.cc
+++ b/components/js_injection/renderer/js_binding.cc
@@ -285,6 +285,13 @@
   return receiver_.Bind(std::move(receiver));
 }
 
+void JsBinding::Dispose() {
+  // Explicitly reset the receiver to prevent IPC messages from being dispatched
+  // to this object while it is awaiting lazy sweeping. This prevents a UAF if
+  // synchronous JS execution triggers a nested GC. See crbug.com/503889643.
+  receiver_.reset();
+}
+
 gin::ObjectTemplateBuilder JsBinding::GetObjectTemplateBuilder(
     v8::Isolate* isolate) {
   return gin::Wrappable<JsBinding>::GetObjectTemplateBuilder(isolate)
diff --git a/components/js_injection/renderer/js_binding.h b/components/js_injection/renderer/js_binding.h
index 9ed2d45..5c4eade 100644
--- a/components/js_injection/renderer/js_binding.h
+++ b/components/js_injection/renderer/js_binding.h
@@ -18,6 +18,7 @@
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "third_party/blink/public/common/messaging/string_message_codec.h"
 #include "v8/include/cppgc/persistent.h"
+#include "v8/include/cppgc/prefinalizer.h"
 #include "v8/include/v8.h"
 
 namespace v8 {
@@ -38,6 +39,8 @@
 // to the page. JsBinding is owned by v8.
 class JsBinding final : public gin::Wrappable<JsBinding>,
                         public mojom::BrowserToJsMessaging {
+  CPPGC_USING_PRE_FINALIZER(JsBinding, Dispose);
+
  public:
   static constexpr gin::WrapperInfo kWrapperInfo = {{gin::kEmbedderNativeGin},
                                                     gin::kJsBinding};
@@ -73,6 +76,8 @@
       mojo::PendingAssociatedReceiver<mojom::BrowserToJsMessaging> receiver);
 
  private:
+  void Dispose();
+
   // gin::WrappableBase implementation.
   gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
       v8::Isolate* isolate) override;
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential Use-After-Free in js_injection::JsBinding due to lazy sweeping of Mojo receiver

Flapjack, 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: js_injection::JsBinding is an Oilpan-managed object that uses a raw Mojo receiver, allowing IPC messages to be dispatched to a logically dead object. Synchronous JavaScript execution during the IPC handler can force a nested garbage collection cycle that destroys the object while its member function is active, leading to a highly exploitable Use-After-Free.

Affected files:

  • components/js_injection/renderer/js_binding.cc
  • components/js_injection/renderer/js_binding.h

Estimated timestamp from git blame: 2023-10-25

Summary

A potential Use-After-Free (UAF) vulnerability exists in the js_injection::JsBinding class in the renderer process. The class is managed by Oilpan (cppgc) but uses a raw mojo::AssociatedReceiver, which lacks awareness of Oilpan’s lifecycle. This allows Mojo messages to be dispatched to a logically dead object. An attacker can exploit this by triggering synchronous JavaScript execution that forces the object to be swept while it is executing on the call stack.

Vulnerability Details

The js_injection::JsBinding class inherits from gin::Wrappable<JsBinding>, making its lifetime managed by Chromium’s Oilpan garbage collector (cppgc). It hosts a mojo::AssociatedReceiver<mojom::BrowserToJsMessaging> member (receiver_) which is bound to this to handle IPC messages from the browser.

In Chromium’s Oilpan system, garbage collection uses “lazy sweeping”. When an object becomes unreachable, it is marked as dead during the atomic pause, but its C++ destructor is deferred until the sweeping phase reclaims the memory later. Because JsBinding uses a raw Mojo receiver and does not have a pre-finalizer to unbind it during the atomic pause, a race condition exists: the receiver remains active and bound to the IPC pipe even after the object is marked dead.

Exploitation Steps (Theoretical)

An attacker could potentially trigger this vulnerability using the following steps:

  1. Setup: The attacker uses JavaScript to register an onmessage callback on the injected JS object, and adds a dummy event listener to populate the listeners_ vector.
  2. Orphan the Object: The attacker drops all JavaScript references to the object and forces a garbage collection cycle.
  3. Marking & Handle Clearing: cppgc identifies the JsBinding object as unreachable. V8’s TracedHandles::ResetDeadNodes clears the wrapper_ reference. The object is logically dead but its mojo::AssociatedReceiver remains active pending lazy sweeping.
  4. IPC Dispatch: The Browser process sends an OnPostMessage IPC. Mojo dispatches this to the active receiver, calling JsBinding::OnPostMessage with the this pointer referencing the logically dead memory.
  5. Synchronous JS Execution: Inside OnPostMessage, the code safely generates a new wrapper (as wrapper_.IsEmpty() is true) and synchronously executes the attacker’s on_message_ callback via PausableScriptExecutor::CreateAndRun(..., EvaluationTiming::kSynchronous).
  6. Nested GC & Synchronous Sweeping: Inside the JS callback, the attacker forces massive allocations to trigger a new GC cycle. In cppgc, starting a new GC cycle explicitly calls sweeper_.FinishIfRunning(). This forces the synchronous execution of the previous cycle’s destructors on the main thread, including ~JsBinding(). The memory for this is freed.
  7. Heap Spraying: The attacker immediately allocates a new cppgc object of the same size, overlapping the newly freed JsBinding memory and forging the internal pointers of the listeners_ vector.
  8. Use-After-Free: The JS callback returns. OnPostMessage resumes and attempts to access listeners_.size() and iterate over the vector (listeners_copy.reserve(listeners_.size());). This reads from the attacker-controlled overlapped memory.

Because MiraclePtr (BackupRefPtr) protects PartitionAlloc but does not protect the cppgc managed heap, this UAF can be reliably transformed into an arbitrary memory read/write primitive, leading to Remote Code Execution (RCE) in the renderer process.

Fix Recommendations

  1. Primary Fix: Replace the raw mojo::AssociatedReceiver with HeapMojoAssociatedReceiver (or HeapMojoReceiver if the interface is updated). Heap Mojo wrappers are cppgc-aware and automatically disconnect when the owner object is marked dead, preventing dispatch to a dying object.
  2. Alternative Fix: Add the USING_PRE_FINALIZER macro to JsBinding to explicitly call receiver_.reset() during the pre-finalization phase (the atomic pause), before lazy sweeping begins.

Evaluated with Chrome root at commit: c0eb5541aebfa4ea08806eaf6e94bcc69f87ab2f


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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