blob: 0ca6378bac1670efeec94eac20edf084a0041254 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/safe_browsing/content/browser/ui_manager.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_macros.h"
#include "base/observer_list.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/content/browser/safe_browsing_blocking_page.h"
#include "components/safe_browsing/content/browser/threat_details.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/browser/ping_manager.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "components/security_interstitials/content/unsafe_resource_util.h"
#include "components/security_interstitials/core/unsafe_resource.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_contents.h"
#include "ipc/ipc_message.h"
#include "url/gurl.h"
using content::BrowserThread;
using content::NavigationEntry;
using content::WebContents;
using safe_browsing::HitReport;
using safe_browsing::SBThreatType;
namespace safe_browsing {
SafeBrowsingUIManager::SafeBrowsingUIManager(
std::unique_ptr<Delegate> delegate,
std::unique_ptr<SafeBrowsingBlockingPageFactory> blocking_page_factory,
const GURL& default_safe_page)
: delegate_(std::move(delegate)),
blocking_page_factory_(std::move(blocking_page_factory)),
default_safe_page_(default_safe_page) {}
SafeBrowsingUIManager::~SafeBrowsingUIManager() {}
void SafeBrowsingUIManager::Stop(bool shutdown) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (shutdown) {
shut_down_ = true;
}
}
void SafeBrowsingUIManager::CreateAndSendHitReport(
const UnsafeResource& resource) {
WebContents* web_contents =
security_interstitials::GetWebContentsForResource(resource);
DCHECK(web_contents);
std::unique_ptr<HitReport> hit_report = std::make_unique<HitReport>();
hit_report->malicious_url = resource.url;
hit_report->is_subresource = resource.is_subresource;
hit_report->threat_type = resource.threat_type;
hit_report->threat_source = resource.threat_source;
hit_report->population_id = resource.threat_metadata.population_id;
NavigationEntry* entry = GetNavigationEntryForResource(resource);
if (entry) {
hit_report->page_url = entry->GetURL();
hit_report->referrer_url = entry->GetReferrer().url;
}
// When the malicious url is on the main frame, and resource.original_url
// is not the same as the resource.url, that means we have a redirect from
// resource.original_url to resource.url.
// Also, at this point, page_url points to the _previous_ page that we
// were on. We replace page_url with resource.original_url and referrer
// with page_url.
if (!resource.is_subresource && !resource.original_url.is_empty() &&
resource.original_url != resource.url) {
hit_report->referrer_url = hit_report->page_url;
hit_report->page_url = resource.original_url;
}
const auto& prefs = *delegate_->GetPrefs(web_contents->GetBrowserContext());
hit_report->extended_reporting_level = GetExtendedReportingLevel(prefs);
hit_report->is_enhanced_protection = IsEnhancedProtectionEnabled(prefs);
hit_report->is_metrics_reporting_active =
delegate_->IsMetricsAndCrashReportingEnabled();
MaybeReportSafeBrowsingHit(std::move(hit_report), web_contents);
for (Observer& observer : observer_list_)
observer.OnSafeBrowsingHit(resource);
}
void SafeBrowsingUIManager::StartDisplayingBlockingPage(
const security_interstitials::UnsafeResource& resource) {
content::WebContents* web_contents =
security_interstitials::GetWebContentsForResource(resource);
if (!web_contents) {
// Tab is gone.
resource.DispatchCallback(FROM_HERE, false /*proceed*/,
false /*showed_interstitial*/);
return;
}
prerender::NoStatePrefetchContents* no_state_prefetch_contents =
delegate_->GetNoStatePrefetchContentsIfExists(web_contents);
if (no_state_prefetch_contents) {
no_state_prefetch_contents->Destroy(prerender::FINAL_STATUS_SAFE_BROWSING);
// Tab is being prerendered.
resource.DispatchCallback(FROM_HERE, false /*proceed*/,
false /*showed_interstitial*/);
return;
}
// Whether we have a FrameTreeNode id or a RenderFrameHost id depends on
// whether SB was triggered for a frame navigation or a document's subresource
// load respectively. We consider both cases here.
const content::GlobalRenderFrameHostId rfh_id(resource.render_process_id,
resource.render_frame_id);
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(rfh_id);
const bool is_prerender =
web_contents->IsPrerenderedFrame(resource.frame_tree_node_id) ||
(rfh && rfh->GetLifecycleState() ==
content::RenderFrameHost::LifecycleState::kPrerendering);
if (is_prerender) {
// TODO(mcnee): If we were to indicate that this does not show an
// interstitial, the loader throttle would cancel with ERR_ABORTED to
// suppress an error page, instead of blocking using ERR_BLOCKED_BY_CLIENT.
// Prerendering code needs to distiguish these cases, so we pretend that
// we've shown an interstitial to get a meaningful error code.
// Given that the only thing the |showed_interstitial| parameter is used for
// is controlling the error code, perhaps this should be renamed to better
// indicate its purpose.
resource.DispatchCallback(FROM_HERE, false /*proceed*/,
true /*showed_interstitial*/);
return;
}
// We don't show interstitials for extension triggered SB errors, since they
// might not be visible, and cause the extension to hang. The request is just
// cancelled instead.
if (delegate_->IsHostingExtension(web_contents)) {
resource.DispatchCallback(FROM_HERE, false /* proceed */,
false /* showed_interstitial */);
return;
}
// With committed interstitials, if this is a main frame load, we need to
// get the navigation URL and referrer URL from the navigation entry now,
// since they are required for threat reporting, and the entry will be
// destroyed once the request is failed.
if (resource.IsMainPageLoadBlocked()) {
content::NavigationEntry* entry =
security_interstitials::GetNavigationEntryForResource(resource);
if (entry) {
security_interstitials::UnsafeResource resource_copy(resource);
resource_copy.navigation_url = entry->GetURL();
resource_copy.referrer_url = entry->GetReferrer().url;
DisplayBlockingPage(resource_copy);
return;
}
}
DisplayBlockingPage(resource);
}
void SafeBrowsingUIManager::CheckLookupMechanismExperimentEligibility(
security_interstitials::UnsafeResource resource,
base::OnceCallback<void(bool)> callback,
scoped_refptr<base::SequencedTaskRunner> callback_task_runner) {
content::WebContents* web_contents =
security_interstitials::GetWebContentsForResource(resource);
auto determine_if_is_prerender = [resource, web_contents]() {
const content::GlobalRenderFrameHostId rfh_id(resource.render_process_id,
resource.render_frame_id);
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(rfh_id);
return web_contents->IsPrerenderedFrame(resource.frame_tree_node_id) ||
(rfh && rfh->GetLifecycleState() ==
content::RenderFrameHost::LifecycleState::kPrerendering);
};
// These checks parallel the ones performed by StartDisplayingBlockingPage to
// determine if a blocking page would be shown for a mainframe URL. The
// experiment is only eligible if the blocking page would be shown.
bool is_ineligible =
!web_contents ||
delegate_->GetNoStatePrefetchContentsIfExists(web_contents) ||
determine_if_is_prerender() ||
delegate_->IsHostingExtension(web_contents) || IsAllowlisted(resource);
callback_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), !is_ineligible));
}
void SafeBrowsingUIManager::CheckExperimentEligibilityAndStartBlockingPage(
security_interstitials::UnsafeResource resource,
base::OnceCallback<void(bool)> callback,
scoped_refptr<base::SequencedTaskRunner> callback_task_runner) {
CheckLookupMechanismExperimentEligibility(resource, std::move(callback),
callback_task_runner);
StartDisplayingBlockingPage(resource);
}
bool SafeBrowsingUIManager::ShouldSendHitReport(HitReport* hit_report,
WebContents* web_contents) {
return web_contents &&
hit_report->extended_reporting_level != SBER_LEVEL_OFF &&
!web_contents->GetBrowserContext()->IsOffTheRecord() &&
delegate_->IsSendingOfHitReportsEnabled();
}
// A SafeBrowsing hit is sent after a blocking page for malware/phishing
// or after the warning dialog for download urls, only for
// extended-reporting users.
void SafeBrowsingUIManager::MaybeReportSafeBrowsingHit(
std::unique_ptr<HitReport> hit_report,
WebContents* web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Send report if user opted-in to extended reporting and is not in
// incognito mode.
if (!ShouldSendHitReport(hit_report.get(), web_contents)) {
return;
}
if (shut_down_)
return;
DVLOG(1) << "ReportSafeBrowsingHit: " << hit_report->malicious_url << " "
<< hit_report->page_url << " " << hit_report->referrer_url << " "
<< hit_report->is_subresource << " " << hit_report->threat_type;
delegate_->GetPingManager(web_contents->GetBrowserContext())
->ReportSafeBrowsingHit(std::move(hit_report));
}
// Static.
void SafeBrowsingUIManager::CreateAllowlistForTesting(
content::WebContents* web_contents) {
EnsureAllowlistCreated(web_contents);
}
// static
std::string SafeBrowsingUIManager::GetThreatTypeStringForInterstitial(
safe_browsing::SBThreatType threat_type) {
switch (threat_type) {
case safe_browsing::SB_THREAT_TYPE_URL_PHISHING:
case safe_browsing::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING:
return "SOCIAL_ENGINEERING";
case safe_browsing::SB_THREAT_TYPE_URL_MALWARE:
case safe_browsing::SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE:
return "MALWARE";
case safe_browsing::SB_THREAT_TYPE_URL_UNWANTED:
return "UNWANTED_SOFTWARE";
case safe_browsing::SB_THREAT_TYPE_BILLING:
return "THREAT_TYPE_UNSPECIFIED";
case safe_browsing::SB_THREAT_TYPE_MANAGED_POLICY_WARN:
return "MANAGED_POLICY_WARN";
case safe_browsing::SB_THREAT_TYPE_MANAGED_POLICY_BLOCK:
return "MANAGED_POLICY_BLOCK";
case safe_browsing::SB_THREAT_TYPE_UNUSED:
case safe_browsing::SB_THREAT_TYPE_SAFE:
case safe_browsing::SB_THREAT_TYPE_URL_BINARY_MALWARE:
case safe_browsing::SB_THREAT_TYPE_EXTENSION:
case safe_browsing::SB_THREAT_TYPE_BLOCKLISTED_RESOURCE:
case safe_browsing::SB_THREAT_TYPE_API_ABUSE:
case safe_browsing::SB_THREAT_TYPE_SUBRESOURCE_FILTER:
case safe_browsing::SB_THREAT_TYPE_CSD_ALLOWLIST:
case safe_browsing::
DEPRECATED_SB_THREAT_TYPE_URL_PASSWORD_PROTECTION_PHISHING:
case safe_browsing::SB_THREAT_TYPE_SAVED_PASSWORD_REUSE:
case safe_browsing::SB_THREAT_TYPE_SIGNED_IN_SYNC_PASSWORD_REUSE:
case safe_browsing::SB_THREAT_TYPE_SIGNED_IN_NON_SYNC_PASSWORD_REUSE:
case safe_browsing::SB_THREAT_TYPE_AD_SAMPLE:
case safe_browsing::SB_THREAT_TYPE_BLOCKED_AD_POPUP:
case safe_browsing::SB_THREAT_TYPE_BLOCKED_AD_REDIRECT:
case safe_browsing::SB_THREAT_TYPE_SUSPICIOUS_SITE:
case safe_browsing::SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE:
case safe_browsing::SB_THREAT_TYPE_APK_DOWNLOAD:
case safe_browsing::SB_THREAT_TYPE_HIGH_CONFIDENCE_ALLOWLIST:
NOTREACHED();
break;
}
return std::string();
}
void SafeBrowsingUIManager::AddObserver(Observer* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
observer_list_.AddObserver(observer);
}
void SafeBrowsingUIManager::RemoveObserver(Observer* observer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
observer_list_.RemoveObserver(observer);
}
const std::string SafeBrowsingUIManager::app_locale() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return delegate_->GetApplicationLocale();
}
history::HistoryService* SafeBrowsingUIManager::history_service(
content::WebContents* web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return delegate_->GetHistoryService(web_contents->GetBrowserContext());
}
const GURL SafeBrowsingUIManager::default_safe_page() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return default_safe_page_;
}
// If the user had opted-in to send ThreatDetails, this gets called
// when the report is ready.
void SafeBrowsingUIManager::SendThreatDetails(
content::BrowserContext* browser_context,
std::unique_ptr<ClientSafeBrowsingReportRequest> report) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (shut_down_)
return;
DVLOG(1) << "Sending threat details.";
delegate_->GetPingManager(browser_context)
->ReportThreatDetails(std::move(report));
}
void SafeBrowsingUIManager::OnBlockingPageDone(
const std::vector<UnsafeResource>& resources,
bool proceed,
content::WebContents* web_contents,
const GURL& main_frame_url,
bool showed_interstitial) {
BaseUIManager::OnBlockingPageDone(resources, proceed, web_contents,
main_frame_url, showed_interstitial);
if (proceed && !resources.empty()) {
#if !BUILDFLAG(IS_ANDROID)
if (base::FeatureList::IsEnabled((kRealTimeUrlFilteringForEnterprise)) &&
resources[0].threat_type ==
safe_browsing::SB_THREAT_TYPE_MANAGED_POLICY_WARN) {
delegate_->TriggerUrlFilteringInterstitialExtensionEventIfDesired(
web_contents, main_frame_url, "ENTERPRISE_WARNED_BYPASS",
resources[0].rt_lookup_response);
return;
}
#endif
delegate_->TriggerSecurityInterstitialProceededExtensionEventIfDesired(
web_contents, main_frame_url,
GetThreatTypeStringForInterstitial(resources[0].threat_type),
/*net_error_code=*/0);
}
}
// Static.
GURL SafeBrowsingUIManager::GetMainFrameAllowlistUrlForResourceForTesting(
const security_interstitials::UnsafeResource& resource) {
return GetMainFrameAllowlistUrlForResource(resource);
}
BaseBlockingPage* SafeBrowsingUIManager::CreateBlockingPageForSubresource(
content::WebContents* contents,
const GURL& blocked_url,
const UnsafeResource& unsafe_resource) {
SafeBrowsingBlockingPage* blocking_page =
blocking_page_factory_->CreateSafeBrowsingPage(
this, contents, blocked_url, {unsafe_resource},
/*should_trigger_reporting=*/true);
// Report that we showed an interstitial.
ForwardSecurityInterstitialShownExtensionEventToEmbedder(
contents, blocked_url,
GetThreatTypeStringForInterstitial(unsafe_resource.threat_type),
/*net_error_code=*/0);
return blocking_page;
}
void SafeBrowsingUIManager::
ForwardSecurityInterstitialShownExtensionEventToEmbedder(
content::WebContents* web_contents,
const GURL& page_url,
const std::string& reason,
int net_error_code) {
delegate_->TriggerSecurityInterstitialShownExtensionEventIfDesired(
web_contents, page_url, reason, net_error_code);
}
#if !BUILDFLAG(IS_ANDROID)
void SafeBrowsingUIManager::
ForwardUrlFilteringInterstitialExtensionEventToEmbedder(
content::WebContents* web_contents,
const GURL& page_url,
const std::string& threat_type,
safe_browsing::RTLookupResponse rt_lookup_response) {
delegate_->TriggerUrlFilteringInterstitialExtensionEventIfDesired(
web_contents, page_url, threat_type, rt_lookup_response);
}
#endif
} // namespace safe_browsing