CVE-2026-7931
Overview
Background
- Download manager mediator (iOS)
DownloadManagerMediatorbridges a download task to the download UI consumer, telling it the file name and the originating host to show the user.- Originating host
- the host a download actually came from (
GetOriginatingHost/GetRedirectedUrl().GetHost()), surfaced so the user can judge the download’s provenance. - Omnibox host
- the host currently committed in the tab (
GetWebState()->GetLastCommittedURL().GetHost()), i.e. what the address bar shows. - `setOriginatingHost:display:`
- the pre-patch consumer API that took both a host string and a boolean deciding whether to display it.
Root Cause Analysis
The mediator computed display_originating_host as “the originating host differs from the committed/omnibox host,” OR-ing in a sticky should_show_origin_ member so that once shown it stayed shown. The consequence was that whenever a download’s originating host matched the host displayed in the address bar, the origin was suppressed from the download UI — and the logic only ran inside the @available(iOS 18.2, *) block, so on other paths no origin was meaningfully surfaced either. Hiding the origin when it “looks like” the current page lets a page present a download as if it came from the site the user is viewing, even when the true source (e.g. a redirected URL or a cross-origin initiator) is elsewhere, which is the insufficient-validation/UI-provenance weakness the advisory describes.
The fix removes the conditional entirely: it deletes display_originating_host, the omnibox-comparison, and the sticky should_show_origin_ member, and always calls [consumer_ setOriginatingHost:originating_host]; the consumer now always shows an origin host when one is available (or, per the commit message, a placeholder string for unknown origins), so the UI can no longer be made to omit the download’s origin.
should_show_origin_ state.Attack Path
- Serve a download A page initiates a download whose originating host equals the host currently shown in the omnibox (or arranges state so the comparison yields “same”).
- Suppress the origin label
The pre-patch
display_originating_hostevaluates false, sosetOriginatingHost:display:is told not to display the origin in the download manager UI. - Mislead the user The download appears without an explicit origin, so a user cannot distinguish a download genuinely from the current site from one whose true source differs, weakening the provenance signal the UI is meant to provide.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/chrome/browser/download/coordinator/download_manager_mediator.mm |
modified | |
ifios/chrome/browser/download/ui/download_manager_view_controller.mm |
modified |
Files Changed
ios/chrome/browser/download/coordinator/download_manager_mediator.hios/chrome/browser/download/coordinator/download_manager_mediator.mmios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mmios/chrome/browser/download/ui/download_manager_consumer.hios/chrome/browser/download/ui/download_manager_view_controller.mmios/chrome/test/fakes/fake_download_manager_consumer.h
Audit Directions
- Security UI gated on equality heuristicsAudit provenance/origin indicators that are hidden when they match some other displayed value; such “looks the same, hide it” logic can be gamed to mislead users.
- Sticky display stateReview boolean members that latch UI visibility across updates (
should_show_origin_-style), which can desynchronize the shown label from the current task. - Platform-conditional origin plumbingCheck origin/host surfacing that only runs under
@available/version guards, where unsupported paths may leave provenance unshown.
Patch
From 4b80a20a9e149f08efe8437ca00f3e87c113ade8 Mon Sep 17 00:00:00 2001
From: Quentin Pubert <qpubert@google.com>
Date: Wed, 18 Mar 2026 05:41:17 -0700
Subject: [PATCH] [iOS] Always show origin in download manager
This CL changes the download mediator UI so that the download origin
will no longer be hidden if it happens to match the content of the
address bar. Instead there will either be a origin host displayed if any
is available, or instead a string indicating that the origin of the
download is unknown.
Bug: 474338157
Change-Id: Iae9389ac3aed54b5afefc72079d277b79daeeb1a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7674638
Reviewed-by: Olivier Robin <olivierrobin@chromium.org>
Auto-Submit: Quentin Pubert <qpubert@google.com>
Reviewed-by: Ewann Pellé <ewannpv@chromium.org>
Commit-Queue: Olivier Robin <olivierrobin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1601200}
---
diff --git a/ios/chrome/browser/download/coordinator/download_manager_mediator.h b/ios/chrome/browser/download/coordinator/download_manager_mediator.h
index 6b8c8769..95c6771b 100644
--- a/ios/chrome/browser/download/coordinator/download_manager_mediator.h
+++ b/ios/chrome/browser/download/coordinator/download_manager_mediator.h
@@ -148,7 +148,6 @@
// Observers for NSNotificationCenter notifications.
__strong id<NSObject> application_foregrounding_observer_;
bool is_google_drive_app_installed_ = false;
- bool should_show_origin_ = false;
base::WeakPtrFactory<DownloadManagerMediator> weak_ptr_factory_;
};
diff --git a/ios/chrome/browser/download/coordinator/download_manager_mediator.mm b/ios/chrome/browser/download/coordinator/download_manager_mediator.mm
index e6d04fb5..1f074fb 100644
--- a/ios/chrome/browser/download/coordinator/download_manager_mediator.mm
+++ b/ios/chrome/browser/download/coordinator/download_manager_mediator.mm
@@ -246,7 +246,6 @@
[consumer_ setFileName:base::apple::FilePathToNSString(filename)];
NSString* originating_host = nil;
- bool display_originating_host = false;
if (@available(iOS 18.2, *)) {
// The originating host is only populated when compiled with iOS18.2 SDK
// and running on iOS18.2.
@@ -259,19 +258,9 @@
originating_host =
base::SysUTF8ToNSString(download_task_->GetRedirectedUrl().GetHost());
}
- // Only show the compute the originating host if it is not what is displayed
- // in the omnibox.
- display_originating_host =
- download_task_->GetWebState()->GetLastCommittedURL().GetHost() !=
- base::SysNSStringToUTF8(originating_host);
-
- // If the host was already displayed, keep it displayed
- display_originating_host = display_originating_host || should_show_origin_;
- should_show_origin_ = display_originating_host;
}
- [consumer_ setOriginatingHost:originating_host
- display:display_originating_host];
+ [consumer_ setOriginatingHost:originating_host];
int a11y_announcement = GetDownloadManagerA11yAnnouncement();
if (a11y_announcement != -1) {
diff --git a/ios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mm b/ios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mm
index b1159d7..c0273a1 100644
--- a/ios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mm
+++ b/ios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mm
@@ -351,21 +351,18 @@
mediator->UpdateConsumer();
EXPECT_NSEQ(consumer_.originatingHost,
base::SysUTF8ToNSString(GURL(kSameDomainURL).GetHost()));
- EXPECT_FALSE(consumer_.originatingHostDisplayed);
// WebState and task have different domains.
web_state_->SetCurrentURL(GURL(kCrossDomainURL));
mediator->UpdateConsumer();
EXPECT_NSEQ(consumer_.originatingHost,
base::SysUTF8ToNSString(GURL(kSameDomainURL).GetHost()));
- EXPECT_TRUE(consumer_.originatingHostDisplayed);
// Navigate back, origin should still be visible.
web_state_->SetCurrentURL(GURL(kSameDomainURL));
mediator->UpdateConsumer();
EXPECT_NSEQ(consumer_.originatingHost,
base::SysUTF8ToNSString(GURL(kSameDomainURL).GetHost()));
- EXPECT_TRUE(consumer_.originatingHostDisplayed);
// Reset Mediator.
web_state_->SetCurrentURL(GURL(kSameDomainURL));
@@ -380,19 +377,4 @@
mediator->UpdateConsumer();
EXPECT_NSEQ(consumer_.originatingHost,
base::SysUTF8ToNSString(GURL(kCrossDomainURL).GetHost()));
- EXPECT_TRUE(consumer_.originatingHostDisplayed);
-
- // Reset Mediator.
- web_state_->SetCurrentURL(GURL(kSameDomainURL));
- mediator = std::make_unique<DownloadManagerMediator>();
- mediator->SetDownloadTask(task());
- mediator->SetConsumer(consumer_);
-
- // Check that if no URL is available, the placeholder is displayed.
- task()->SetRedirectedURL(GURL("data:"));
- task()->SetOriginatingHost(@"");
- web_state_->SetCurrentURL(GURL("data:"));
- mediator->UpdateConsumer();
- EXPECT_NSEQ(consumer_.originatingHost, nil);
- EXPECT_TRUE(consumer_.originatingHostDisplayed);
}
diff --git a/ios/chrome/browser/download/ui/download_manager_consumer.h b/ios/chrome/browser/download/ui/download_manager_consumer.h
index fb5d489..8359d13e 100644
--- a/ios/chrome/browser/download/ui/download_manager_consumer.h
+++ b/ios/chrome/browser/download/ui/download_manager_consumer.h
@@ -41,11 +41,9 @@
// Sets visible state to Install Google Drive button.
- (void)setInstallDriveButtonVisible:(BOOL)visible animated:(BOOL)animated;
-// Sets the originating host for the consumer.
-// If `display` is false, then the string is not displayed to the user.
-// If `display` is true, the host is displayed in the details of the download,
-// with a special string if the host is actually empty.
-- (void)setOriginatingHost:(NSString*)originatingHost display:(BOOL)display;
+// Sets the originating host for the consumer. If `originatingHost` is nil then
+// it means that the source of the download is unknown.
+- (void)setOriginatingHost:(NSString*)originatingHost;
@optional
diff --git a/ios/chrome/browser/download/ui/download_manager_view_controller.mm b/ios/chrome/browser/download/ui/download_manager_view_controller.mm
index 86a9969..6af12ef 100644
--- a/ios/chrome/browser/download/ui/download_manager_view_controller.mm
+++ b/ios/chrome/browser/download/ui/download_manager_view_controller.mm
@@ -147,7 +147,6 @@
@interface DownloadManagerViewController () <FullscreenUIElement> {
NSString* _fileName;
NSString* _originatingHost;
- BOOL _displayOriginatingHost;
int64_t _countOfBytesReceived;
int64_t _countOfBytesExpectedToReceive;
float _progress;
@@ -352,13 +351,12 @@
}
}
-- (void)setOriginatingHost:(NSString*)originatingHost display:(BOOL)display {
- if ([_originatingHost isEqualToString:originatingHost] &&
- _displayOriginatingHost == display) {
+- (void)setOriginatingHost:(NSString*)originatingHost {
+ if (_originatingHost == originatingHost ||
+ [_originatingHost isEqualToString:originatingHost]) {
return;
}
_originatingHost = [originatingHost copy];
- _displayOriginatingHost = display;
[self updateViews];
}
@@ -817,16 +815,13 @@
self.statusLabel.text = [self localizedFileNameAndSizeWithPeriod:NO];
// Update detail label text.
NSMutableArray* details = [NSMutableArray array];
- if (_displayOriginatingHost) {
- if ([_originatingHost length]) {
- [details addObject:l10n_util::GetNSStringF(
- IDS_IOS_DOWNLOAD_MANAGER_ORIGIN_HOST_LABEL,
- base::SysNSStringToUTF16(_originatingHost))];
- } else {
- [details
- addObject:l10n_util::GetNSString(
- IDS_IOS_DOWNLOAD_MANAGER_ORIGIN_HOST_UNKNOWN_LABEL)];
- }
+ if ([_originatingHost length]) {
+ [details addObject:l10n_util::GetNSStringF(
+ IDS_IOS_DOWNLOAD_MANAGER_ORIGIN_HOST_LABEL,
+ base::SysNSStringToUTF16(_originatingHost))];
+ } else {
+ [details addObject:l10n_util::GetNSString(
+ IDS_IOS_DOWNLOAD_MANAGER_ORIGIN_HOST_UNKNOWN_LABEL)];
}
if (self.incognito) {
[details addObject:l10n_util::GetNSString(
diff --git a/ios/chrome/test/fakes/fake_download_manager_consumer.h b/ios/chrome/test/fakes/fake_download_manager_consumer.h
index f4b00442..32752eb6 100644
--- a/ios/chrome/test/fakes/fake_download_manager_consumer.h
+++ b/ios/chrome/test/fakes/fake_download_manager_consumer.h
@@ -34,9 +34,6 @@
// The host that triggered the download.
@property(nonatomic) NSString* originatingHost;
-// Whether the originating host is displayed.
-@property(nonatomic) BOOL originatingHostDisplayed;
-
@end
#endif // IOS_CHROME_TEST_FAKES_FAKE_DOWNLOAD_MANAGER_CONSUMER_H_
diff --git a/ios/chrome/test/fakes/fake_download_manager_consumer.mm b/ios/chrome/test/fakes/fake_download_manager_consumer.mm
index cf5d544..6d46d66 100644
Regression Test / PoC
diff --git a/ios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mm b/ios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mm
index b1159d7..c0273a1 100644
--- a/ios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mm
+++ b/ios/chrome/browser/download/coordinator/download_manager_mediator_unittest.mm
@@ -351,21 +351,18 @@
mediator->UpdateConsumer();
EXPECT_NSEQ(consumer_.originatingHost,
base::SysUTF8ToNSString(GURL(kSameDomainURL).GetHost()));
- EXPECT_FALSE(consumer_.originatingHostDisplayed);
// WebState and task have different domains.
web_state_->SetCurrentURL(GURL(kCrossDomainURL));
mediator->UpdateConsumer();
EXPECT_NSEQ(consumer_.originatingHost,
base::SysUTF8ToNSString(GURL(kSameDomainURL).GetHost()));
- EXPECT_TRUE(consumer_.originatingHostDisplayed);
// Navigate back, origin should still be visible.
web_state_->SetCurrentURL(GURL(kSameDomainURL));
mediator->UpdateConsumer();
EXPECT_NSEQ(consumer_.originatingHost,
base::SysUTF8ToNSString(GURL(kSameDomainURL).GetHost()));
- EXPECT_TRUE(consumer_.originatingHostDisplayed);
// Reset Mediator.
web_state_->SetCurrentURL(GURL(kSameDomainURL));
@@ -380,19 +377,4 @@
mediator->UpdateConsumer();
EXPECT_NSEQ(consumer_.originatingHost,
base::SysUTF8ToNSString(GURL(kCrossDomainURL).GetHost()));
- EXPECT_TRUE(consumer_.originatingHostDisplayed);
-
- // Reset Mediator.
- web_state_->SetCurrentURL(GURL(kSameDomainURL));
- mediator = std::make_unique<DownloadManagerMediator>();
- mediator->SetDownloadTask(task());
- mediator->SetConsumer(consumer_);
-
- // Check that if no URL is available, the placeholder is displayed.
- task()->SetRedirectedURL(GURL("data:"));
- task()->SetOriginatingHost(@"");
- web_state_->SetCurrentURL(GURL("data:"));
- mediator->UpdateConsumer();
- EXPECT_NSEQ(consumer_.originatingHost, nil);
- EXPECT_TRUE(consumer_.originatingHostDisplayed);
}
diff --git a/ios/chrome/test/fakes/fake_download_manager_consumer.h b/ios/chrome/test/fakes/fake_download_manager_consumer.h
index f4b00442..32752eb6 100644
--- a/ios/chrome/test/fakes/fake_download_manager_consumer.h
+++ b/ios/chrome/test/fakes/fake_download_manager_consumer.h
@@ -34,9 +34,6 @@
// The host that triggered the download.
@property(nonatomic) NSString* originatingHost;
-// Whether the originating host is displayed.
-@property(nonatomic) BOOL originatingHostDisplayed;
-
@end
#endif // IOS_CHROME_TEST_FAKES_FAKE_DOWNLOAD_MANAGER_CONSUMER_H_
diff --git a/ios/chrome/test/fakes/fake_download_manager_consumer.mm b/ios/chrome/test/fakes/fake_download_manager_consumer.mm
index cf5d544..6d46d66 100644
--- a/ios/chrome/test/fakes/fake_download_manager_consumer.mm
+++ b/ios/chrome/test/fakes/fake_download_manager_consumer.mm
@@ -28,9 +28,4 @@
- (void)setCanOpenFile:(BOOL)canOpenFile {
}
-- (void)setOriginatingHost:(NSString*)originatingHost display:(BOOL)display {
- _originatingHost = originatingHost;
- _originatingHostDisplayed = display;
-}
-
@end