CVE-2026-12452
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/download/android/dangerous_download_dialog_bridge.cc |
modified | |
ifchrome/browser/download/android/policy_warning_download_dialog_bridge.cc |
modified |
Files Changed
chrome/browser/download/android/dangerous_download_dialog_bridge.ccchrome/browser/download/android/download_controller.ccchrome/browser/download/android/download_controller.hchrome/browser/download/android/policy_warning_download_dialog_bridge.cc
Patch
From 3268f8dd3e2431833508afeb1626b925bb0ecaa5 Mon Sep 17 00:00:00 2001
From: Brian Lefler <bcl@google.com>
Date: Fri, 05 Jun 2026 15:56:19 -0700
Subject: [PATCH] Schedule download removal on the UI thread for Android bridges
DangerousDownloadDialogBridge and PolicyWarningDownloadDialogBridge
were synchronously removing DownloadItems when no WindowAndroid was
present.
This CL exposes DownloadController::ScheduleRemoveDownloadItem and
uses it to defer the destruction of the DownloadItem.
TAG=agy
Bug: 515462244
Change-Id: I0ca91ba7c62e1768310eee1c657a70ddd9b49dfc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7905754
Reviewed-by: Yaw Frempong <yawfrempong@google.com>
Reviewed-by: Xinghui Lu <xinghuilu@chromium.org>
Commit-Queue: Brian Lefler <bcl@google.com>
Cr-Commit-Position: refs/heads/main@{#1642658}
---
diff --git a/chrome/browser/download/android/dangerous_download_dialog_bridge.cc b/chrome/browser/download/android/dangerous_download_dialog_bridge.cc
index 7887ee4..89c5362 100644
--- a/chrome/browser/download/android/dangerous_download_dialog_bridge.cc
+++ b/chrome/browser/download/android/dangerous_download_dialog_bridge.cc
@@ -13,6 +13,7 @@
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/android/android_theme_resources.h"
#include "chrome/browser/android/resource_mapper.h"
+#include "chrome/browser/download/android/download_controller.h"
#include "chrome/browser/download/android/download_dialog_utils.h"
#include "chrome/grit/generated_resources.h"
#include "components/url_formatter/elide_url.h"
@@ -62,7 +63,7 @@
return;
}
if (!window_android) {
- download_item->Remove();
+ DownloadController::ScheduleRemoveDownloadItem(download_item);
return;
}
download_item->AddObserver(this);
@@ -111,7 +112,7 @@
&download_items_, download_guid);
if (download) {
download->RemoveObserver(this);
- download->Remove();
+ DownloadController::ScheduleRemoveDownloadItem(download);
}
}
diff --git a/chrome/browser/download/android/download_controller.cc b/chrome/browser/download/android/download_controller.cc
index 97b0f08..1206b47 100644
--- a/chrome/browser/download/android/download_controller.cc
+++ b/chrome/browser/download/android/download_controller.cc
@@ -135,15 +135,6 @@
}
}
-void ScheduleRemoveDownloadItem(download::DownloadItem* download) {
- auto download_manager_getter = std::make_unique<DownloadManagerGetter>(
- content::DownloadItemUtils::GetBrowserContext(download)
- ->GetDownloadManager());
- content::GetUIThreadTaskRunner({})->PostTask(
- FROM_HERE,
- base::BindOnce(&RemoveDownloadItem, std::move(download_manager_getter),
- download->GetGuid()));
-}
bool ShouldOpenPdfInline(DownloadItem* item) {
BrowserContext* context = content::DownloadItemUtils::GetBrowserContext(item);
@@ -225,6 +216,18 @@
} // namespace
+// static
+void DownloadController::ScheduleRemoveDownloadItem(
+ download::DownloadItem* item) {
+ auto download_manager_getter = std::make_unique<DownloadManagerGetter>(
+ content::DownloadItemUtils::GetBrowserContext(item)
+ ->GetDownloadManager());
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
+ base::BindOnce(&RemoveDownloadItem, std::move(download_manager_getter),
+ item->GetGuid()));
+}
+
static void JNI_DownloadController_CancelDownload(
JNIEnv* env,
Profile* profile,
diff --git a/chrome/browser/download/android/download_controller.h b/chrome/browser/download/android/download_controller.h
index 954b0ba..943c4eb2 100644
--- a/chrome/browser/download/android/download_controller.h
+++ b/chrome/browser/download/android/download_controller.h
@@ -64,6 +64,9 @@
static void CloseTabIfEmpty(content::WebContents* web_contents,
download::DownloadItem* download);
+ // Schedules the removal of a download item on the UI thread.
+ static void ScheduleRemoveDownloadItem(download::DownloadItem* download);
+
// Callback when user permission prompt finishes. Args: whether file access
// permission is acquired, which permission to update.
using AcquirePermissionCallback =
diff --git a/chrome/browser/download/android/policy_warning_download_dialog_bridge.cc b/chrome/browser/download/android/policy_warning_download_dialog_bridge.cc
index 33aadf3..04c38d8 100644
--- a/chrome/browser/download/android/policy_warning_download_dialog_bridge.cc
+++ b/chrome/browser/download/android/policy_warning_download_dialog_bridge.cc
@@ -13,6 +13,7 @@
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/android/android_theme_resources.h"
#include "chrome/browser/android/resource_mapper.h"
+#include "chrome/browser/download/android/download_controller.h"
#include "chrome/browser/download/android/download_dialog_utils.h"
#include "chrome/grit/generated_resources.h"
#include "components/url_formatter/elide_url.h"
@@ -44,7 +45,7 @@
return;
}
if (!window_android) {
- download_item->Remove();
+ DownloadController::ScheduleRemoveDownloadItem(download_item);
return;
}
download_item->AddObserver(this);
@@ -84,7 +85,7 @@
&download_items_, download_guid);
if (download) {
download->RemoveObserver(this);
- download->Remove();
+ DownloadController::ScheduleRemoveDownloadItem(download);
}
}
Original Bug Report
Potential Browser-process UAF in DownloadItemImpl due to nested UpdateObservers() re-entrancy
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A Use-After-Free (UAF) vulnerability in the browser process occurs when a DownloadItemImpl is synchronously removed during an observer notification loop. A logic error in the handling of a safety flag allows the object’s destruction to bypass a critical check, leading to memory corruption when the notification loop continues execution on freed memory.
Affected files:
components/download/internal/common/download_item_impl.cccontent/browser/download/download_manager_impl.ccchrome/browser/download/android/download_controller.ccchrome/browser/download/android/dangerous_download_dialog_bridge.cc
Estimated timestamp from git blame: 2017-08-17
Description
A potential Use-After-Free (UAF) vulnerability exists in DownloadItemImpl due to improper management of the is_updating_observers_ state flag during re-entrant calls to UpdateObservers(). This can allow a DownloadItemImpl object to be destroyed synchronously while an active iteration over its observers is still on the stack, leading to subsequent memory access on the freed object.
Technical Details
In components/download/internal/common/download_item_impl.cc, the UpdateObservers() method uses the is_updating_observers_ boolean flag to guard against re-entrancy and to ensure the object is not destroyed during observer notification. On entry, the flag is set to true, and on exit, it is unconditionally reset to false (line 556).
A vulnerability arises when a nested call to UpdateObservers() occurs. This happens if an observer synchronously triggers the Remove() method of the DownloadItemImpl. The Remove() method itself calls UpdateObservers() (the nested call). When this nested call completes, it resets the is_updating_observers_ flag to false.
Remove() then proceeds to notify its delegate (DownloadManagerImpl), which erases the unique_ptr owning the DownloadItemImpl (content/browser/download/download_manager_impl.cc:1049), triggering the destructor. The destructor ~DownloadItemImpl() contains a CHECK(!is_updating_observers_) designed to prevent destruction during iteration. However, because the nested UpdateObservers() call already reset the flag to false, this check passes, and the object is freed.
When the stack unwinds back to the original (outer) UpdateObservers() call, the code continues execution using the freed this pointer:
- The range-for loop continues, accessing the destroyed
ObserverList(line 554). - The code performs a UAF write:
this->is_updating_observers_ = false(line 556). - Control returns to the original caller (e.g.,
OnTargetResolved), which continues accessing the freed object (e.g., callingMaybeCompleteDownload()).
Potential Trigger (Android)
On Chrome for Android, a synchronous path to this UAF exists. During target determination, if a download is identified as dangerous, DownloadController::OnDownloadUpdated is called. If the associated WebContents is detached from its WindowAndroid (e.g., during tab manipulation), DangerousDownloadDialogBridge::Show is invoked with a null window, which synchronously calls item->Remove() (chrome/browser/download/android/dangerous_download_dialog_bridge.cc:65).
Suggested Potential Steps to Reproduce
- Initiate a download of a file type that triggers a dangerous file warning (e.g., an
.apk). - Manipulate the browser state (e.g., by detaching or closing the tab) at the exact moment target determination completes (
OnTargetResolved). - This triggers the synchronous removal path on Android, causing the UAF in the browser process.
Note: These are potential steps; our tooling agent does not yet have the ability to run code or provide a functional PoC.
Suggested Fix
To prevent this issue, the is_updating_observers_ flag should be managed in a way that respects nesting (e.g., using a re-entrancy counter or base::AutoReset<bool>). Additionally, the destruction of DownloadItemImpl should be deferred if a notification loop is currently active on the stack. The Remove() implementation should be audited to ensure that deletion via the delegate is never performed synchronously during an observer update.
Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/download/android/dangerous_download_dialog_bridge.cc;l=65
- https://source.chromium.org/chromium/chromium/src/+/main:components/download/internal/common/download_item_impl.cc;l=554
- https://source.chromium.org/chromium/chromium/src/+/main:components/download/internal/common/download_item_impl.cc;l=556
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/download/download_manager_impl.cc;l=1049