Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebView
DescriptionUse after free in WebView
ComponentWebView
Bug ClassUAF
Tracker500032538
Fix commit62cc9509a72a (chromium/src) +44/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
android_webview/browser/permission/permission_request_handler.cc
modified
if
android_webview/browser/permission/permission_request_handler_unittest.cc
modified
TestPermissionRequestHandler
android_webview/browser/permission/permission_request_handler_unittest.cc
modified
TEST_F
android_webview/browser/permission/permission_request_handler_unittest.cc
modified

Files Changed

  • android_webview/browser/permission/permission_request_handler.cc
  • android_webview/browser/permission/permission_request_handler_unittest.cc
From 62cc9509a72ab5636a0046fd5a98dabfc65f0d4a Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <[email protected]>
Date: Wed, 08 Apr 2026 11:18:46 -0700
Subject: [PATCH] [AW] Use WeakPtr in PermissionRequestHandler::CancelRequestInternal

This prevents a Use-After-Free (UAF) vulnerability when an embedder
synchronously deletes an AwPermissionRequest (e.g., by calling
grant() or deny()) inside the OnPermissionRequestCanceled callback.

Fixed: 500032538
Change-Id: Icb4b0ddd85dcb1099d5ff80d5ffcdf4f8a9a8857
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7735864
Reviewed-by: Richard (Torne) Coles <[email protected]>
Commit-Queue: Andrew Paseltiner <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1611674}
---

diff --git a/android_webview/browser/permission/permission_request_handler.cc b/android_webview/browser/permission/permission_request_handler.cc
index 667e0b4..cd43127 100644
--- a/android_webview/browser/permission/permission_request_handler.cc
+++ b/android_webview/browser/permission/permission_request_handler.cc
@@ -111,10 +111,16 @@
 }
 
 void PermissionRequestHandler::CancelRequestInternal(RequestIterator i) {
-  AwPermissionRequest* request = i->get();
+  // Use a WeakPtr to check if the request is still alive after the synchronous
+  // JNI call to OnPermissionRequestCanceled. The embedder might synchronously
+  // delete the request (e.g., by calling grant() or deny()) inside the
+  // cancellation callback.
+  base::WeakPtr<AwPermissionRequest> request = *i;
   if (request) {
-    client_->OnPermissionRequestCanceled(request);
-    request->CancelAndDelete();
+    client_->OnPermissionRequestCanceled(request.get());
+    if (request) {
+      request->CancelAndDelete();
+    }
   }
 }
 
diff --git a/android_webview/browser/permission/permission_request_handler_unittest.cc b/android_webview/browser/permission/permission_request_handler_unittest.cc
index 0b08fb5..a0374be 100644
--- a/android_webview/browser/permission/permission_request_handler_unittest.cc
+++ b/android_webview/browser/permission/permission_request_handler_unittest.cc
@@ -66,6 +66,9 @@
   void OnPermissionRequestCanceled(AwPermissionRequest* request) override {
     canceled_permission_ =
         Permission(request->GetOrigin(), request->GetResources());
+    if (grant_on_cancel_) {
+      Grant();
+    }
   }
 
   AwPermissionRequest* request() { return request_; }
@@ -90,6 +93,11 @@
     request_ = nullptr;
     requested_permission_ = Permission();
     canceled_permission_ = Permission();
+    grant_on_cancel_ = false;
+  }
+
+  void SetGrantOnCancel(bool grant_on_cancel) {
+    grant_on_cancel_ = grant_on_cancel;
   }
 
  private:
@@ -97,6 +105,7 @@
   raw_ptr<AwPermissionRequest> request_;
   Permission requested_permission_;
   Permission canceled_permission_;
