Critical watch CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebView
DescriptionUse after free in WebView
ComponentWebView
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker511715166
Fix commit88f9f577ff43 (chromium/src) +40/-26
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-10 (per vendor advisory)
Fix landed2026-05-14 (public git)
Disclosed2026-05-27
Patch gap13 days — fix public in source before the advisory

Background

`AwContents`
The native (C++) peer of the Android WebView AwContents Java object; it holds a weak java_ref_ to the Java side, which can be garbage-collected independently.
`AwPermissionRequest`
A native object (with a paired Java peer) representing a single pending WebView permission prompt, created via AwPermissionRequest::Create.
`PermissionRequestHandler`
Browser-side manager that tracks outstanding permission requests in requests_ (a vector of base::WeakPtr<AwPermissionRequest>) and notifies its client (AwContents).
`PermissionRequestHandlerClient`
The interface AwContents implements to receive OnPermissionRequest callbacks.

Root Cause Analysis

In the pre-patch flow, the method in permission_request_handler.cc eagerly called AwPermissionRequest::Create to build both the native request and its Java peer, pushed the resulting weak pointer into requests_, and only then invoked client_->OnPermissionRequest(java_peer, weak_request.get()). Inside AwContents::OnPermissionRequest, if the Java AwContents had already been garbage-collected (java_ref_.get(env) returned null), the code called permission_request_handler_->CancelRequest(request->GetOrigin(), request->GetResources()) to tear the just-created request back down on the same stack. This create-then-immediately-cancel dance on the GC’d path meant an AwPermissionRequest was constructed, registered, and destroyed re-entrantly, leaving a dangling pointer/registration that could be used after the object was freed (the exact downstream dereference is (inferred)).

The fix inverts the order so the object is created lazily: OnPermissionRequest now receives a std::unique_ptr<AwPermissionRequestDelegate>, returns early with nullptr when j_ref is null (creating nothing), and only calls AwPermissionRequest::Create after the Java object is confirmed alive; the handler registers the request in requests_ only when a non-null base::WeakPtr comes back.

Key insight
The code allocated and registered a permission-request object before checking whether its owning Java AwContents still existed, so on the garbage-collected path it had to cancel and free the object it had just created — the fix defers creation until after the liveness check, so nothing is created that must be immediately destroyed.

Attack Path

  1. Trigger a permission prompt Web content loaded in a WebView requests a permission (e.g. geolocation or media capture), driving PermissionRequestHandler to begin a new request.
  2. Race the Java object's lifetime The embedding Java AwContents is garbage-collected so that java_ref_ is null by the time the native request is being set up.
  3. Eager creation then cancel Pre-patch, AwPermissionRequest::Create has already built the native object and pushed its weak pointer into requests_, then the null-j_ref branch calls CancelRequest, destroying the object during the same callback.
  4. Use-after-free window A reference to the just-freed AwPermissionRequest (registered in requests_ and/or passed as weak_request.get()) is used after destruction (inferred), corrupting browser-process state.

Impact Assessment

An attacker controlling web content rendered in an Android WebView can, by timing a permission request against garbage collection of the Java AwContents, trigger a use-after-free in the app’s browser-side process where WebView runs; exploitation requires winning this GC/timing race rather than a non-default flag or extension. The vendor advisory rates this critical; any escalation beyond the use-after-free itself is (inferred) and the advisory does not state in-the-wild exploitation.

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

Audit Directions

  • Eager allocation before liveness checks
    Look for native WebView/browser objects created (especially with a paired Java peer) before verifying that a weak owner reference such as java_ref_ is still valid; defer creation past the guard.
  • Create-then-cancel on failure paths
    Flag callbacks that build an object and then synchronously cancel/destroy it on an early-exit branch, which invites re-entrant use-after-free; prefer returning before construction.
  • WeakPtr registries mutated during callbacks
    Audit containers like requests_ that are populated before invoking a client callback that may destroy or prune their entries on the same stack.
From 88f9f577ff437c1066b9a1815fde2e30a744484d Mon Sep 17 00:00:00 2001
From: Peter Pakkenberg <[email protected]>
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 <[email protected]>
Auto-Submit: Peter Pakkenberg <[email protected]>
Reviewed-by: Peter Beverloo <[email protected]>
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 [email protected]

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