CVE-2026-11080
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifandroid_webview/browser/permission/permission_request_handler.cc |
modified | |
ifandroid_webview/browser/permission/permission_request_handler_unittest.cc |
modified | |
TestPermissionRequestHandlerandroid_webview/browser/permission/permission_request_handler_unittest.cc |
modified | |
TEST_Fandroid_webview/browser/permission/permission_request_handler_unittest.cc |
modified |
Files Changed
android_webview/browser/permission/permission_request_handler.ccandroid_webview/browser/permission/permission_request_handler_unittest.cc
Patch
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
Regression Test / PoC
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
Original Bug Report
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.ccandroid_webview/browser/permission/aw_permission_request.ccandroid_webview/java/src/org/chromium/android_webview/CleanupReference.javaandroid_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:
AwPermissionRequest.deny()callsdestroyNative().destroyNative()invokesmCleanupReference.cleanupNow().- Since this occurs on the UI thread, the cleanup task executes synchronously instead of posting to the looper.
- The cleanup task invokes
AwPermissionRequestJni.get().destroy(nativeAwPermissionRequest). - The native
AwPermissionRequest::Destroymethod executesdelete 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
- A malicious web page loaded in a WebView requests a permission (e.g., MIDI SysEx).
- The page triggers a navigation to a different origin.
- The navigation causes
PermissionRequestHandler::CancelAllRequeststo be called, which invokesCancelRequestInternal. - The embedder app’s defensive
onPermissionRequestCanceledhandler callsdeny()on the canceled request. - 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.