CVE-2026-6315
Overview
Files Changed
components/permissions/android/permission_prompt/permission_dialog_delegate.cccomponents/permissions/android/permission_prompt/permission_dialog_delegate.h
Patch
From 5457c6e7e7cb051f87fe90545cfd68c5640432c4 Mon Sep 17 00:00:00 2001 From: Antonio Sartori <[email protected]> Date: Tue, 07 Apr 2026 01:46:27 -0700 Subject: [PATCH] [permissions] Destroy PermissionDialogJavaDelegate async This CL makes permissionDialogDelegate::Dismissed destroy the PermissionDialogJavaDelegate asynchronously. This prevents UaF issues in case Dismissed() is called while a PermissionDialogJavaDelegate method is running. Bug: 499247910 Change-Id: Iac8a46b4f4fc3fc83dc989c54d0fea41d3a3e6ac Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7734121 Commit-Queue: Antonio Sartori <[email protected]> Reviewed-by: Balazs Engedy <[email protected]> Cr-Commit-Position: refs/heads/main@{#1610616} --- diff --git a/components/permissions/android/permission_prompt/permission_dialog_delegate.cc b/components/permissions/android/permission_prompt/permission_dialog_delegate.cc index e570092..4c671a1 100644 --- a/components/permissions/android/permission_prompt/permission_dialog_delegate.cc +++ b/components/permissions/android/permission_prompt/permission_dialog_delegate.cc @@ -224,13 +224,16 @@ // But, all the underlying data associated with it will get wiped. // So, we destroy the Java delegate and use the `IsJavaDelegateDestroyed` // signal as a way to tell if the `PermissionPrompt` creation failed. - DestroyJavaDelegate(); + + // Delete asynchronously to avoid re-entrancy issues. + base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon( + FROM_HERE, std::move(java_delegate_)); } permission_prompt_->Dismiss(prompt_options_); } void PermissionDialogDelegate::Destroy(JNIEnv* env) { - DestroyJavaDelegate(); + java_delegate_.reset(); } void PermissionDialogDelegate::NotifyPermissionAllowed() { diff --git a/components/permissions/android/permission_prompt/permission_dialog_delegate.h b/components/permissions/android/permission_prompt/permission_dialog_delegate.h index f4f760c..0361ee0 100644 --- a/components/permissions/android/permission_prompt/permission_dialog_delegate.h +++ b/components/permissions/android/permission_prompt/permission_dialog_delegate.h @@ -120,8 +120,6 @@ // On navigation or page destruction, hide the dialog. void DismissDialog(); - void DestroyJavaDelegate() { java_delegate_.reset(); } - // WebContentsObserver: void PrimaryPageChanged(content::Page&) override; void WebContentsDestroyed() override;
Original Bug Report
Potential browser-process UAF in PermissionDialogJavaDelegate due to synchronous JNI re-entry
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 potential Use-After-Free (UAF) vulnerability exists in the Android permission prompt logic. A synchronous JNI call during dialog creation can trigger immediate dismissal, destroying the underlying C++ delegate object while its method is still on the stack. Returning from JNI results in a UAF when accessing member variables.
Affected files:
components/permissions/android/permission_prompt/permission_dialog_delegate.cccomponents/permissions/android/permission_prompt/permission_dialog_delegate.hcomponents/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.javacomponents/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.javacomponents/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogCoordinator.java
Estimated timestamp from git blame: 2026-01-16
Summary
A potential Use-After-Free (UAF) exists in PermissionDialogJavaDelegate::CreateDialog on Android. When initializing a permission prompt, a synchronous JNI call is made to Java to show the dialog. If the Java side immediately dismisses the dialog (e.g., due to a null ModalDialogManager), a synchronous callback to native code destroys the PermissionDialogJavaDelegate object. When execution unwinds back to CreateDialog, the this pointer is dangling, leading to a UAF and a potential control flow hijack.
Technical Details
The vulnerability is triggered during the creation of a permission prompt via PermissionRequestManager::RecreateView(). This function invokes a factory method that eventually calls the PermissionDialogDelegate constructor, which allocates a PermissionDialogJavaDelegate using a std::unique_ptr and calls CreateDialog() on it.
In components/permissions/android/permission_prompt/permission_dialog_delegate.cc:
void PermissionDialogJavaDelegate::CreateDialog(content::WebContents* web_contents) {
// ...
PermissionDialogController::CreateDialog(env, j_delegate_); // Synchronous JNI call
if (permission_prompt_->ShouldUseRequestingOriginFavicon()) { // UAF occurs here
// ...
}
}
- JNI Execution: The code calls
PermissionDialogController::CreateDialogin Java. - Synchronous Dismissal: In Java, if
PermissionDialogCoordinator.showDialogdetermines that theModalDialogManageris null (which can happen in certain embedders like WebEngine where layout isn’t inflated, or during navigation/backgrounding races), it immediately callsonDismiss(DismissalType.AUTODISMISS_NO_DIALOG_MANAGER). - Native Re-entry: The
onDismisscall synchronously invokes the native methodPermissionDialogDelegate::Dismissed(). - Destruction: Inside
Dismissed(), the code checksif (!permission_prompt_->IsShowing()). Because the prompt factory has not yet returned,PermissionRequestManagerhas not assigned the new prompt to itsview_member. Therefore,IsShowing()evaluates tofalse, and the code callsDestroyJavaDelegate(), which callsjava_delegate_.reset(). - Use-After-Free: The
reset()completely frees thePermissionDialogJavaDelegateobject. The stack unwinds back toPermissionDialogJavaDelegate::CreateDialog(). The code proceeds to dereferencepermission_prompt_from the now-deletedthisobject.
Note on MiraclePtr (BackupRefPtr): MiraclePtr does not mitigate this issue. The object being freed is the PermissionDialogJavaDelegate itself, which is owned by a std::unique_ptr and accessed via the raw C++ this pointer. Because there are no raw_ptrs pointing to the PermissionDialogJavaDelegate object, PartitionAlloc frees the memory immediately without quarantining or zapping it. The subsequent read of permission_prompt_ reads from freed, potentially reallocated memory.
Potential Exploitation Steps
An attacker could potentially exploit this with the following steps:
- Trigger a permission request (e.g.,
navigator.permissions.request({name: 'geolocation'})) on an Android device. - Race the prompt creation with an action that invalidates the
WindowAndroidorModalDialogManager(e.g., navigating away, backgrounding the tab), or target an embedder where this is deterministically null. - During the synchronous
permission_prompt_->Dismiss()cascade triggered byDestroyJavaDelegate(), use the extensive observer notifications to spray the heap and reallocate the freedPermissionDialogJavaDelegatechunk with attacker-controlled bytes. - When execution returns to
CreateDialog(), the virtual methodShouldUseRequestingOriginFavicon()is called on the attacker’s fake object. - The attacker controls the vtable, successfully hijacking execution flow to achieve Remote Code Execution (RCE) in the browser process.
Note: Our tooling agent does not have the capability to run code or provide a working PoC, so these steps are theoretical.
Suggested Fix
Do not synchronously destroy the Java delegate while it might be on the stack.
One approach is to have PermissionDialogDelegate::Dismissed() post the destruction of the java_delegate_ to the message loop (e.g., using base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon()), ensuring it is only destroyed after the current stack unwinds.
Alternatively, PermissionDialogJavaDelegate::CreateDialog could check a weak pointer to itself after the JNI call returns, or the JNI call could be made asynchronous so it does not block and re-enter synchronously.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
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.