CVE-2025-12431
Overview
Files Changed
chrome/browser/extensions/api/downloads/downloads_api.cc
Patch
From f9f478196e288d68bf0b64a30fa7618a27fe1fc2 Mon Sep 17 00:00:00 2001
From: Min Qin <qinmin@chromium.org>
Date: Tue, 02 Sep 2025 16:44:44 -0700
Subject: [PATCH] Fix an issue when determining whether a download is resumable in extension
To resume a download, the download should be in an interrupted state,
rather than paused state. The current code will just always return
true and skip the canResume() check.
Bug: 436887350
Change-Id: Ie5cd0e7684a1eeacc4d80899098e89137dc1a85f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6907509
Reviewed-by: David Trainor <dtrainor@chromium.org>
Commit-Queue: Min Qin <qinmin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1509939}
---
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
index f89b9608..4dfb639 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -1288,7 +1288,9 @@
browser_context(), include_incognito_information(), params->download_id);
std::string error;
if (InvalidId(download_item, &error) ||
- Fault(download_item->IsPaused() && !download_item->CanResume(),
+ Fault(download_item->GetState() == DownloadItem::CANCELLED ||
+ (download_item->GetState() == DownloadItem::INTERRUPTED &&
+ !download_item->CanResume()),
download_extension_errors::kNotResumable, &error)) {
return RespondNow(Error(std::move(error)));
}
Original Bug Report
Security: Extension can download file by resuming interrupted download
SUMMARY
An extension with downloads permission can download a file:// URL by calling downloads.download(), waiting for the download to be interrupted, and then calling downloads.resume() on the interrupted download. This causes the download to be initiated by the browser, bypassing DownloadRequestUtils::IsURLSafe()/CPSP::CanRequestURL() checks.
Downloading local files is meant to be blocked when the extension does not have file:// permissions. The tabs.create() workaround to download some local files was also blocked in https://crrev.com/c/4772028 (August 2023, also see issue 40063229).
The resume bypass also works with all other ways to resume downloads, including resumes made through UI.
When chained with other bugs, such as issue 433800617 or issue 435684924, or if the attacker otherwise convinces user to upload the downloaded file, the attacker can steal the downloaded file’s contents.
VULNERABILITY DETAILS
In DownloadManagerImpl::BeginDownloadInternal()…
void DownloadManagerImpl::BeginDownloadInternal(
std::unique_ptr<download::DownloadUrlParameters> params,
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
bool is_new_download,
const std::string& serialized_embedder_download_data) {
// Check if the renderer is permitted to request the requested URL.
if (params->render_process_host_id() >= 0 &&
!DownloadRequestUtils::IsURLSafe(params->render_process_host_id(),
params->url())) {
CreateInterruptedDownload(
std::move(params),
download::DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST,
weak_factory_.GetWeakPtr());
return;
}
// ...
}
…CPSP::CanRequestURL() is called through DownloadRequestUtils::IsURLSafe() only if render_process_host_id() >= 0.
When downloads.download() is called with a file:// URL, render_process_host_id is greater than zero, and CPSP::CanRequestURL() returns false for file:// URLs, therefore the download is interrupted.
However, when downloads.resume() is called for the interrupted download, render_process_host_id is -1, which per this comment means it’s not associated with a frame. This means the IsURLSafe()/CPSP::CanRequestURL() check is bypassed, and the file is downloaded.
Additional context
In the DownloadUrlParameters() constructor that leaves render_process_host_id at -1, there is this comment that points out the security checks bypass:
// Constructs a download not associated with a frame.
//
// It is not safe to have downloads not associated with a frame and
// this should only be done in a limited set of cases where the download URL
// has been previously vetted. A download that's initiated without
// associating it with a frame don't receive the same security checks
// as a request that's associated with one. Hence, downloads that are not
// associated with a frame should only be made for URLs that are either
// trusted or URLs that have previously been successfully issued using a
// non-privileged frame.
DownloadUrlParameters(
const GURL& url,
const net::NetworkTrafficAnnotationTag& traffic_annotation);
Unfortunately, this constructor is used in the downloads.resume() code path and most other download resume code paths: DownloadsResumeFunction::Run() calls DownloadItem::Resume() which calls DownloadItemImpl::ResumeInterruptedDownload().
ResumeInterruptedDownload() has this comment explaining why the unsafe constructor is used for resumes:
// Avoid using the WebContents even if it's still around. Resumption requests
// are consistently routed through the no-renderer code paths so that the
// request will not be dropped if the WebContents (and by extension, the
// associated renderer) goes away before a response is received.
std::unique_ptr<DownloadUrlParameters> download_params(
new DownloadUrlParameters(GetURL(), traffic_annotation));
BISECT
Likely commit 00b621f5126d538df488090c19175ee892a7161b (February 2016) which starts using the unsafe constructor in resume code path.
I’ll verify bisect later today.
VERSION
Chrome version: 139.0.7258.67 Stable, 141.0.7340.0 Canary
Operating System: Windows 10
REPRODUCTION CASE
To download a file in a user directory, the PoC first makes a throwaway download to get the username from the download path. Then we make the second download exploiting the vulnerability.
The core vulnerability itself doesn’t need user interaction to download the file, nor does it need to show an extension page or otherwise open a new tab.
I’ll upload chained scenarios in comments.
Setup:
- Install attached extension:
manifest.json,background.js,page.html,page.js. - Disable
file://URL access in the extension details page (this is enabled by default for unpacked extensions).
Scenario 1: Chrome history file
- Click extension icon to trigger download. (Note: Initiating/resuming downloads doesn’t require user interaction, so this step is only for PoC.)
- Click the page, then select the downloaded file. (Note: When chained, we don’t need user to explicitly upload file.)
Observed: Extension can download file:// URLs without file permission. When chained with other vulns, or through social engineering, user can read downloaded local file.
Expected: Extension cannot download file:// URLs without file permission.
Debug scenario
To see the attack occurring more slowly, you can modify background.js and set debug to true, which delays the resume() call by two seconds. Reminder: Disable file:// access in extension for proper repro.
Credit Information
Reporter credit: Alesandro Ortiz https://AlesandroOrtiz.com
- http://crrev.com/00b621f5126d538df488090c19175ee892a7161b
- https://AlesandroOrtiz.com
- https://crrev.com/c/4772028
- https://issuetracker.google.com/issues/40063229
- https://issuetracker.google.com/issues/433800617
- https://issuetracker.google.com/issues/435684924
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/extensions/api/downloads/downloads_api.cc;l=1297;drc=27f899da22267983fb22a8caf88d300a9d49cf3d
- https://source.chromium.org/chromium/chromium/src/+/main:components/download/internal/common/download_item_impl.cc;l=2543;drc=877c4eff05eee91a64ab498b2f02928fe718278b
- https://source.chromium.org/chromium/chromium/src/+/main:components/download/internal/common/download_item_impl.cc;l=2592;drc=877c4eff05eee91a64ab498b2f02928fe718278b
- https://source.chromium.org/chromium/chromium/src/+/main:components/download/public/common/download_url_parameters.h;l=305;drc=877c4eff05eee91a64ab498b2f02928fe718278b
- https://source.chromium.org/chromium/chromium/src/+/main:components/download/public/common/download_url_parameters.h;l=78;drc=877c4eff05eee91a64ab498b2f02928fe718278b
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/download/download_manager_impl.cc;l=1520;drc=877c4eff05eee91a64ab498b2f02928fe718278b
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/download/download_request_utils.cc;l=31;drc=27f899da22267983fb22a8caf88d300a9d49cf3d