+  bool grant_on_cancel_ = false;
 };
 
 class TestPermissionRequestHandler : public PermissionRequestHandler {
@@ -244,7 +253,7 @@
 
   // Cancel the request.
   handler()->CancelRequest(origin(), resources());
-  // Verify client's OnPermissionRequestCancled() was called.
+  // Verify client's OnPermissionRequestCanceled() was called.
   EXPECT_EQ(origin(), client()->canceled_permission().origin);
   EXPECT_EQ(resources(), client()->canceled_permission().resources);
   // Verify Handler store the request correctly, the 1st and 3rd were removed.
@@ -328,4 +337,29 @@
   EXPECT_EQ(nullptr, client()->request());
 }
 
+// Regression test for crbug.com/500032538.
+TEST_F(PermissionRequestHandlerTest, TestCancelRequestDuringCallback) {
+  handler()->SendRequest(delegate());
+  // Verify Handler store the request correctly.
+  ASSERT_EQ(1u, handler()->requests().size());
+
+  // Set the client to grant the request when it is canceled.
+  client()->SetGrantOnCancel(true);
+
+  // Cancel the request. This will trigger OnPermissionRequestCanceled,
+  // which will call client()->Grant(), which synchronously deletes the request.
+  // The fix ensures we don't use the deleted request after the callback.
+  handler()->CancelRequest(origin(), resources());
+
+  // Verify client's OnPermissionRequestCanceled() was called.
+  EXPECT_EQ(origin(), client()->canceled_permission().origin);
+  EXPECT_EQ(resources(), client()->canceled_permission().resources);
+
+  // Verify the request was granted.
+  EXPECT_TRUE(allowed());
+
+  handler()->PruneRequests();
+  EXPECT_TRUE(handler()->requests().empty());
+}
+
 }  // namespace android_webview
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 0b08fb5..a0374be 100644
--- a/android_webview/browser/permission/permission_request_handler_unittest.cc
+++ b/android_webview/browser/permission/permission_request_handler_unittest.cc
@@ -66,6 +66,9 @@
   void OnPermissionRequestCanceled(AwPermissionRequest* request) override {
     canceled_permission_ =
         Permission(request->GetOrigin(), request->GetResources());
+    if (grant_on_cancel_) {
+      Grant();
+    }
   }
 
   AwPermissionRequest* request() { return request_; }
@@ -90,6 +93,11 @@
     request_ = nullptr;
     requested_permission_ = Permission();
     canceled_permission_ = Permission();
+    grant_on_cancel_ = false;
+  }
+
+  void SetGrantOnCancel(bool grant_on_cancel) {
+    grant_on_cancel_ = grant_on_cancel;
   }
 
  private:
@@ -97,6 +105,7 @@
   raw_ptr<AwPermissionRequest> request_;
   Permission requested_permission_;
   Permission canceled_permission_;
+  bool grant_on_cancel_ = false;
 };
 
 class TestPermissionRequestHandler : public PermissionRequestHandler {
@@ -244,7 +253,7 @@
 
   // Cancel the request.
   handler()->CancelRequest(origin(), resources());
-  // Verify client's OnPermissionRequestCancled() was called.
+  // Verify client's OnPermissionRequestCanceled() was called.
   EXPECT_EQ(origin(), client()->canceled_permission().origin);
   EXPECT_EQ(resources(), client()->canceled_permission().resources);
   // Verify Handler store the request correctly, the 1st and 3rd were removed.
@@ -328,4 +337,29 @@
   EXPECT_EQ(nullptr, client()->request());
 }
 
+// Regression test for crbug.com/500032538.
+TEST_F(PermissionRequestHandlerTest, TestCancelRequestDuringCallback) {
+  handler()->SendRequest(delegate());
+  // Verify Handler store the request correctly.
+  ASSERT_EQ(1u, handler()->requests().size());
+
+  // Set the client to grant the request when it is canceled.
+  client()->SetGrantOnCancel(true);
+
+  // Cancel the request. This will trigger OnPermissionRequestCanceled,
+  // which will call client()->Grant(), which synchronously deletes the request.
+  // The fix ensures we don't use the deleted request after the callback.
+  handler()->CancelRequest(origin(), resources());
+
+  // Verify client's OnPermissionRequestCanceled() was called.
+  EXPECT_EQ(origin(), client()->canceled_permission().origin);
+  EXPECT_EQ(resources(), client()->canceled_permission().resources);
+
+  // Verify the request was granted.
+  EXPECT_TRUE(allowed());
+
+  handler()->PruneRequests();
+  EXPECT_TRUE(handler()->requests().empty());
+}
+
 }  // namespace android_webview
