blob: be71e3a5137eb0a02b30ef2b93e4421c520c5bfe [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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/check_client_download_request.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.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/cloud_content_scanning/binary_upload_service.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
#include "chrome/browser/safe_browsing/dm_token_utils.h"
#include "chrome/browser/safe_browsing/download_protection/check_client_download_request_base.h"
#include "chrome/browser/safe_browsing/download_protection/deep_scanning_request.h"
#include "chrome/browser/safe_browsing/download_protection/download_feedback_service.h"
#include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
#include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
#include "chrome/common/safe_browsing/download_type_util.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/policy/core/browser/url_util.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/safe_browsing/core/common/utils.h"
#include "components/safe_browsing/core/features.h"
#include "components/safe_browsing/core/file_type_policies.h"
#include "components/safe_browsing/core/proto/csd.pb.h"
#include "components/safe_browsing/core/proto/webprotect.pb.h"
#include "components/url_matcher/url_matcher.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/download_item_utils.h"
namespace safe_browsing {
namespace {
// This function is called when the result of malware scanning is already known
// (via |reason|), but we still want to perform DLP scanning.
void MaybeOverrideDlpScanResult(DownloadCheckResultReason reason,
CheckDownloadRepeatingCallback callback,
DownloadCheckResult deep_scan_result) {
if (reason == REASON_DOWNLOAD_DANGEROUS ||
reason == REASON_DOWNLOAD_DANGEROUS_HOST ||
reason == REASON_DOWNLOAD_POTENTIALLY_UNWANTED ||
reason == REASON_DOWNLOAD_UNCOMMON) {
// Don't let safe deep scanning results override these previous
// dangerous reasons.
switch (deep_scan_result) {
case DownloadCheckResult::UNKNOWN:
case DownloadCheckResult::SENSITIVE_CONTENT_WARNING:
case DownloadCheckResult::DEEP_SCANNED_SAFE:
if (reason == REASON_DOWNLOAD_DANGEROUS)
callback.Run(DownloadCheckResult::DANGEROUS);
else if (reason == REASON_DOWNLOAD_DANGEROUS_HOST)
callback.Run(DownloadCheckResult::DANGEROUS_HOST);
else if (reason == REASON_DOWNLOAD_POTENTIALLY_UNWANTED)
callback.Run(DownloadCheckResult::POTENTIALLY_UNWANTED);
else
callback.Run(DownloadCheckResult::UNCOMMON);
return;
case DownloadCheckResult::ASYNC_SCANNING:
case DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED:
case DownloadCheckResult::BLOCKED_TOO_LARGE:
case DownloadCheckResult::SENSITIVE_CONTENT_BLOCK:
case DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE:
callback.Run(deep_scan_result);
return;
case DownloadCheckResult::SAFE:
case DownloadCheckResult::UNCOMMON:
case DownloadCheckResult::DANGEROUS_HOST:
case DownloadCheckResult::WHITELISTED_BY_POLICY:
case DownloadCheckResult::PROMPT_FOR_SCANNING:
case DownloadCheckResult::DANGEROUS:
case DownloadCheckResult::POTENTIALLY_UNWANTED:
NOTREACHED();
return;
}
} else if (reason == REASON_WHITELISTED_URL) {
callback.Run(deep_scan_result);
return;
}
NOTREACHED();
}
} // namespace
using content::BrowserThread;
CheckClientDownloadRequest::CheckClientDownloadRequest(
download::DownloadItem* item,
CheckDownloadRepeatingCallback callback,
DownloadProtectionService* service,
scoped_refptr<SafeBrowsingDatabaseManager> database_manager,
scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor)
: CheckClientDownloadRequestBase(
item->GetURL(),
item->GetTargetFilePath(),
item->GetFullPath(),
{item->GetTabUrl(), item->GetTabReferrerUrl()},
item->GetMimeType(),
item->GetHash(),
content::DownloadItemUtils::GetBrowserContext(item),
callback,
service,
std::move(database_manager),
std::move(binary_feature_extractor)),
item_(item),
callback_(callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
item_->AddObserver(this);
DVLOG(2) << "Starting SafeBrowsing download check for: "
<< item_->DebugString(true);
}
void CheckClientDownloadRequest::OnDownloadDestroyed(
download::DownloadItem* download) {
FinishRequest(DownloadCheckResult::UNKNOWN, REASON_DOWNLOAD_DESTROYED);
}
void CheckClientDownloadRequest::OnDownloadUpdated(
download::DownloadItem* download) {
// Consider the scan bypassed if the user clicked "Open now" or "Cancel"
// before scanning is done.
if (download->GetDangerType() ==
download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING &&
(download->GetState() == download::DownloadItem::COMPLETE ||
download->GetState() == download::DownloadItem::CANCELLED)) {
RecordDeepScanMetrics(
/*access_point=*/DeepScanAccessPoint::DOWNLOAD,
/*duration=*/base::TimeTicks::Now() - upload_start_time_,
/*total_size=*/item_->GetTotalBytes(),
/*result=*/"BypassedByUser",
/*failure=*/true);
}
}
// static
bool CheckClientDownloadRequest::IsSupportedDownload(
const download::DownloadItem& item,
const base::FilePath& target_path,
DownloadCheckResultReason* reason,
ClientDownloadRequest::DownloadType* type) {
if (item.GetUrlChain().empty()) {
*reason = REASON_EMPTY_URL_CHAIN;
return false;
}
const GURL& final_url = item.GetUrlChain().back();
if (!final_url.is_valid() || final_url.is_empty()) {
*reason = REASON_INVALID_URL;
return false;
}
if (!final_url.IsStandard() && !final_url.SchemeIsBlob() &&
!final_url.SchemeIs(url::kDataScheme)) {
*reason = REASON_UNSUPPORTED_URL_SCHEME;
return false;
}
// TODO(crbug.com/814813): Remove duplicated counting of REMOTE_FILE
// and LOCAL_FILE in SBClientDownload.UnsupportedScheme.*.
if (final_url.SchemeIsFile()) {
*reason = final_url.has_host() ? REASON_REMOTE_FILE : REASON_LOCAL_FILE;
return false;
}
// This check should be last, so we know the earlier checks passed.
if (!FileTypePolicies::GetInstance()->IsCheckedBinaryFile(target_path)) {
*reason = REASON_NOT_BINARY_FILE;
return false;
}
*type = download_type_util::GetDownloadType(target_path);
return true;
}
CheckClientDownloadRequest::~CheckClientDownloadRequest() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
item_->RemoveObserver(this);
}
bool CheckClientDownloadRequest::IsSupportedDownload(
DownloadCheckResultReason* reason,
ClientDownloadRequest::DownloadType* type) {
return IsSupportedDownload(*item_, item_->GetTargetFilePath(), reason, type);
}
content::BrowserContext* CheckClientDownloadRequest::GetBrowserContext() const {
return content::DownloadItemUtils::GetBrowserContext(item_);
}
bool CheckClientDownloadRequest::IsCancelled() {
return item_->GetState() == download::DownloadItem::CANCELLED;
}
void CheckClientDownloadRequest::PopulateRequest(
ClientDownloadRequest* request) {
request->mutable_digests()->set_sha256(item_->GetHash());
request->set_length(item_->GetReceivedBytes());
for (size_t i = 0; i < item_->GetUrlChain().size(); ++i) {
ClientDownloadRequest::Resource* resource = request->add_resources();
resource->set_url(SanitizeUrl(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(SanitizeUrl(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);
}
}
request->set_user_initiated(item_->HasUserGesture());
auto* referrer_chain_data = static_cast<ReferrerChainData*>(
item_->GetUserData(ReferrerChainData::kDownloadReferrerChainDataKey));
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());
UMA_HISTOGRAM_COUNTS_100(
"SafeBrowsing.ReferrerURLChainSize.DownloadAttribution",
referrer_chain_data->referrer_chain_length());
}
}
base::WeakPtr<CheckClientDownloadRequestBase>
CheckClientDownloadRequest::GetWeakPtr() {
return weakptr_factory_.GetWeakPtr();
}
void CheckClientDownloadRequest::NotifySendRequest(
const ClientDownloadRequest* request) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
service()->client_download_request_callbacks_.Notify(item_, request);
}
void CheckClientDownloadRequest::SetDownloadPingToken(
const std::string& token) {
DCHECK(!token.empty());
DownloadProtectionService::SetDownloadPingToken(item_, token);
}
void CheckClientDownloadRequest::MaybeStorePingsForDownload(
DownloadCheckResult result,
bool upload_requested,
const std::string& request_data,
const std::string& response_body) {
DownloadFeedbackService::MaybeStorePingsForDownload(
result, upload_requested, item_, request_data, response_body);
}
base::Optional<enterprise_connectors::AnalysisSettings>
CheckClientDownloadRequest::ShouldUploadBinary(
DownloadCheckResultReason reason) {
// If the download was destroyed, we can't upload it.
if (reason == REASON_DOWNLOAD_DESTROYED)
return base::nullopt;
auto settings = DeepScanningRequest::ShouldUploadBinary(item_);
if (settings && (reason == REASON_DOWNLOAD_DANGEROUS ||
reason == REASON_DOWNLOAD_DANGEROUS_HOST ||
reason == REASON_WHITELISTED_URL)) {
settings->tags.erase("malware");
if (settings->tags.empty())
return base::nullopt;
}
return settings;
}
void CheckClientDownloadRequest::UploadBinary(
DownloadCheckResultReason reason,
enterprise_connectors::AnalysisSettings settings) {
if (reason == REASON_DOWNLOAD_DANGEROUS ||
reason == REASON_DOWNLOAD_DANGEROUS_HOST ||
reason == REASON_DOWNLOAD_POTENTIALLY_UNWANTED ||
reason == REASON_DOWNLOAD_UNCOMMON || reason == REASON_WHITELISTED_URL) {
service()->UploadForDeepScanning(
item_,
base::BindRepeating(&MaybeOverrideDlpScanResult, reason, callback_),
DeepScanningRequest::DeepScanTrigger::TRIGGER_POLICY,
std::move(settings));
} else {
service()->UploadForDeepScanning(
item_, callback_, DeepScanningRequest::DeepScanTrigger::TRIGGER_POLICY,
std::move(settings));
}
}
void CheckClientDownloadRequest::NotifyRequestFinished(
DownloadCheckResult result,
DownloadCheckResultReason reason) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
weakptr_factory_.InvalidateWeakPtrs();
DVLOG(2) << "SafeBrowsing download verdict for: " << item_->DebugString(true)
<< " verdict:" << reason << " result:" << static_cast<int>(result);
item_->RemoveObserver(this);
}
bool CheckClientDownloadRequest::ShouldPromptForDeepScanning(
DownloadCheckResultReason reason) const {
if (reason != REASON_DOWNLOAD_UNCOMMON)
return false;
#if defined(OS_CHROMEOS)
return false;
#else
Profile* profile = Profile::FromBrowserContext(GetBrowserContext());
if (!profile)
return false;
AdvancedProtectionStatusManager* advanced_protection_status_manager =
AdvancedProtectionStatusManagerFactory::GetForProfile(profile);
if (!advanced_protection_status_manager)
return false;
return base::FeatureList::IsEnabled(kPromptAppForDeepScanning) &&
advanced_protection_status_manager->IsUnderAdvancedProtection();
#endif
}
bool CheckClientDownloadRequest::IsWhitelistedByPolicy() const {
Profile* profile = Profile::FromBrowserContext(GetBrowserContext());
if (!profile)
return false;
return MatchesEnterpriseWhitelist(*profile->GetPrefs(), item_->GetUrlChain());
}
} // namespace safe_browsing