CVE-2026-13037
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
synchronizedandroid_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxMessagePort.java |
modified | |
ifandroid_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxMessagePort.java |
modified |
Files Changed
android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxMessagePort.java
Patch
From 3b696bd61c35756ebc1e79b0d8864b8fef5190c9 Mon Sep 17 00:00:00 2001
From: Ashley Newson <ashleynewson@chromium.org>
Date: Mon, 15 Jun 2026 15:05:35 -0700
Subject: [PATCH] [js_sandbox] Fix JsSandboxMessagePort thread safety
Teardown of a message port in the sandbox (due to closing the isolate)
could theoretically race with incoming messages from the app-side that
were dispatched before the app received the close signal. This could
theoretically lead to calls being directed to a destroyed native object.
Additionally, the constructor leaked `this` when setting the internal
message port client, so an incoming message could theoretically race
with returning from the constructor, resulting in undefined behavior.
This change adds Java synchronization around use of the native object
and avoids leakage of `this`.
Fixed: 523721871
Change-Id: Ifc5af234259de086bf0db416d5a8db684c1ee56d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7942016
Reviewed-by: Abhijith Nair <abhijithnair@chromium.org>
Commit-Queue: Ashley Newson <ashleynewson@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1647108}
---
diff --git a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxMessagePort.java b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxMessagePort.java
index 873514b..01ac1f20 100644
--- a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxMessagePort.java
+++ b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxMessagePort.java
@@ -13,27 +13,42 @@
import org.chromium.build.annotations.NullMarked;
+import javax.annotation.concurrent.GuardedBy;
+
@JNINamespace("android_webview")
@NullMarked
public class JsSandboxMessagePort {
- private final long mNativeJsSandboxMessagePort;
+ private final Object mLock = new Object();
private final MessagePortInternal mMessagePortInternal;
+ @GuardedBy("mLock")
+ private long mNativeJsSandboxMessagePort;
+
@CalledByNative
public static JsSandboxMessagePort create(
MessagePortInternal messagePortInternal, long nativeSandboxMessagePort) {
JsSandboxMessagePort jsSandboxMessagePort =
new JsSandboxMessagePort(messagePortInternal, nativeSandboxMessagePort);
+ // Setup the client after construction to avoid undefined behavior from leaking `this` to
+ // the MessagePortInternal, which could otherwise be invoked from another thread before all
+ // fields have been initialized.
+ jsSandboxMessagePort.setupClient();
return jsSandboxMessagePort;
}
private JsSandboxMessagePort(
MessagePortInternal messagePortInternal, long nativeJsSandboxMessagePort) {
- mNativeJsSandboxMessagePort = nativeJsSandboxMessagePort;
+ mMessagePortInternal = messagePortInternal;
+ synchronized (mLock) {
+ mNativeJsSandboxMessagePort = nativeJsSandboxMessagePort;
+ }
+ }
+
+ private void setupClient() {
// TODO(b/435619571):
// Memory allocation checks should ideally happen before arbitrary size data is received
// (via FDs), as this could easily exhaust memory before the MessagePortClient is invoked.
- messagePortInternal.setClient(
+ mMessagePortInternal.setClient(
new MessagePortInternal.MessagePortClient() {
@Override
public void onString(String string) {
@@ -41,28 +56,36 @@
// UTF-8, it may end up being more or less. As such, this is somewhat more
// of a best-effort estimate than a concrete value.
long size = (long) string.length() * Character.BYTES;
- if (!JsSandboxMessagePortJni.get()
- .tryAllocateMemoryBudget(mNativeJsSandboxMessagePort, size)) {
- return;
+ synchronized (mLock) {
+ if (mNativeJsSandboxMessagePort == 0) {
+ return;
+ }
+ if (!JsSandboxMessagePortJni.get()
+ .tryAllocateMemoryBudget(mNativeJsSandboxMessagePort, size)) {
+ return;
+ }
+ JsSandboxMessagePortJni.get()
+ .handleString(mNativeJsSandboxMessagePort, string, size);
}
-
- JsSandboxMessagePortJni.get()
- .handleString(mNativeJsSandboxMessagePort, string, size);
}
@Override
public void onArrayBuffer(byte[] arrayBuffer) {
long size = arrayBuffer.length;
- if (!JsSandboxMessagePortJni.get()
- .tryAllocateMemoryBudget(mNativeJsSandboxMessagePort, size)) {
- return;
+ synchronized (mLock) {
+ if (mNativeJsSandboxMessagePort == 0) {
+ return;
+ }
+ if (!JsSandboxMessagePortJni.get()
+ .tryAllocateMemoryBudget(mNativeJsSandboxMessagePort, size)) {
+ return;
+ }
+ JsSandboxMessagePortJni.get()
+ .handleArrayBuffer(
+ mNativeJsSandboxMessagePort, arrayBuffer, size);
}
-
- JsSandboxMessagePortJni.get()
- .handleArrayBuffer(mNativeJsSandboxMessagePort, arrayBuffer, size);
}
});
- mMessagePortInternal = messagePortInternal;
}
// Called by isolate thread
@@ -80,6 +103,9 @@
// Called by isolate thread
@CalledByNative
void close() {
+ synchronized (mLock) {
+ mNativeJsSandboxMessagePort = 0;
+ }
mMessagePortInternal.close();
}
Original Bug Report
Potential Use-After-Free in JsSandboxMessagePort due to isolate teardown race
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 Use-After-Free exists in JsSandboxMessagePort due to a race condition during JsSandboxIsolate destruction. Incoming Binder messages from Java blindly cast a raw long to a C++ pointer, which may have already been freed when the V8 isolate tears down its garbage-collected heap. An attacker could potentially exploit this to achieve arbitrary code execution in the utility process.
Affected files:
android_webview/js_sandbox/service/js_sandbox_message_port.ccandroid_webview/js_sandbox/service/js_sandbox_message_port.handroid_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxMessagePort.java
Estimated timestamp from git blame: Unknown (Google3 checkout)
Summary
A potential Use-After-Free (UAF) vulnerability exists in JsSandboxMessagePort within the Android WebView JS Sandbox service. The vulnerability stems from a lack of synchronization between the destruction of a JsSandboxIsolate (and its associated V8 Oilpan heap) and concurrent incoming Inter-Process Communication (IPC) messages processed on Binder threads.
Technical Details
When a JsSandboxMessagePort is created, its C++ counterpart is allocated on V8’s cppgc (Oilpan) heap. The native memory address is passed back to Java and stored as a raw long (mNativeJsSandboxMessagePort).
When a JsSandboxIsolate is closed via the Java API, a DeleteSelf task is posted to the isolate thread. This task synchronously destroys the V8 isolate and its attached CppHeap. Destroying the heap unconditionally frees the memory of all associated JsSandboxMessagePort objects.
However, incoming messages on the MessagePort (handled by MessagePortInternal.MessagePortClient callbacks on a Binder thread) do not synchronize with this teardown. A concurrent message can invoke JNI methods such as tryAllocateMemoryBudget or handleString using the raw mNativeJsSandboxMessagePort value. The JNI bridge blindly casts this long back to a JsSandboxMessagePort*.
If the V8 isolate teardown occurs precisely between the message arriving and the JNI boundary crossing, the Binder thread will execute methods on a freed C++ object.
Exploitability
This vulnerability is not protected by MiraclePtr (BackupRefPtr). The JNI boundary relies on an unmanaged jlong, bypassing reference counting. Furthermore, JsSandboxMessagePort is allocated in the CppHeap, which does not support BRP.
An attacker could potentially exploit this to gain Remote Code Execution (RCE) in the js_sandbox utility process using the following theoretical steps:
- Spawn two isolates (
Isolate AandIsolate B) via the JS Sandbox API. - Trigger the destruction of
Isolate Awhile simultaneously flooding its MessagePort with incoming strings from Java to hit the race window. - As soon as
Isolate Abegins teardown, execute a heap spray payload inIsolate B(which shares the same process address space). - Reclaim the freed
JsSandboxMessagePortmemory chunk with an attacker-controlled fake object. - Overwrite the
js_sandbox_isolate_pointer in the fake object to point to another forged structure containing a fakeTaskRunner. - When the racing Binder thread executes
JsSandboxMessagePort::HandleString, it will dereference the fakejs_sandbox_isolate_and callGetIsolateTaskRunner()->PostTask(...). PostTaskmakes a virtual method call toPostDelayedTask, which will be hijacked via the fakeTaskRunner’s vtable, redirecting control flow to an attacker payload.
(Note: These are suggested steps based on code analysis; our tooling has not run a live proof-of-concept exploit.)
Suggested Fix
- Do not use raw memory addresses (
long) to track C++ object lifetimes across the JNI boundary if the C++ object can be destroyed asynchronously. - A thread-safe handle or registry (e.g., using a lock and an ID-to-pointer map) should be used to look up the
JsSandboxMessagePortpointer from Java. - During isolate teardown, the
JsSandboxMessagePortobjects should be safely unregistered from this map so that concurrent Binder calls fail gracefully rather than dereferencing a dangling pointer.
Evaluated with Chrome root at commit: 65b3256311f3ab6fb9870eaa522de7e6dd2663bb
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.