Loading diff…

Original Bug Report

reported by [email protected]

Potential Browser Process UAF in PermissionRequestHandler::CancelRequestInternal

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 security team.

Overview: A Use-After-Free (UAF) vulnerability exists in the Android WebView browser process when a permission request is canceled. If an embedder application defensively calls grant() or deny() during the cancellation callback, the native object is synchronously deleted, leaving a dangling stack pointer that is subsequently accessed.

Affected files:

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

Estimated timestamp from git blame: 2015-05-06

Summary

A Use-After-Free (UAF) vulnerability exists in the Android WebView browser process within PermissionRequestHandler::CancelRequestInternal. The function caches a raw pointer to an AwPermissionRequest object and then performs a synchronous JNI call into the embedder application’s code. If the embedder application calls request.deny() or request.grant() inside the onPermissionRequestCanceled callback, the native AwPermissionRequest object is synchronously deleted. When the JNI call returns, the cached pointer is dereferenced, leading to a UAF.

Technical Details

In android_webview/browser/permission/permission_request_handler.cc, the CancelRequestInternal function caches a raw stack pointer to the request:

void PermissionRequestHandler::CancelRequestInternal(RequestIterator i) {
  AwPermissionRequest* request = i->get(); // Caches raw stack pointer
  if (request) {
    client_->OnPermissionRequestCanceled(request); // Synchronous JNI callback
    request->CancelAndDelete(); // UAF happens here
  }
}

The OnPermissionRequestCanceled call ultimately triggers a synchronous JNI callback to the Java method onPermissionRequestCanceled in the embedder’s WebChromeClient.

If the embedder application calls deny() or grant() on the provided PermissionRequest adapter within this callback, it triggers a synchronous deletion chain in Java:

  1. AwPermissionRequest.deny() calls destroyNative().
  2. destroyNative() invokes mCleanupReference.cleanupNow().
  3. Since this occurs on the UI thread, the cleanup task executes synchronously instead of posting to the looper.
  4. The cleanup task invokes AwPermissionRequestJni.get().destroy(nativeAwPermissionRequest).
  5. The native AwPermissionRequest::Destroy method executes delete this;.

At this point, the AwPermissionRequest object is freed. However, PermissionRequestHandler::CancelRequestInternal still holds the cached raw pointer request on the stack. Because it is a local stack pointer rather than a class member, it bypasses MiraclePtr (BackupRefPtr) protections.

Upon returning from the JNI call, request->CancelAndDelete() is executed on the freed memory, causing a UAF write (processed_ = true) and a UAF read (retrieving the Java object reference).

Potential Attack Vector

  1. A malicious web page loaded in a WebView requests a permission (e.g., MIDI SysEx).
  2. The page triggers a navigation to a different origin.
  3. The navigation causes PermissionRequestHandler::CancelAllRequests to be called, which invokes CancelRequestInternal.
  4. The embedder app’s defensive onPermissionRequestCanceled handler calls deny() on the canceled request.
  5. The UAF is triggered upon returning to C++.

While achieving Remote Code Execution is extremely difficult due to the inability to run the message loop and reallocate memory synchronously during the callback, the vulnerability reliably crashes the browser process and represents a valid memory safety violation.

Suggested Fix

Use a base::WeakPtr to verify the object is still alive after the synchronous callback returns.

void PermissionRequestHandler::CancelRequestInternal(RequestIterator i) {
  base::WeakPtr<AwPermissionRequest> request = *i;
  if (request) {
    client_->OnPermissionRequestCanceled(request.get());
    if (request) {
      request->CancelAndDelete();
    }
  }
}

Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad


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