Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebView
DescriptionUse after free in WebView
ComponentWebView
Bug ClassUAF
Tracker511715166
Fix commit88f9f577ff43 (chromium/src) +40/-26
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
if
android_webview/browser/aw_contents.cc
modified
if
android_webview/browser/permission/permission_request_handler.cc
modified
AwPermissionRequest
android_webview/browser/permission/permission_request_handler_client.h
modified
AwPermissionRequestDelegate
android_webview/browser/permission/permission_request_handler_client.h
modified
PermissionRequestHandlerClient
android_webview/browser/permission/permission_request_handler_client.h
modified
if
android_webview/browser/permission/permission_request_handler_unittest.cc
modified
TestPermissionRequestHandler
android_webview/browser/permission/permission_request_handler_unittest.cc
modified

Files Changed

  • android_webview/browser/aw_contents.cc
  • android_webview/browser/aw_contents.h
  • android_webview/browser/permission/permission_request_handler.cc
  • android_webview/browser/permission/permission_request_handler_client.h
  • android_webview/browser/permission/permission_request_handler_unittest.cc
From 88f9f577ff437c1066b9a1815fde2e30a744484d Mon Sep 17 00:00:00 2001
From: Peter Pakkenberg <pbirk@google.com>
Date: Thu, 14 May 2026 04:49:21 -0700
Subject: [PATCH] Avoid creating AwPermissionRequest for destroyed contents

There is no need to create an AwPermissionRequest only to immediately
cancel and delete it if the Java AwContents object has already been
garbage collected. This change only creates the Java object once we've
determined that it is actually required.

Fixed: 511715166
Change-Id: Ied57deba7c8b92aafd9359d8cf617fe89118d6cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7845196
Commit-Queue: Peter Beverloo <peter@chromium.org>
Auto-Submit: Peter Pakkenberg <pbirk@chromium.org>
Reviewed-by: Peter Beverloo <peter@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1630578}
---

diff --git a/android_webview/browser/aw_contents.cc b/android_webview/browser/aw_contents.cc
index 94e6b752..b632f8b 100644
--- a/android_webview/browser/aw_contents.cc
+++ b/android_webview/browser/aw_contents.cc
@@ -733,21 +733,23 @@
   }
 }
 
