| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/safe_browsing/download_protection/download_request_maker.h" |
| |
| #include <memory> |
| |
| #include "base/feature_list.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/download/download_item_warning_data.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/policy/chrome_browser_policy_connector.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h" |
| #include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h" |
| #include "chrome/browser/safe_browsing/chrome_user_population_helper.h" |
| #include "chrome/browser/safe_browsing/download_protection/download_protection_service.h" |
| #include "chrome/browser/safe_browsing/download_protection/download_protection_util.h" |
| #include "components/safe_browsing/core/common/features.h" |
| #include "components/safe_browsing/core/common/proto/csd.pb.h" |
| #include "components/safe_browsing/core/common/utils.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/download_item_utils.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace safe_browsing { |
| |
| namespace { |
| |
| // The version of this client supporting tailored warnings. |
| // Please update the description of TailoredInfo field in csd.proto when |
| // changing this value. |
| // LINT.IfChange |
| constexpr int kTailoredWarningVersion = 5; |
| // LINT.ThenChange(/components/safe_browsing/core/common/proto/csd.proto) |
| |
| DownloadRequestMaker::TabUrls TabUrlsFromWebContents( |
| base::WeakPtr<content::WebContents> web_contents) { |
| DownloadRequestMaker::TabUrls result; |
| if (web_contents) { |
| content::NavigationEntry* entry = |
| web_contents->GetController().GetVisibleEntry(); |
| if (entry) { |
| result.url = entry->GetURL(); |
| result.referrer = entry->GetReferrer().url; |
| } |
| } |
| return result; |
| } |
| |
| void SetDownloadItemWarningData(download::DownloadItem* item, |
| const std::optional<std::string>& password, |
| const FileAnalyzer::Results& results) { |
| DownloadItemWarningData::SetIsTopLevelEncryptedArchive( |
| item, results.encryption_info.is_top_level_encrypted); |
| DownloadItemWarningData::SetIsFullyExtractedArchive( |
| item, results.archive_summary.parser_status() == |
| ClientDownloadRequest::ArchiveSummary::VALID && |
| (!results.encryption_info.is_encrypted || |
| results.encryption_info.password_status == |
| EncryptionInfo::kKnownCorrect)); |
| if (password.has_value()) { |
| DownloadItemWarningData::SetHasIncorrectPassword( |
| item, results.encryption_info.password_status == |
| EncryptionInfo::kKnownIncorrect); |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<DownloadRequestMaker> |
| DownloadRequestMaker::CreateFromDownloadItem( |
| scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor, |
| download::DownloadItem* item, |
| base::optional_ref<const std::string> password) { |
| std::vector<ClientDownloadRequest::Resource> resources; |
| for (size_t i = 0; i < item->GetUrlChain().size(); ++i) { |
| ClientDownloadRequest::Resource resource; |
| resource.set_url(ShortURLForReporting(item->GetUrlChain()[i])); |
| if (i == item->GetUrlChain().size() - 1) { |
| // The last URL in the chain is the download URL. |
| resource.set_type(ClientDownloadRequest::DOWNLOAD_URL); |
| resource.set_referrer(ShortURLForReporting(item->GetReferrerUrl())); |
| DVLOG(2) << "dl url " << resource.url(); |
| if (!item->GetRemoteAddress().empty()) { |
| resource.set_remote_ip(item->GetRemoteAddress()); |
| DVLOG(2) << " dl url remote addr: " << resource.remote_ip(); |
| } |
| DVLOG(2) << "dl referrer " << resource.referrer(); |
| } else { |
| DVLOG(2) << "dl redirect " << i << " " << resource.url(); |
| resource.set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT); |
| } |
| |
| resources.push_back(std::move(resource)); |
| } |
| |
| return std::make_unique<DownloadRequestMaker>( |
| binary_feature_extractor, |
| content::DownloadItemUtils::GetBrowserContext(item), |
| TabUrls{item->GetTabUrl(), item->GetTabReferrerUrl()}, |
| #if BUILDFLAG(IS_ANDROID) |
| /*target_file_name=*/item->GetFileNameToReportUser(), |
| #else |
| /*target_file_name=*/item->GetTargetFilePath(), |
| #endif |
| item->GetFullPath(), item->GetURL(), item->GetHash(), |
| item->GetReceivedBytes(), resources, item->HasUserGesture(), |
| static_cast<ReferrerChainData*>( |
| item->GetUserData(ReferrerChainData::kDownloadReferrerChainDataKey)), |
| password, DownloadProtectionService::GetDownloadPingToken(item), |
| // It's safe to use a raw pointer to `item` here because this class is |
| // owned by the CheckClientDownloadRequest, which observes for `item` |
| // being destroyed, and deletes this if it is. |
| base::BindOnce(&SetDownloadItemWarningData, item, |
| password.CopyAsOptional())); |
| } |
| |
| // static |
| std::unique_ptr<DownloadRequestMaker> |
| DownloadRequestMaker::CreateFromFileSystemAccess( |
| scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor, |
| const content::FileSystemAccessWriteItem& item) { |
| ClientDownloadRequest::Resource resource; |
| resource.set_url( |
| ShortURLForReporting(GetFileSystemAccessDownloadUrl(item.frame_url))); |
| resource.set_type(ClientDownloadRequest::DOWNLOAD_URL); |
| if (item.frame_url.is_valid()) |
| resource.set_referrer(ShortURLForReporting(item.frame_url)); |
| |
| std::unique_ptr<ReferrerChainData> referrer_chain_data = |
| IdentifyReferrerChain( |
| item, |
| DownloadProtectionService::GetDownloadAttributionUserGestureLimit()); |
| |
| return std::make_unique<DownloadRequestMaker>( |
| binary_feature_extractor, item.browser_context, |
| TabUrlsFromWebContents(item.web_contents), item.target_file_path, |
| item.full_path, GetFileSystemAccessDownloadUrl(item.frame_url), |
| item.sha256_hash, item.size, |
| std::vector<ClientDownloadRequest::Resource>{resource}, |
| item.has_user_gesture, referrer_chain_data.get(), std::nullopt, |
| /*previous_token=*/"", base::DoNothing()); |
| } |
| |
| DownloadRequestMaker::DownloadRequestMaker( |
| scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor, |
| content::BrowserContext* browser_context, |
| TabUrls tab_urls, |
| base::FilePath target_file_name, |
| base::FilePath full_path, |
| GURL source_url, |
| std::string sha256_hash, |
| int64_t length, |
| const std::vector<ClientDownloadRequest::Resource>& resources, |
| bool is_user_initiated, |
| ReferrerChainData* referrer_chain_data, |
| base::optional_ref<const std::string> password, |
| const std::string& previous_token, |
| base::OnceCallback<void(const FileAnalyzer::Results&)> on_results_callback) |
| : browser_context_(browser_context), |
| request_(std::make_unique<ClientDownloadRequest>()), |
| binary_feature_extractor_(binary_feature_extractor), |
| tab_urls_(tab_urls), |
| target_file_name_(target_file_name), |
| full_path_(full_path), |
| password_(password.CopyAsOptional()), |
| on_results_callback_(std::move(on_results_callback)) { |
| request_->set_url(ShortURLForReporting(source_url)); |
| request_->mutable_digests()->set_sha256(sha256_hash); |
| request_->set_length(length); |
| for (const ClientDownloadRequest::Resource& resource : resources) { |
| *request_->add_resources() = resource; |
| } |
| |
| request_->set_user_initiated(is_user_initiated); |
| |
| if (referrer_chain_data && |
| !referrer_chain_data->GetReferrerChain()->empty()) { |
| request_->mutable_referrer_chain()->Swap( |
| referrer_chain_data->GetReferrerChain()); |
| request_->mutable_referrer_chain_options() |
| ->set_recent_navigations_to_collect( |
| referrer_chain_data->recent_navigations_to_collect()); |
| } |
| |
| request_->set_previous_token(previous_token); |
| } |
| |
| DownloadRequestMaker::~DownloadRequestMaker() = default; |
| |
| void DownloadRequestMaker::Start(DownloadRequestMaker::Callback callback) { |
| CallbackWithDetails callback_adapter = |
| base::IgnoreArgs<RequestCreationDetails>(std::move(callback)); |
| Start(std::move(callback_adapter)); |
| } |
| |
| void DownloadRequestMaker::Start( |
| DownloadRequestMaker::CallbackWithDetails callback) { |
| callback_ = std::move(callback); |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context_); |
| bool is_under_advanced_protection = |
| profile && AdvancedProtectionStatusManagerFactory::GetForProfile(profile) |
| ->IsUnderAdvancedProtection(); |
| |
| *request_->mutable_population() = GetUserPopulationForProfile(profile); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| if (base::FeatureList::IsEnabled(kMaliciousApkDownloadCheck)) { |
| std::string malicious_apk_check = "MaliciousApkDownloadCheck"; |
| if (kMaliciousApkDownloadCheckTelemetryOnly.Get()) { |
| base::StrAppend(&malicious_apk_check, {".TelemetryOnly"}); |
| } |
| request_->mutable_population()->add_finch_active_groups( |
| std::move(malicious_apk_check)); |
| } |
| #endif |
| |
| request_->set_request_ap_verdicts(is_under_advanced_protection); |
| request_->set_locale(g_browser_process->GetApplicationLocale()); |
| request_->set_file_basename(target_file_name_.BaseName().AsUTF8Unsafe()); |
| |
| PopulateTailoredInfo(); |
| |
| file_analyzer_->Start( |
| target_file_name_, full_path_, password_, |
| base::BindOnce(&DownloadRequestMaker::OnFileFeatureExtractionDone, |
| weakptr_factory_.GetWeakPtr())); |
| } |
| |
| void DownloadRequestMaker::OnFileFeatureExtractionDone( |
| FileAnalyzer::Results results) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| details_.inspection_type = results.inspection_performed; |
| |
| request_->set_download_type(results.type); |
| request_->mutable_archived_binary()->CopyFrom(results.archived_binaries); |
| request_->mutable_signature()->CopyFrom(results.signature_info); |
| request_->mutable_image_headers()->CopyFrom(results.image_headers); |
| request_->mutable_archive_summary()->CopyFrom(results.archive_summary); |
| |
| #if BUILDFLAG(IS_MAC) |
| if (!results.disk_image_signature.empty()) { |
| request_->set_udif_code_signature(results.disk_image_signature.data(), |
| results.disk_image_signature.size()); |
| } |
| if (!results.detached_code_signatures.empty()) { |
| request_->mutable_detached_code_signature()->CopyFrom( |
| results.detached_code_signatures); |
| } |
| #endif |
| |
| if (on_results_callback_) { |
| std::move(on_results_callback_).Run(results); |
| } |
| |
| GetTabRedirects(); |
| } |
| |
| void DownloadRequestMaker::GetTabRedirects() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!tab_urls_.url.is_valid()) { |
| OnGotTabRedirects({}); |
| return; |
| } |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context_); |
| if (!profile) { |
| OnGotTabRedirects({}); |
| return; |
| } |
| |
| history::HistoryService* history = HistoryServiceFactory::GetForProfile( |
| profile, ServiceAccessType::EXPLICIT_ACCESS); |
| if (!history) { |
| OnGotTabRedirects({}); |
| return; |
| } |
| |
| history->QueryRedirectsTo( |
| tab_urls_.url, |
| base::BindOnce(&DownloadRequestMaker::OnGotTabRedirects, |
| weakptr_factory_.GetWeakPtr()), |
| &request_tracker_); |
| } |
| |
| void DownloadRequestMaker::OnGotTabRedirects( |
| history::RedirectList redirect_list) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| for (size_t i = 0; i < redirect_list.size(); ++i) { |
| ClientDownloadRequest::Resource* resource = request_->add_resources(); |
| DVLOG(2) << "tab redirect " << i << " " << redirect_list[i].spec(); |
| resource->set_url(ShortURLForReporting(redirect_list[i])); |
| resource->set_type(ClientDownloadRequest::TAB_REDIRECT); |
| } |
| if (tab_urls_.url.is_valid()) { |
| ClientDownloadRequest::Resource* resource = request_->add_resources(); |
| resource->set_url(ShortURLForReporting(tab_urls_.url)); |
| DVLOG(2) << "tab url " << resource->url(); |
| resource->set_type(ClientDownloadRequest::TAB_URL); |
| if (tab_urls_.referrer.is_valid()) { |
| resource->set_referrer(ShortURLForReporting(tab_urls_.referrer)); |
| DVLOG(2) << "tab referrer " << resource->referrer(); |
| } |
| } |
| |
| std::move(callback_).Run(details_, std::move(request_)); |
| } |
| |
| void DownloadRequestMaker::PopulateTailoredInfo() { |
| ClientDownloadRequest::TailoredInfo tailored_info; |
| int version = kTailoredWarningVersion; |
| tailored_info.set_version(version); |
| *request_->mutable_tailored_info() = tailored_info; |
| } |
| |
| } // namespace safe_browsing |