-void AwContents::OnPermissionRequest(
-    base::android::ScopedJavaLocalRef<jobject> j_request,
-    AwPermissionRequest* request) {
-  DCHECK(j_request);
-  DCHECK(request);
+base::WeakPtr<AwPermissionRequest> AwContents::OnPermissionRequest(
+    std::unique_ptr<AwPermissionRequestDelegate> permission_request) {
+  DCHECK(permission_request);
 
   JNIEnv* env = AttachCurrentThread();
   ScopedJavaLocalRef<jobject> j_ref = java_ref_.get(env);
   if (!j_ref) {
-    permission_request_handler_->CancelRequest(request->GetOrigin(),
-                                               request->GetResources());
-    return;
+    return nullptr;
   }
 
+  base::WeakPtr<AwPermissionRequest> weak_request;
+  // The Create method also assigns the `weak_request` ptr.
+  base::android::ScopedJavaLocalRef<jobject> j_request =
+      AwPermissionRequest::Create(std::move(permission_request), &weak_request);
+
   Java_AwContents_onPermissionRequest(env, j_ref, j_request);
+  return weak_request;
 }
 
 void AwContents::OnPermissionRequestCanceled(AwPermissionRequest* request) {
diff --git a/android_webview/browser/aw_contents.h b/android_webview/browser/aw_contents.h
index 71c470d..d178db6 100644
--- a/android_webview/browser/aw_contents.h
+++ b/android_webview/browser/aw_contents.h
@@ -228,8 +228,8 @@
   bool GetViewTreeForceDarkState() { return view_tree_force_dark_state_; }
 
   // PermissionRequestHandlerClient implementation.
-  void OnPermissionRequest(base::android::ScopedJavaLocalRef<jobject> j_request,
-                           AwPermissionRequest* request) override;
+  base::WeakPtr<AwPermissionRequest> OnPermissionRequest(
+      std::unique_ptr<AwPermissionRequestDelegate> permission_request) override;
   void OnPermissionRequestCanceled(AwPermissionRequest* request) override;
 
   PermissionRequestHandler* GetPermissionRequestHandler() {
diff --git a/android_webview/browser/permission/permission_request_handler.cc b/android_webview/browser/permission/permission_request_handler.cc
index cd43127..1f47e53 100644
--- a/android_webview/browser/permission/permission_request_handler.cc
+++ b/android_webview/browser/permission/permission_request_handler.cc
@@ -52,11 +52,11 @@
     return;
   }
 
-  base::WeakPtr<AwPermissionRequest> weak_request;
-  base::android::ScopedJavaLocalRef<jobject> java_peer =
-      AwPermissionRequest::Create(std::move(request), &weak_request);
-  requests_.push_back(weak_request);
-  client_->OnPermissionRequest(java_peer, weak_request.get());
+  base::WeakPtr<AwPermissionRequest> weak_request =
+      client_->OnPermissionRequest(std::move(request));
+  if (weak_request) {
+    requests_.push_back(weak_request);
+  }
   PruneRequests();
 }
 
diff --git a/android_webview/browser/permission/permission_request_handler_client.h b/android_webview/browser/permission/permission_request_handler_client.h
index 51eb98e..a6a4c04e 100644
--- a/android_webview/browser/permission/permission_request_handler_client.h
+++ b/android_webview/browser/permission/permission_request_handler_client.h
@@ -5,20 +5,26 @@
 #ifndef ANDROID_WEBVIEW_BROWSER_PERMISSION_PERMISSION_REQUEST_HANDLER_CLIENT_H_
 #define ANDROID_WEBVIEW_BROWSER_PERMISSION_PERMISSION_REQUEST_HANDLER_CLIENT_H_
 
+#include <memory>
+
 #include "base/android/scoped_java_ref.h"
+#include "base/memory/weak_ptr.h"
 
 namespace android_webview {
 
 class AwPermissionRequest;
+class AwPermissionRequestDelegate;
 
 class PermissionRequestHandlerClient {
  public:
   PermissionRequestHandlerClient();
   virtual ~PermissionRequestHandlerClient();
 
-  virtual void OnPermissionRequest(
-      base::android::ScopedJavaLocalRef<jobject> java_request,
-      AwPermissionRequest* request) = 0;
+  // Handles the permission request. Will return a WeakPtr to the created
+  // AwPermissionRequest if one was instantiated, or `nullptr` if no Java
+  // objects were created.
+  virtual base::WeakPtr<AwPermissionRequest> OnPermissionRequest(
+      std::unique_ptr<AwPermissionRequestDelegate> permission_request) = 0;
   virtual void OnPermissionRequestCanceled(AwPermissionRequest* request) = 0;
 };
 
diff --git a/android_webview/browser/permission/permission_request_handler_unittest.cc b/android_webview/browser/permission/permission_request_handler_unittest.cc
index a0374be..f32d3b6 100644
--- a/android_webview/browser/permission/permission_request_handler_unittest.cc
+++ b/android_webview/browser/permission/permission_request_handler_unittest.cc
@@ -54,13 +54,19 @@
 
   TestPermissionRequestHandlerClient() : request_(nullptr) {}
 
-  void OnPermissionRequest(base::android::ScopedJavaLocalRef<jobject> j_request,
-                           AwPermissionRequest* request) override {
-    DCHECK(request);
-    request_ = request;
-    java_request_ = j_request;
-    requested_permission_ =
-        Permission(request->GetOrigin(), request->GetResources());
+  base::WeakPtr<AwPermissionRequest> OnPermissionRequest(
+      std::unique_ptr<AwPermissionRequestDelegate> permission_request)
+      override {
+    DCHECK(permission_request);
+    base::WeakPtr<AwPermissionRequest> weak_request;
+    java_request_ = AwPermissionRequest::Create(std::move(permission_request),
+                                                &weak_request);
+    request_ = weak_request.get();
+    if (request_) {
+      requested_permission_ =
+          Permission(request_->GetOrigin(), request_->GetResources());
+    }
+    return weak_request;
   }
 
   void OnPermissionRequestCanceled(AwPermissionRequest* request) override {
@@ -110,7 +116,7 @@
 
 class TestPermissionRequestHandler : public PermissionRequestHandler {
  public:
-  TestPermissionRequestHandler(PermissionRequestHandlerClient* client)
+  explicit TestPermissionRequestHandler(PermissionRequestHandlerClient* client)
       : PermissionRequestHandler(client, nullptr) {}
 
   const std::vector<base::WeakPtr<AwPermissionRequest>> requests() {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/android_webview/browser/permission/permission_request_handler_unittest.cc b/android_webview/browser/permission/permission_request_handler_unittest.cc
index a0374be..f32d3b6 100644
--- a/android_webview/browser/permission/permission_request_handler_unittest.cc
+++ b/android_webview/browser/permission/permission_request_handler_unittest.cc
@@ -54,13 +54,19 @@
 
   TestPermissionRequestHandlerClient() : request_(nullptr) {}
 
-  void OnPermissionRequest(base::android::ScopedJavaLocalRef<jobject> j_request,
-                           AwPermissionRequest* request) override {
-    DCHECK(request);
-    request_ = request;
-    java_request_ = j_request;
-    requested_permission_ =
-        Permission(request->GetOrigin(), request->GetResources());
+  base::WeakPtr<AwPermissionRequest> OnPermissionRequest(
+      std::unique_ptr<AwPermissionRequestDelegate> permission_request)
+      override {
+    DCHECK(permission_request);
+    base::WeakPtr<AwPermissionRequest> weak_request;
+    java_request_ = AwPermissionRequest::Create(std::move(permission_request),
+                                                &weak_request);
+    request_ = weak_request.get();
+    if (request_) {
+      requested_permission_ =
+          Permission(request_->GetOrigin(), request_->GetResources());
+    }
+    return weak_request;
   }
 
   void OnPermissionRequestCanceled(AwPermissionRequest* request) override {
@@ -110,7 +116,7 @@
 
 class TestPermissionRequestHandler : public PermissionRequestHandler {
  public:
-  TestPermissionRequestHandler(PermissionRequestHandlerClient* client)
+  explicit TestPermissionRequestHandler(PermissionRequestHandlerClient* client)
       : PermissionRequestHandler(client, nullptr) {}
 
   const std::vector<base::WeakPtr<AwPermissionRequest>> requests() {
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-After-Free in PermissionRequestHandler during synchronous CleanupReference queue drain

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 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: A potential Use-After-Free (UAF) vulnerability exists in Android WebView when a synchronous Java CleanupReference execution unexpectedly destroys the native AwContents object. This deletes the currently executing PermissionRequestHandler, leading to a UAF write when the execution stack unwinds.

Affected files:

  • android_webview/browser/permission/permission_request_handler.cc
  • android_webview/browser/aw_contents.cc
  • android_webview/java/src/org/chromium/android_webview/CleanupReference.java
  • android_webview/java/src/org/chromium/android_webview/permission/AwPermissionRequest.java
  • android_webview/browser/permission/aw_permission_request.cc

Estimated timestamp from git blame: 2026-01-26

Vulnerability Details

A potential Use-After-Free (UAF) vulnerability exists in the Android WebView browser process (the embedding application process). The issue is caused by an unsafe synchronous reentrancy and destruction pattern involving Java’s CleanupReference mechanism.

In Android WebView, native objects are typically managed by Java peer objects using the CleanupReference class. When a Java object is garbage collected, its corresponding CleanupReference is enqueued in a global queue (sGcQueue) to be processed by a reaper thread.

If an embedder application abandons a WebView without explicitly calling destroy(), the Java AwContents object is eventually garbage collected, and its cleanup task is added to sGcQueue. A race condition occurs if a renderer-initiated permission request (e.g., Geolocation, MIDI) is processed on the UI thread while the AwContents cleanup task is still pending in the queue.

Potential Execution Chain:

  1. PermissionRequestHandler::SendRequest is called on the UI thread to handle a new permission request from the renderer.
  2. It calls client_->OnPermissionRequest (implemented by the C++ AwContents).
  3. Inside AwContents::OnPermissionRequest, it attempts to fetch the Java peer. Since the Java AwContents has already been collected, the JNI weak reference returns null. It then synchronously re-enters PermissionRequestHandler::CancelRequest() to cleanly abort the request.
  4. CancelRequest enters a while loop, retrieves an iterator i to the request, and calls CancelRequestInternal, which invokes AwPermissionRequest::CancelAndDelete.
  5. This triggers a JNI call to AwPermissionRequest.destroyNative, which explicitly calls mCleanupReference.cleanupNow() in the Java layer.
  6. CleanupReference.cleanupNow() dispatches a message to the UI thread. Since execution is already on the UI thread, the message handler runs synchronously.
  7. Crucially, this message handler contains a loop that drains the entire global sGcQueue synchronously:
    while ((ref = (CleanupReference) sGcQueue.poll()) != null) {
        ref.runCleanupTaskInternal();
    }
    
  8. If the AwContents cleanup task is still in the queue, it is executed synchronously. This triggers the destruction of the native AwContents object (delete this). Because AwContents owns the PermissionRequestHandler via a std::unique_ptr, the handler is also immediately deleted from the heap.
  9. When execution unwinds back to the C++ PermissionRequestHandler::CancelRequest method, it resumes the while loop and executes requests_.erase(i).
  10. At this point, the implicit this pointer (the PermissionRequestHandler) and its requests_ vector have been deleted, resulting in a UAF read and write on the vector’s freed heap buffer.

(Note: These are potential steps based on code analysis; we do not yet have a working proof of concept.)

Impact

This vulnerability allows a compromised renderer to trigger a Use-After-Free write in the unsandboxed browser process (the embedding Android application). This provides a strong primitive that can lead to Remote Code Execution (RCE) in the context of the application using WebView.

This vulnerability is not protected by MiraclePtr (BRP) because the access occurs through the implicit this pointer held on the call stack and a stack-based iterator (i), both of which bypass BRP protections.

Suggested Fix

To prevent this synchronous destruction race, avoid executing the entire sGcQueue synchronously during an explicit cleanupNow(). Alternatively, in PermissionRequestHandler::CancelRequest or AwContents::OnPermissionRequest, the cancellation callbacks or the destruction of the AwPermissionRequest should be posted asynchronously (e.g., using base::SingleThreadTaskRunner::PostTask) rather than executed synchronously on the current call stack.

Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955


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