diff options
Diffstat (limited to 'chromium/components/safe_browsing')
75 files changed, 2198 insertions, 912 deletions
diff --git a/chromium/components/safe_browsing/BUILD.gn b/chromium/components/safe_browsing/BUILD.gn index 89ed2c0243c..cc7fd1ddae6 100644 --- a/chromium/components/safe_browsing/BUILD.gn +++ b/chromium/components/safe_browsing/BUILD.gn @@ -11,17 +11,14 @@ buildflag_header("buildflags") { flags = [] if (safe_browsing_mode == 0) { flags += [ "FULL_SAFE_BROWSING=0" ] - flags += [ "SAFE_BROWSING_CSD=0" ] flags += [ "SAFE_BROWSING_DB_LOCAL=0" ] flags += [ "SAFE_BROWSING_DB_REMOTE=0" ] } else if (safe_browsing_mode == 1) { flags += [ "FULL_SAFE_BROWSING=1" ] - flags += [ "SAFE_BROWSING_CSD=1" ] flags += [ "SAFE_BROWSING_DB_LOCAL=1" ] flags += [ "SAFE_BROWSING_DB_REMOTE=0" ] } else if (safe_browsing_mode == 2) { flags += [ "FULL_SAFE_BROWSING=0" ] - flags += [ "SAFE_BROWSING_CSD=0" ] flags += [ "SAFE_BROWSING_DB_LOCAL=0" ] flags += [ "SAFE_BROWSING_DB_REMOTE=1" ] } diff --git a/chromium/components/safe_browsing/DEPS b/chromium/components/safe_browsing/DEPS index ace05e79c17..9f374d3a591 100644 --- a/chromium/components/safe_browsing/DEPS +++ b/chromium/components/safe_browsing/DEPS @@ -12,6 +12,7 @@ include_rules = [ "+components/sync_preferences/testing_pref_service_syncable.h", "+components/unified_consent", "+components/user_prefs/user_prefs.h", + "+components/variations", "+content/public/browser", "+content/public/common", "+content/public/test", diff --git a/chromium/components/safe_browsing/android/safe_browsing_api_handler_bridge.cc b/chromium/components/safe_browsing/android/safe_browsing_api_handler_bridge.cc index 58f43251d40..41a76b2f200 100644 --- a/chromium/components/safe_browsing/android/safe_browsing_api_handler_bridge.cc +++ b/chromium/components/safe_browsing/android/safe_browsing_api_handler_bridge.cc @@ -14,7 +14,6 @@ #include "base/containers/flat_set.h" #include "base/feature_list.h" #include "base/metrics/histogram_macros.h" -#include "base/task/post_task.h" #include "base/trace_event/trace_event.h" #include "components/safe_browsing/android/jni_headers/SafeBrowsingApiBridge_jni.h" #include "components/safe_browsing/android/safe_browsing_api_handler_util.h" @@ -41,8 +40,8 @@ void RunCallbackOnIOThread( const ThreatMetadata& metadata) { CHECK(callback); // Remove after fixing crbug.com/889972 CHECK(!callback->is_null()); // Remove after fixing crbug.com/889972 - base::PostTask(FROM_HERE, {BrowserThread::IO}, - base::BindOnce(std::move(*callback), threat_type, metadata)); + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(std::move(*callback), threat_type, metadata)); } void ReportUmaResult(safe_browsing::UmaRemoteCallResult result) { @@ -188,8 +187,8 @@ void JNI_SafeBrowsingApiBridge_OnUrlCheckDone( TRACE_EVENT1("safe_browsing", "SafeBrowsingApiHandlerBridge::OnUrlCheckDone", "metadata", metadata_str); - base::PostTask(FROM_HERE, {BrowserThread::IO}, - base::BindOnce(&OnUrlCheckDoneOnIOThread, callback_id, + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&OnUrlCheckDoneOnIOThread, callback_id, result_status, metadata_str)); } diff --git a/chromium/components/safe_browsing/content/base_blocking_page.cc b/chromium/components/safe_browsing/content/base_blocking_page.cc index 513ed5b08f7..bc7d4dc3c7d 100644 --- a/chromium/components/safe_browsing/content/base_blocking_page.cc +++ b/chromium/components/safe_browsing/content/base_blocking_page.cc @@ -138,10 +138,6 @@ void BaseBlockingPage::PopulateInterstitialStrings( sb_error_ui_->PopulateStringsForHtml(load_time_data); } -void BaseBlockingPage::OnInterstitialClosing() { - UpdateMetricsAfterSecurityInterstitial(); -} - void BaseBlockingPage::FinishThreatDetails(const base::TimeDelta& delay, bool did_proceed, int num_visits) {} diff --git a/chromium/components/safe_browsing/content/base_blocking_page.h b/chromium/components/safe_browsing/content/base_blocking_page.h index 1e35a434363..97bfb779040 100644 --- a/chromium/components/safe_browsing/content/base_blocking_page.h +++ b/chromium/components/safe_browsing/content/base_blocking_page.h @@ -61,7 +61,7 @@ class BaseBlockingPage bool ShouldCreateNewNavigation() const override; void PopulateInterstitialStrings( base::DictionaryValue* load_time_data) override; - void OnInterstitialClosing() override; + void OnInterstitialClosing() override {} // Called when the interstitial is going away. Intentionally do nothing in // this base class. diff --git a/chromium/components/safe_browsing/content/base_ui_manager.cc b/chromium/components/safe_browsing/content/base_ui_manager.cc index 6643359fd2e..680b1098f46 100644 --- a/chromium/components/safe_browsing/content/base_ui_manager.cc +++ b/chromium/components/safe_browsing/content/base_ui_manager.cc @@ -190,6 +190,21 @@ void BaseUIManager::OnBlockingPageDone( } } +namespace { +// In the case of nested WebContents, returns the WebContents where it is +// suitable to show an interstitial. +content::WebContents* GetEmbeddingWebContentsForInterstitial( + content::WebContents* source_contents) { + content::WebContents* top_level_contents = source_contents; + // Note that |WebContents::GetResponsibleWebContents| is not suitable here + // since we want to stay within any GuestViews. + while (top_level_contents->IsPortal()) { + top_level_contents = top_level_contents->GetPortalHostWebContents(); + } + return top_level_contents; +} +} // namespace + void BaseUIManager::DisplayBlockingPage( const UnsafeResource& resource) { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -245,13 +260,26 @@ void BaseUIManager::DisplayBlockingPage( } AddToWhitelistUrlSet(GetMainFrameWhitelistUrlForResource(resource), - resource.web_contents_getter.Run(), - true /* A decision is now pending */, + web_contents, true /* A decision is now pending */, resource.threat_type); - GURL unsafe_url = (resource.IsMainPageLoadBlocked() || - !GetNavigationEntryForResource(resource)) - ? resource.url - : GetNavigationEntryForResource(resource)->GetURL(); + + // |entry| can be null if we are on a brand new tab, and a resource is added + // via javascript without a navigation. + content::NavigationEntry* entry = GetNavigationEntryForResource(resource); + + // If unsafe content is loaded in a portal, we treat its embedder as + // dangerous. + content::WebContents* outermost_contents = + GetEmbeddingWebContentsForInterstitial(web_contents); + + GURL unsafe_url = resource.url; + if (outermost_contents != web_contents) { + DCHECK(outermost_contents->GetController().GetLastCommittedEntry()); + unsafe_url = + outermost_contents->GetController().GetLastCommittedEntry()->GetURL(); + } else if (entry && !resource.IsMainPageLoadBlocked()) { + unsafe_url = entry->GetURL(); + } AddUnsafeResource(unsafe_url, resource); // If the delayed warnings experiment is not enabled, with committed // interstitials we just cancel the load from here, the actual interstitial @@ -273,28 +301,24 @@ void BaseUIManager::DisplayBlockingPage( DCHECK(!resource.is_delayed_warning); } - if ((!resource.IsMainPageLoadBlocked() || resource.is_delayed_warning) && - !IsWhitelisted(resource)) { + if (!resource.IsMainPageLoadBlocked() || resource.is_delayed_warning || + outermost_contents != web_contents) { + DCHECK(!IsWhitelisted(resource)); // For subresource triggered interstitials, we trigger the error page // navigation from here since there will be no navigation to intercept // in the throttle. - content::WebContents* contents = resource.web_contents_getter.Run(); - content::NavigationEntry* entry = GetNavigationEntryForResource(resource); - // entry can be null if we are on a brand new tab, and a resource is added - // via javascript without a navigation. - GURL blocked_url = entry ? entry->GetURL() : resource.url; - + // // Blocking pages handle both user interaction, and generation of the // interstitial HTML. In the case of subresources, we need the HTML // content prior to (and in a different process than when) installing the // command handlers. For this reason we create a blocking page here just // to generate the HTML, and immediately delete it. - BaseBlockingPage* blocking_page = - CreateBlockingPageForSubresource(contents, blocked_url, resource); - contents->GetController().LoadPostCommitErrorPage( - contents->GetMainFrame(), blocked_url, blocking_page->GetHTMLContents(), - net::ERR_BLOCKED_BY_CLIENT); - delete blocking_page; + std::unique_ptr<BaseBlockingPage> blocking_page = + base::WrapUnique(CreateBlockingPageForSubresource( + outermost_contents, unsafe_url, resource)); + outermost_contents->GetController().LoadPostCommitErrorPage( + outermost_contents->GetMainFrame(), unsafe_url, + blocking_page->GetHTMLContents(), net::ERR_BLOCKED_BY_CLIENT); } } diff --git a/chromium/components/safe_browsing/content/browser/BUILD.gn b/chromium/components/safe_browsing/content/browser/BUILD.gn index 6111969ed2b..0b942f26ce3 100644 --- a/chromium/components/safe_browsing/content/browser/BUILD.gn +++ b/chromium/components/safe_browsing/content/browser/BUILD.gn @@ -33,7 +33,7 @@ jumbo_source_set("browser") { "//components/safe_browsing/core/common:common", "//components/safe_browsing/core/db:database_manager", "//components/safe_browsing/core/realtime:policy_engine", - "//components/safe_browsing/core/realtime:url_lookup_service", + "//components/safe_browsing/core/realtime:url_lookup_service_base", "//components/safe_browsing/core/web_ui:constants", "//components/security_interstitials/content:security_interstitial_page", "//components/security_interstitials/core:unsafe_resource", diff --git a/chromium/components/safe_browsing/content/browser/browser_url_loader_throttle.cc b/chromium/components/safe_browsing/content/browser/browser_url_loader_throttle.cc index cb3aa4a82be..2fa3d9cc4c9 100644 --- a/chromium/components/safe_browsing/content/browser/browser_url_loader_throttle.cc +++ b/chromium/components/safe_browsing/content/browser/browser_url_loader_throttle.cc @@ -13,7 +13,7 @@ #include "components/safe_browsing/core/common/safebrowsing_constants.h" #include "components/safe_browsing/core/common/utils.h" #include "components/safe_browsing/core/realtime/policy_engine.h" -#include "components/safe_browsing/core/realtime/url_lookup_service.h" +#include "components/safe_browsing/core/realtime/url_lookup_service_base.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/web_contents.h" #include "net/log/net_log_event_type.h" @@ -34,14 +34,16 @@ class BrowserURLLoaderThrottle::CheckerOnIO base::RepeatingCallback<content::WebContents*()> web_contents_getter, base::WeakPtr<BrowserURLLoaderThrottle> throttle, bool real_time_lookup_enabled, - bool enhanced_protection_enabled, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service) + bool can_rt_check_subresource_url, + bool can_check_db, + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service) : delegate_getter_(std::move(delegate_getter)), frame_tree_node_id_(frame_tree_node_id), web_contents_getter_(web_contents_getter), throttle_(std::move(throttle)), real_time_lookup_enabled_(real_time_lookup_enabled), - enhanced_protection_enabled_(enhanced_protection_enabled), + can_rt_check_subresource_url_(can_rt_check_subresource_url), + can_check_db_(can_check_db), url_lookup_service_(url_lookup_service) {} // Starts the initial safe browsing check. This check and future checks may be @@ -63,8 +65,8 @@ class BrowserURLLoaderThrottle::CheckerOnIO url, frame_tree_node_id_, -1 /* render_process_id */, -1 /* render_frame_id */, originated_from_service_worker); if (skip_checks_) { - base::PostTask( - FROM_HERE, {content::BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&BrowserURLLoaderThrottle::SkipChecks, throttle_)); return; } @@ -72,7 +74,7 @@ class BrowserURLLoaderThrottle::CheckerOnIO url_checker_ = std::make_unique<SafeBrowsingUrlCheckerImpl>( headers, load_flags, resource_type, has_user_gesture, url_checker_delegate, web_contents_getter_, real_time_lookup_enabled_, - enhanced_protection_enabled_, url_lookup_service_); + can_rt_check_subresource_url_, can_check_db_, url_lookup_service_); CheckUrl(url, method); } @@ -81,8 +83,8 @@ class BrowserURLLoaderThrottle::CheckerOnIO void CheckUrl(const GURL& url, const std::string& method) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (skip_checks_) { - base::PostTask( - FROM_HERE, {content::BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&BrowserURLLoaderThrottle::SkipChecks, throttle_)); return; } @@ -108,8 +110,8 @@ class BrowserURLLoaderThrottle::CheckerOnIO return; } - base::PostTask( - FROM_HERE, {content::BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&BrowserURLLoaderThrottle::NotifySlowCheck, throttle_)); // In this case |proceed| and |showed_interstitial| should be ignored. The @@ -124,8 +126,8 @@ class BrowserURLLoaderThrottle::CheckerOnIO void OnCompleteCheck(bool slow_check, bool proceed, bool showed_interstitial) { - base::PostTask( - FROM_HERE, {content::BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&BrowserURLLoaderThrottle::OnCompleteCheck, throttle_, slow_check, proceed, showed_interstitial)); } @@ -139,8 +141,9 @@ class BrowserURLLoaderThrottle::CheckerOnIO bool skip_checks_ = false; base::WeakPtr<BrowserURLLoaderThrottle> throttle_; bool real_time_lookup_enabled_ = false; - bool enhanced_protection_enabled_ = false; - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service_; + bool can_rt_check_subresource_url_ = false; + bool can_check_db_ = true; + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service_; }; // static @@ -148,7 +151,7 @@ std::unique_ptr<BrowserURLLoaderThrottle> BrowserURLLoaderThrottle::Create( GetDelegateCallback delegate_getter, const base::RepeatingCallback<content::WebContents*()>& web_contents_getter, int frame_tree_node_id, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service) { + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service) { return base::WrapUnique<BrowserURLLoaderThrottle>( new BrowserURLLoaderThrottle(std::move(delegate_getter), web_contents_getter, frame_tree_node_id, @@ -159,7 +162,7 @@ BrowserURLLoaderThrottle::BrowserURLLoaderThrottle( GetDelegateCallback delegate_getter, const base::RepeatingCallback<content::WebContents*()>& web_contents_getter, int frame_tree_node_id, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service) { + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Decide whether to do real time URL lookups or not. @@ -167,13 +170,18 @@ BrowserURLLoaderThrottle::BrowserURLLoaderThrottle( url_lookup_service ? url_lookup_service->CanPerformFullURLLookup() : false; - bool enhanced_protection_enabled = - url_lookup_service && url_lookup_service->IsUserEpOptedIn(); + bool can_rt_check_subresource_url = + url_lookup_service && url_lookup_service->CanCheckSubresourceURL(); + // Decide whether safe browsing database can be checked. + // If url_lookup_service is null, safe browsing database should be checked by + // default. + bool can_check_db = + url_lookup_service ? url_lookup_service->CanCheckSafeBrowsingDb() : true; io_checker_ = std::make_unique<CheckerOnIO>( std::move(delegate_getter), frame_tree_node_id, web_contents_getter, weak_factory_.GetWeakPtr(), real_time_lookup_enabled, - enhanced_protection_enabled, url_lookup_service); + can_rt_check_subresource_url, can_check_db, url_lookup_service); } BrowserURLLoaderThrottle::~BrowserURLLoaderThrottle() { @@ -193,8 +201,8 @@ void BrowserURLLoaderThrottle::WillStartRequest( original_url_ = request->url; pending_checks_++; - base::PostTask( - FROM_HERE, {content::BrowserThread::IO}, + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce( &BrowserURLLoaderThrottle::CheckerOnIO::Start, io_checker_->AsWeakPtr(), request->headers, request->load_flags, @@ -223,8 +231,8 @@ void BrowserURLLoaderThrottle::WillRedirectRequest( return; pending_checks_++; - base::PostTask( - FROM_HERE, {content::BrowserThread::IO}, + content::GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&BrowserURLLoaderThrottle::CheckerOnIO::CheckUrl, io_checker_->AsWeakPtr(), redirect_info->new_url, redirect_info->new_method)); @@ -324,8 +332,8 @@ void BrowserURLLoaderThrottle::NotifySlowCheck() { } void BrowserURLLoaderThrottle::DeleteCheckerOnIO() { - base::DeleteSoon(FROM_HERE, {content::BrowserThread::IO}, - std::move(io_checker_)); + content::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE, + std::move(io_checker_)); } } // namespace safe_browsing diff --git a/chromium/components/safe_browsing/content/browser/browser_url_loader_throttle.h b/chromium/components/safe_browsing/content/browser/browser_url_loader_throttle.h index d31b38a3dbc..fbd0536a4c7 100644 --- a/chromium/components/safe_browsing/content/browser/browser_url_loader_throttle.h +++ b/chromium/components/safe_browsing/content/browser/browser_url_loader_throttle.h @@ -28,7 +28,7 @@ namespace safe_browsing { class UrlCheckerDelegate; -class RealTimeUrlLookupService; +class RealTimeUrlLookupServiceBase; // BrowserURLLoaderThrottle is used in the browser process to query // SafeBrowsing to determine whether a URL and also its redirect URLs are safe @@ -49,7 +49,7 @@ class BrowserURLLoaderThrottle : public blink::URLLoaderThrottle { const base::RepeatingCallback<content::WebContents*()>& web_contents_getter, int frame_tree_node_id, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service); + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service); ~BrowserURLLoaderThrottle() override; @@ -86,7 +86,7 @@ class BrowserURLLoaderThrottle : public blink::URLLoaderThrottle { const base::RepeatingCallback<content::WebContents*()>& web_contents_getter, int frame_tree_node_id, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service); + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service); // |slow_check| indicates whether it reports the result of a slow check. // (Please see comments of CheckerOnIO::OnCheckUrlResult() for what slow check diff --git a/chromium/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc b/chromium/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc index 43e5f816d0b..b9dc2d53324 100644 --- a/chromium/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc +++ b/chromium/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc @@ -156,7 +156,9 @@ void MojoSafeBrowsingImpl::CreateCheckerAndCheck( delegate_, base::BindRepeating(&GetWebContentsFromID, render_process_id_, static_cast<int>(render_frame_id)), - /*real_time_lookup_enabled=*/false, /*enhanced_protection_enabled=*/false, + /*real_time_lookup_enabled=*/false, + /*can_rt_check_subresource_url=*/false, + /*can_check_db=*/true, /*url_lookup_service=*/nullptr); checker_impl->CheckUrl( diff --git a/chromium/components/safe_browsing/content/browser/safe_browsing_url_checker_impl_content.cc b/chromium/components/safe_browsing/content/browser/safe_browsing_url_checker_impl_content.cc index 4ac3714571e..b22dc6eed7d 100644 --- a/chromium/components/safe_browsing/content/browser/safe_browsing_url_checker_impl_content.cc +++ b/chromium/components/safe_browsing/content/browser/safe_browsing_url_checker_impl_content.cc @@ -13,7 +13,7 @@ #include "components/safe_browsing/core/common/safebrowsing_constants.h" #include "components/safe_browsing/core/common/thread_utils.h" #include "components/safe_browsing/core/realtime/policy_engine.h" -#include "components/safe_browsing/core/realtime/url_lookup_service.h" +#include "components/safe_browsing/core/realtime/url_lookup_service_base.h" #include "components/safe_browsing/core/web_ui/constants.h" namespace safe_browsing { @@ -21,8 +21,8 @@ namespace safe_browsing { bool SafeBrowsingUrlCheckerImpl::CanPerformFullURLLookup(const GURL& url) { return real_time_lookup_enabled_ && RealTimePolicyEngine::CanPerformFullURLLookupForResourceType( - resource_type_, enhanced_protection_enabled_) && - RealTimeUrlLookupService::CanCheckUrl(url); + resource_type_, can_rt_check_subresource_url_) && + RealTimeUrlLookupServiceBase::CanCheckUrl(url); } void SafeBrowsingUrlCheckerImpl::OnRTLookupRequest( @@ -48,7 +48,7 @@ void SafeBrowsingUrlCheckerImpl::OnRTLookupResponse( bool is_expected_resource_type = (ResourceType::kMainFrame == resource_type_) || ((ResourceType::kSubFrame == resource_type_) && - enhanced_protection_enabled_); + can_rt_check_subresource_url_); DCHECK(is_expected_resource_type); const GURL& url = urls_[next_index_].url; @@ -75,8 +75,9 @@ void SafeBrowsingUrlCheckerImpl::OnRTLookupResponse( // TODO(crbug.com/1033692): Only take the first threat info into account // because threat infos are returned in decreasing order of severity. // Consider extend it to support multiple threat types. - sb_threat_type = RealTimeUrlLookupService::GetSBThreatTypeForRTThreatType( - response->threat_info(0).threat_type()); + sb_threat_type = + RealTimeUrlLookupServiceBase::GetSBThreatTypeForRTThreatType( + response->threat_info(0).threat_type()); } OnUrlResult(url, sb_threat_type, ThreatMetadata()); } diff --git a/chromium/components/safe_browsing/content/browser/threat_details.cc b/chromium/components/safe_browsing/content/browser/threat_details.cc index a71ac3092c4..c63087e8c8f 100644 --- a/chromium/components/safe_browsing/content/browser/threat_details.cc +++ b/chromium/components/safe_browsing/content/browser/threat_details.cc @@ -20,7 +20,6 @@ #include "base/stl_util.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" -#include "base/task/post_task.h" #include "components/history/core/browser/history_service.h" #include "components/safe_browsing/content/base_ui_manager.h" #include "components/safe_browsing/content/browser/threat_details_cache.h" @@ -849,8 +848,8 @@ void ThreatDetails::OnCacheCollectionReady() { return; } - base::PostTask( - FROM_HERE, {content::BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&WebUIInfoSingleton::AddToCSBRRsSent, base::Unretained(WebUIInfoSingleton::GetInstance()), std::move(report_))); @@ -877,8 +876,8 @@ void ThreatDetails::MaybeFillReferrerChain() { void ThreatDetails::AllDone() { is_all_done_ = true; - base::PostTask(FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(std::move(done_callback_), + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(std::move(done_callback_), base::Unretained(web_contents()))); } diff --git a/chromium/components/safe_browsing/content/browser/threat_details_cache.cc b/chromium/components/safe_browsing/content/browser/threat_details_cache.cc index d0ea614c3bd..70c5ee9278c 100644 --- a/chromium/components/safe_browsing/content/browser/threat_details_cache.cc +++ b/chromium/components/safe_browsing/content/browser/threat_details_cache.cc @@ -12,7 +12,6 @@ #include "base/hash/md5.h" #include "base/lazy_instance.h" #include "base/strings/string_util.h" -#include "base/task/post_task.h" #include "components/safe_browsing/content/browser/threat_details.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -52,8 +51,8 @@ void ThreatDetailsCacheCollector::StartCacheCollection( // Post a task in the message loop, so the callers don't need to // check if we call their callback immediately. - base::PostTask(FROM_HERE, {BrowserThread::UI}, - base::BindOnce(&ThreatDetailsCacheCollector::OpenEntry, this)); + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&ThreatDetailsCacheCollector::OpenEntry, this)); } bool ThreatDetailsCacheCollector::HasStarted() { @@ -227,15 +226,15 @@ void ThreatDetailsCacheCollector::AdvanceEntry() { current_load_.reset(); // Create a task so we don't take over the UI thread for too long. - base::PostTask(FROM_HERE, {BrowserThread::UI}, - base::BindOnce(&ThreatDetailsCacheCollector::OpenEntry, this)); + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&ThreatDetailsCacheCollector::OpenEntry, this)); } void ThreatDetailsCacheCollector::AllDone(bool success) { DVLOG(1) << "AllDone"; DCHECK_CURRENTLY_ON(BrowserThread::UI); *result_ = success; - base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(callback_)); + content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback_)); } } // namespace safe_browsing diff --git a/chromium/components/safe_browsing/content/browser/threat_details_history.cc b/chromium/components/safe_browsing/content/browser/threat_details_history.cc index d434e97c4ac..c9976122b8c 100644 --- a/chromium/components/safe_browsing/content/browser/threat_details_history.cc +++ b/chromium/components/safe_browsing/content/browser/threat_details_history.cc @@ -10,7 +10,6 @@ #include "base/bind.h" #include "base/bind_helpers.h" -#include "base/task/post_task.h" #include "components/safe_browsing/content/browser/threat_details.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -41,8 +40,8 @@ void ThreatDetailsRedirectsCollector::StartHistoryCollection( return; } - base::PostTask( - FROM_HERE, {BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&ThreatDetailsRedirectsCollector::StartGetRedirects, this, urls)); } @@ -107,7 +106,7 @@ void ThreatDetailsRedirectsCollector::OnGotQueryRedirectsTo( void ThreatDetailsRedirectsCollector::AllDone() { DVLOG(1) << "AllDone"; - base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(callback_)); + content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback_)); } void ThreatDetailsRedirectsCollector::HistoryServiceBeingDeleted( diff --git a/chromium/components/safe_browsing/content/common/safe_browsing.mojom b/chromium/components/safe_browsing/content/common/safe_browsing.mojom index a5cc71ad13e..72c21819f36 100644 --- a/chromium/components/safe_browsing/content/common/safe_browsing.mojom +++ b/chromium/components/safe_browsing/content/common/safe_browsing.mojom @@ -118,7 +118,13 @@ enum PhishingDetectorResult { }; [EnableIf=full_safe_browsing] +// Interface for setting the CSD model and to start phishing classification. interface PhishingDetector { + // A classification model for client-side phishing detection. + // The string is an encoded safe_browsing::ClientSideModel protocol buffer, or + // empty to disable client-side phishing detection for this renderer. + SetPhishingModel(string model); + // Tells the renderer to begin phishing detection for the given toplevel URL // which it has started loading. Returns the serialized request proto and a // |result| enum to indicate failure. If the URL is phishing the request proto @@ -126,10 +132,3 @@ interface PhishingDetector { StartPhishingDetection(url.mojom.Url url) => (PhishingDetectorResult result, string request_proto); }; - -interface PhishingModelSetter { - // A classification model for client-side phishing detection. - // The string is an encoded safe_browsing::ClientSideModel protocol buffer, or - // empty to disable client-side phishing detection for this renderer. - SetPhishingModel(string model); -}; diff --git a/chromium/components/safe_browsing/content/password_protection/BUILD.gn b/chromium/components/safe_browsing/content/password_protection/BUILD.gn index 26d6f07d5ee..2d06ca399b5 100644 --- a/chromium/components/safe_browsing/content/password_protection/BUILD.gn +++ b/chromium/components/safe_browsing/content/password_protection/BUILD.gn @@ -28,6 +28,7 @@ source_set("password_protection") { "//components/password_manager/core/browser:browser", "//components/safe_browsing/content/common:interfaces", "//components/safe_browsing/content/web_ui:web_ui", + "//components/safe_browsing/core:client_model_proto", "//components/safe_browsing/core:csd_proto", "//components/safe_browsing/core:features", "//components/safe_browsing/core/browser:referrer_chain_provider", @@ -43,7 +44,9 @@ source_set("password_protection") { "//components/url_formatter", "//content/public/browser:browser", "//net:net", + "//third_party/opencv:emd", "//third_party/protobuf:protobuf_lite", + "//ui/gfx:color_utils", ] } if (safe_browsing_mode == 1) { diff --git a/chromium/components/safe_browsing/content/password_protection/DEPS b/chromium/components/safe_browsing/content/password_protection/DEPS index c39c8a73429..776629f7b37 100644 --- a/chromium/components/safe_browsing/content/password_protection/DEPS +++ b/chromium/components/safe_browsing/content/password_protection/DEPS @@ -10,8 +10,9 @@ include_rules = [ "+net", "+services/network/public", "+third_party/blink/public/common/page/page_zoom.h", - "+ui/gfx/geometry", + "+ui/gfx", "+third_party/skia/include", + "+third_party/opencv", ] specific_include_rules = { diff --git a/chromium/components/safe_browsing/content/password_protection/metrics_util.cc b/chromium/components/safe_browsing/content/password_protection/metrics_util.cc index 7b66bf6308b..072a0f97219 100644 --- a/chromium/components/safe_browsing/content/password_protection/metrics_util.cc +++ b/chromium/components/safe_browsing/content/password_protection/metrics_util.cc @@ -7,6 +7,7 @@ #include "base/logging.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" +#include "base/notreached.h" #include "base/time/time.h" #include "net/http/http_status_code.h" diff --git a/chromium/components/safe_browsing/content/password_protection/mock_password_protection_service.h b/chromium/components/safe_browsing/content/password_protection/mock_password_protection_service.h index 1e3783718e4..e88bcd4d1d9 100644 --- a/chromium/components/safe_browsing/content/password_protection/mock_password_protection_service.h +++ b/chromium/components/safe_browsing/content/password_protection/mock_password_protection_service.h @@ -36,13 +36,13 @@ class MockPasswordProtectionService : public PasswordProtectionService { MOCK_CONST_METHOD1(GetSignedInNonSyncAccount, AccountInfo(const std::string&)); MOCK_CONST_METHOD1(IsOtherGaiaAccountGmail, bool(const std::string&)); - MOCK_CONST_METHOD2(IsURLWhitelistedForPasswordEntry, - bool(const GURL&, RequestOutcome*)); + MOCK_CONST_METHOD1(IsURLWhitelistedForPasswordEntry, bool(const GURL&)); MOCK_METHOD0(CanSendSamplePing, bool()); MOCK_METHOD0(IsExtendedReporting, bool()); MOCK_METHOD0(IsEnhancedProtection, bool()); MOCK_METHOD0(IsIncognito, bool()); + MOCK_METHOD1(IsInPasswordAlertMode, bool(ReusedPasswordAccountType)); MOCK_METHOD0(IsHistorySyncEnabled, bool()); MOCK_METHOD0(IsUnderAdvancedProtection, bool()); MOCK_METHOD0(ReportPasswordChanged, void()); @@ -57,10 +57,13 @@ class MockPasswordProtectionService : public PasswordProtectionService { MOCK_METHOD1( RemovePhishedSavedPasswordCredential, void(const std::vector<password_manager::MatchingReusedCredential>&)); - MOCK_METHOD3(IsPingingEnabled, + MOCK_METHOD2(IsPingingEnabled, bool(LoginReputationClientRequest::TriggerType, - ReusedPasswordAccountType, - RequestOutcome*)); + ReusedPasswordAccountType)); + MOCK_METHOD3(GetPingNotSentReason, + RequestOutcome(LoginReputationClientRequest::TriggerType, + const GURL&, + ReusedPasswordAccountType)); MOCK_METHOD5(ShowModalWarning, void(content::WebContents*, RequestOutcome, @@ -85,8 +88,8 @@ class MockPasswordProtectionService : public PasswordProtectionService { RequestOutcome, PasswordType, const safe_browsing::LoginReputationClientResponse*)); - MOCK_METHOD3(CanShowInterstitial, - bool(RequestOutcome, ReusedPasswordAccountType, const GURL&)); + MOCK_METHOD2(CanShowInterstitial, + bool(ReusedPasswordAccountType, const GURL&)); MOCK_METHOD5(MaybeStartPasswordFieldOnFocusRequest, void(content::WebContents*, const GURL&, diff --git a/chromium/components/safe_browsing/content/password_protection/password_protection_request.cc b/chromium/components/safe_browsing/content/password_protection/password_protection_request.cc index 82b217141c6..94831c3c64c 100644 --- a/chromium/components/safe_browsing/content/password_protection/password_protection_request.cc +++ b/chromium/components/safe_browsing/content/password_protection/password_protection_request.cc @@ -11,7 +11,6 @@ #include "base/containers/flat_set.h" #include "base/memory/weak_ptr.h" #include "base/metrics/histogram_macros.h" -#include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "base/time/time.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" @@ -159,7 +158,7 @@ void PasswordProtectionRequest::CheckWhitelist() { auto result_callback = base::BindOnce(&OnWhitelistCheckDoneOnIO, GetWeakPtr()); tracker_.PostTask( - base::CreateSingleThreadTaskRunner({BrowserThread::IO}).get(), FROM_HERE, + content::GetIOThreadTaskRunner({}).get(), FROM_HERE, base::BindOnce(&AllowlistCheckerClient::StartCheckCsdWhitelist, password_protection_service_->database_manager(), main_frame_url_, std::move(result_callback))); @@ -170,8 +169,8 @@ void PasswordProtectionRequest::OnWhitelistCheckDoneOnIO( base::WeakPtr<PasswordProtectionRequest> weak_request, bool match_whitelist) { // Don't access weak_request on IO thread. Move it back to UI thread first. - base::PostTask( - FROM_HERE, {BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&PasswordProtectionRequest::OnWhitelistCheckDone, weak_request, match_whitelist)); } @@ -332,8 +331,8 @@ void PasswordProtectionRequest::FillRequestProto(bool is_sampled_ping) { main_frame_url_, base::BindRepeating(&PasswordProtectionRequest::OnGetDomFeatures, GetWeakPtr())); - base::PostDelayedTask( - FROM_HERE, {BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostDelayedTask( + FROM_HERE, base::BindOnce(&PasswordProtectionRequest::OnGetDomFeatureTimeout, GetWeakPtr()), base::TimeDelta::FromMilliseconds(kDomFeatureTimeoutMs)); @@ -520,8 +519,8 @@ void PasswordProtectionRequest::StartTimeout() { // The weak pointer used for the timeout will be invalidated (and // hence would prevent the timeout) if the check completes on time and // execution reaches Finish(). - base::PostDelayedTask( - FROM_HERE, {BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostDelayedTask( + FROM_HERE, base::BindOnce(&PasswordProtectionRequest::Cancel, GetWeakPtr(), true), base::TimeDelta::FromMilliseconds(request_timeout_in_ms_)); } diff --git a/chromium/components/safe_browsing/content/password_protection/password_protection_service.cc b/chromium/components/safe_browsing/content/password_protection/password_protection_service.cc index f93318f850b..6a180cb90ef 100644 --- a/chromium/components/safe_browsing/content/password_protection/password_protection_service.cc +++ b/chromium/components/safe_browsing/content/password_protection/password_protection_service.cc @@ -17,7 +17,6 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" -#include "base/task/post_task.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_reuse_detector.h" @@ -101,18 +100,22 @@ void PasswordProtectionService::MaybeStartPasswordFieldOnFocusRequest( const GURL& password_form_frame_url, const std::string& hosted_domain) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - RequestOutcome reason; - if (CanSendPing(LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, - main_frame_url, - GetPasswordProtectionReusedPasswordAccountType( - PasswordType::PASSWORD_TYPE_UNKNOWN, - /*username=*/""), - &reason)) { + LoginReputationClientRequest::TriggerType trigger_type = + LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE; + ReusedPasswordAccountType reused_password_account_type = + GetPasswordProtectionReusedPasswordAccountType( + PasswordType::PASSWORD_TYPE_UNKNOWN, + /*username=*/""); + if (CanSendPing(trigger_type, main_frame_url, reused_password_account_type)) { StartRequest(web_contents, main_frame_url, password_form_action, password_form_frame_url, /* username */ "", PasswordType::PASSWORD_TYPE_UNKNOWN, {}, /* matching_reused_credentials: not used for this type */ LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, true); + } else { + RequestOutcome reason = GetPingNotSentReason(trigger_type, main_frame_url, + reused_password_account_type); + LogNoPingingReason(trigger_type, reason, reused_password_account_type); } } #endif @@ -127,13 +130,10 @@ void PasswordProtectionService::MaybeStartProtectedPasswordEntryRequest( matching_reused_credentials, bool password_field_exists) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + LoginReputationClientRequest::TriggerType trigger_type = + LoginReputationClientRequest::PASSWORD_REUSE_EVENT; ReusedPasswordAccountType reused_password_account_type = GetPasswordProtectionReusedPasswordAccountType(password_type, username); - RequestOutcome reason; - // Need to populate |reason| to be passed into CanShowInterstitial. - bool can_send_ping = - CanSendPing(LoginReputationClientRequest::PASSWORD_REUSE_EVENT, - main_frame_url, reused_password_account_type, &reason); if (IsSupportedPasswordTypeForPinging(password_type)) { #if BUILDFLAG(FULL_SAFE_BROWSING) // Collect metrics about typical page-zoom on login pages. @@ -143,7 +143,8 @@ void PasswordProtectionService::MaybeStartProtectedPasswordEntryRequest( "PasswordProtection.PageZoomFactor", static_cast<int>(100 * blink::PageZoomLevelToZoomFactor(zoom_level))); #endif // defined(FULL_SAFE_BROWSING) - if (can_send_ping) { + if (CanSendPing(LoginReputationClientRequest::PASSWORD_REUSE_EVENT, + main_frame_url, reused_password_account_type)) { saved_passwords_matching_reused_credentials_ = matching_reused_credentials; StartRequest(web_contents, main_frame_url, GURL(), GURL(), username, @@ -151,6 +152,9 @@ void PasswordProtectionService::MaybeStartProtectedPasswordEntryRequest( LoginReputationClientRequest::PASSWORD_REUSE_EVENT, password_field_exists); } else { + RequestOutcome reason = GetPingNotSentReason( + trigger_type, main_frame_url, reused_password_account_type); + LogNoPingingReason(trigger_type, reason, reused_password_account_type); #if defined(SYNC_PASSWORD_REUSE_WARNING_ENABLED) if (reused_password_account_type.is_account_syncing()) MaybeLogPasswordReuseLookupEvent(web_contents, reason, password_type, @@ -160,8 +164,9 @@ void PasswordProtectionService::MaybeStartProtectedPasswordEntryRequest( } #if defined(SYNC_PASSWORD_REUSE_WARNING_ENABLED) - if (CanShowInterstitial(reason, reused_password_account_type, - main_frame_url)) { + if (CanShowInterstitial(reused_password_account_type, main_frame_url)) { + LogPasswordAlertModeOutcome(RequestOutcome::SUCCEEDED, + reused_password_account_type); username_for_last_shown_warning_ = username; reused_password_account_type_for_last_shown_warning_ = reused_password_account_type; @@ -247,24 +252,9 @@ void PasswordProtectionService::StartRequest( bool PasswordProtectionService::CanSendPing( LoginReputationClientRequest::TriggerType trigger_type, const GURL& main_frame_url, - ReusedPasswordAccountType password_type, - RequestOutcome* reason) { - *reason = RequestOutcome::UNKNOWN; - bool is_pinging_enabled = - IsPingingEnabled(trigger_type, password_type, reason); - // Pinging is enabled for password_reuse trigger level; however we need to - // make sure *reason is set appropriately. - PasswordProtectionTrigger trigger_level = - GetPasswordProtectionWarningTriggerPref(password_type); - if (trigger_level == PASSWORD_REUSE) { - *reason = RequestOutcome::PASSWORD_ALERT_MODE; - } - if (is_pinging_enabled && - !IsURLWhitelistedForPasswordEntry(main_frame_url, reason)) { - return true; - } - LogNoPingingReason(trigger_type, *reason, password_type); - return false; + ReusedPasswordAccountType password_type) { + return IsPingingEnabled(trigger_type, password_type) && + !IsURLWhitelistedForPasswordEntry(main_frame_url); } void PasswordProtectionService::RequestFinished( @@ -408,8 +398,8 @@ void PasswordProtectionService::FillUserPopulation( void PasswordProtectionService::OnURLsDeleted( history::HistoryService* history_service, const history::DeletionInfo& deletion_info) { - base::PostTask( - FROM_HERE, {BrowserThread::UI}, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindRepeating(&PasswordProtectionService:: RemoveUnhandledSyncPasswordReuseOnURLsDeleted, GetWeakPtr(), deletion_info.IsAllHistory(), @@ -551,7 +541,8 @@ bool PasswordProtectionService::IsSupportedPasswordTypeForPinging( PasswordType password_type) const { switch (password_type) { case PasswordType::SAVED_PASSWORD: - return true; + return base::FeatureList::IsEnabled( + safe_browsing::kPasswordProtectionForSavedPasswords); case PasswordType::PRIMARY_ACCOUNT_PASSWORD: return true; case PasswordType::ENTERPRISE_PASSWORD: diff --git a/chromium/components/safe_browsing/content/password_protection/password_protection_service.h b/chromium/components/safe_browsing/content/password_protection/password_protection_service.h index 307e95dbd3d..1823146c4b4 100644 --- a/chromium/components/safe_browsing/content/password_protection/password_protection_service.h +++ b/chromium/components/safe_browsing/content/password_protection/password_protection_service.h @@ -208,9 +208,7 @@ class PasswordProtectionService : public history::HistoryServiceObserver { // If |url| matches Safe Browsing whitelist domains, password protection // change password URL, or password protection login URLs in the enterprise // policy. - virtual bool IsURLWhitelistedForPasswordEntry( - const GURL& url, - RequestOutcome* reason) const = 0; + virtual bool IsURLWhitelistedForPasswordEntry(const GURL& url) const = 0; // Persist the phished saved password credential in the "compromised // credentials" table. Calls the password store to add a row for each @@ -300,14 +298,12 @@ class PasswordProtectionService : public history::HistoryServiceObserver { friend class PasswordProtectionRequest; // Chrome can send password protection ping if it is allowed by for the - // |trigger_type| and if Safe Browsing can compute reputation of - // |main_frame_url| (e.g. Safe Browsing is not able to compute reputation of a - // private IP or a local host). Update |reason| if sending ping is not - // allowed. |password_type| is used for UMA metric recording. + // |trigger_type| and |password_type| and if Safe Browsing can compute + // reputation of |main_frame_url| (e.g. Safe Browsing is not able to compute + // reputation of a private IP or a local host). bool CanSendPing(LoginReputationClientRequest::TriggerType trigger_type, const GURL& main_frame_url, - ReusedPasswordAccountType password_type, - RequestOutcome* reason); + ReusedPasswordAccountType password_type); // Called by a PasswordProtectionRequest instance when it finishes to remove // itself from |requests_|. @@ -364,10 +360,12 @@ class PasswordProtectionService : public history::HistoryServiceObserver { virtual bool IsIncognito() = 0; + virtual bool IsInPasswordAlertMode( + ReusedPasswordAccountType password_type) = 0; + virtual bool IsPingingEnabled( LoginReputationClientRequest::TriggerType trigger_type, - ReusedPasswordAccountType password_type, - RequestOutcome* reason) = 0; + ReusedPasswordAccountType password_type) = 0; virtual bool IsHistorySyncEnabled() = 0; @@ -408,10 +406,8 @@ class PasswordProtectionService : public history::HistoryServiceObserver { bool IsModalWarningShowingInWebContents(content::WebContents* web_contents); // Determines if we should show chrome://reset-password interstitial based on - // previous request outcome, the reused |password_type| and the - // |main_frame_url|. - virtual bool CanShowInterstitial(RequestOutcome reason, - ReusedPasswordAccountType password_type, + // the reused |password_type| and the |main_frame_url|. + virtual bool CanShowInterstitial(ReusedPasswordAccountType password_type, const GURL& main_frame_url) = 0; #endif @@ -422,6 +418,13 @@ class PasswordProtectionService : public history::HistoryServiceObserver { virtual LoginReputationClientRequest::PasswordReuseEvent::SyncAccountType GetSyncAccountType() const = 0; + // Returns the reason why a ping is not sent based on the |trigger_type|, + // |url| and |password_type|. Crash if |CanSendPing| is true. + virtual RequestOutcome GetPingNotSentReason( + LoginReputationClientRequest::TriggerType trigger_type, + const GURL& url, + ReusedPasswordAccountType password_type) = 0; + const std::list<std::string>& common_spoofed_domains() const { return common_spoofed_domains_; } diff --git a/chromium/components/safe_browsing/content/password_protection/password_protection_service_unittest.cc b/chromium/components/safe_browsing/content/password_protection/password_protection_service_unittest.cc index d7c00d3fe41..26357224b45 100644 --- a/chromium/components/safe_browsing/content/password_protection/password_protection_service_unittest.cc +++ b/chromium/components/safe_browsing/content/password_protection/password_protection_service_unittest.cc @@ -96,6 +96,8 @@ class TestPhishingDetector : public mojom::PhishingDetector { mojo::PendingReceiver<mojom::PhishingDetector>(std::move(handle))); } + void SetPhishingModel(const std::string& model) override {} + void StartPhishingDetection( const GURL& url, StartPhishingDetectionCallback callback) override { @@ -275,7 +277,7 @@ class PasswordProtectionServiceTest : public ::testing::TestWithParam<bool> { EXPECT_CALL(*password_protection_service_, IsIncognito()) .WillRepeatedly(Return(false)); EXPECT_CALL(*password_protection_service_, - IsURLWhitelistedForPasswordEntry(_, _)) + IsURLWhitelistedForPasswordEntry(_)) .WillRepeatedly(Return(false)); EXPECT_CALL(*password_protection_service_, GetPasswordProtectionWarningTriggerPref(_)) @@ -1166,9 +1168,13 @@ TEST_P(PasswordProtectionServiceTest, VerifyShouldShowModalWarning) { reused_password_account_type.set_account_type( ReusedPasswordAccountType::SAVED_PASSWORD); - EXPECT_TRUE(password_protection_service_->ShouldShowModalWarning( + EXPECT_FALSE(password_protection_service_->ShouldShowModalWarning( LoginReputationClientRequest::PASSWORD_REUSE_EVENT, - reused_password_account_type, LoginReputationClientResponse::PHISHING)); + reused_password_account_type, + LoginReputationClientResponse::LOW_REPUTATION)); + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature( + safe_browsing::kPasswordProtectionForSavedPasswords); EXPECT_TRUE(password_protection_service_->ShouldShowModalWarning( LoginReputationClientRequest::PASSWORD_REUSE_EVENT, reused_password_account_type, @@ -1330,38 +1336,26 @@ TEST_P(PasswordProtectionServiceTest, VerifyIsSupportedPasswordTypeForPinging) { .WillRepeatedly(Return(account_info)); EXPECT_TRUE(password_protection_service_->IsSupportedPasswordTypeForPinging( - PasswordType::SAVED_PASSWORD)); - EXPECT_TRUE(password_protection_service_->IsSupportedPasswordTypeForPinging( PasswordType::PRIMARY_ACCOUNT_PASSWORD)); -// kPasswordProtectionForSignedInUsers is disabled by default on Android. -#if defined(OS_ANDROID) EXPECT_FALSE(password_protection_service_->IsSupportedPasswordTypeForPinging( -#else - EXPECT_TRUE(password_protection_service_->IsSupportedPasswordTypeForPinging( -#endif PasswordType::OTHER_GAIA_PASSWORD)); EXPECT_TRUE(password_protection_service_->IsSupportedPasswordTypeForPinging( PasswordType::ENTERPRISE_PASSWORD)); - - EXPECT_TRUE(password_protection_service_->IsSupportedPasswordTypeForPinging( + EXPECT_FALSE(password_protection_service_->IsSupportedPasswordTypeForPinging( PasswordType::SAVED_PASSWORD)); - EXPECT_TRUE(password_protection_service_->IsSupportedPasswordTypeForPinging( - PasswordType::ENTERPRISE_PASSWORD)); { base::test::ScopedFeatureList feature_list; - feature_list.InitAndDisableFeature( + feature_list.InitAndEnableFeature( safe_browsing::kPasswordProtectionForSignedInUsers); - // Only ping for signed in, non-syncing users if the experiment is on. - EXPECT_FALSE( - password_protection_service_->IsSupportedPasswordTypeForPinging( - PasswordType::OTHER_GAIA_PASSWORD)); + EXPECT_TRUE(password_protection_service_->IsSupportedPasswordTypeForPinging( + PasswordType::OTHER_GAIA_PASSWORD)); } { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature( - safe_browsing::kPasswordProtectionForSignedInUsers); + safe_browsing::kPasswordProtectionForSavedPasswords); EXPECT_TRUE(password_protection_service_->IsSupportedPasswordTypeForPinging( - PasswordType::OTHER_GAIA_PASSWORD)); + PasswordType::SAVED_PASSWORD)); } } diff --git a/chromium/components/safe_browsing/content/password_protection/visual_utils.cc b/chromium/components/safe_browsing/content/password_protection/visual_utils.cc index 85349795389..c751167b8a3 100644 --- a/chromium/components/safe_browsing/content/password_protection/visual_utils.cc +++ b/chromium/components/safe_browsing/content/password_protection/visual_utils.cc @@ -7,9 +7,14 @@ #include "components/safe_browsing/content/password_protection/visual_utils.h" +#include "base/check_op.h" #include "base/numerics/checked_math.h" +#include "components/safe_browsing/core/proto/client_model.pb.h" +#include "components/safe_browsing/core/proto/csd.pb.h" +#include "third_party/opencv/src/emd_wrapper.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkPixmap.h" +#include "ui/gfx/color_utils.h" namespace safe_browsing { namespace visual_utils { @@ -22,6 +27,87 @@ const int kPHashDownsampleWidth = 288; const int kPHashDownsampleHeight = 288; const int kPHashBlockSize = 6; +// Constants for computing Earth Movers Distance (EMD) between ColorHistograms. +const float kMaxCentroidDistance = 1.414; +const float kMaxColorDistance = 14; + +size_t GetBitCount(uint8_t byte) { + size_t count = 0; + for (; byte > 0; byte >>= 1) { + if (byte & 1) + count++; + } + + return count; +} + +bool GetHashDistance(const std::string& hash1, + const std::string& hash2, + size_t* out) { + // The beginning of the hash is a Varint representing the stride. For values + // at most 127, this is just the value of the first char. Chrome only + // generates strides less than 128, so we can simply extract the first char. + if (hash1[0] != hash2[0]) + return false; + if (hash1[0] & 0x80) + return false; + + size_t count = std::min(hash1.size(), hash2.size()); + size_t distance = 8 * (std::max(hash1.size(), hash2.size()) - count); + for (size_t i = 1; i < count; i++) { + distance += GetBitCount(hash1[i] ^ hash2[i]); + } + + *out = distance; + return true; +} + +opencv::PointDistribution HistogramBinsToPointDistribution( + const google::protobuf::RepeatedPtrField<VisualFeatures::ColorHistogramBin>& + bins) { + opencv::PointDistribution distribution; + distribution.dimensions = 5; + for (const VisualFeatures::ColorHistogramBin& bin : bins) { + distribution.weights.push_back(bin.weight()); + std::vector<float> position(5); + position[0] = bin.centroid_x() / kMaxCentroidDistance; + position[1] = bin.centroid_y() / kMaxCentroidDistance; + position[2] = bin.quantized_r() / kMaxColorDistance; + position[3] = bin.quantized_g() / kMaxColorDistance; + position[4] = bin.quantized_b() / kMaxColorDistance; + distribution.positions.push_back(std::move(position)); + } + + return distribution; +} + +bool ImageHasColorInRange(const SkBitmap& image, + const MatchRule::ColorRange& color_range) { + for (int i = 0; i < image.width(); i++) { + for (int j = 0; j < image.height(); j++) { + SkScalar hsv[3]; + SkColorToHSV(image.getColor(i, j), hsv); + if (color_range.low() <= hsv[0] && hsv[0] <= color_range.high()) + return true; + } + } + + return false; +} + +uint8_t GetMedian(const std::vector<uint8_t>& data) { + std::vector<uint8_t> buffer; + buffer.assign(data.begin(), data.end()); + std::vector<uint8_t>::iterator middle = buffer.begin() + (buffer.size() / 2); + std::nth_element(buffer.begin(), middle, buffer.end()); + if (buffer.size() % 2 == 1) { + return *middle; + } else { + // For even-sized sets, return the average of the two middle elements. + return (*middle + *std::max_element(buffer.begin(), middle)) / 2; + } +} + } // namespace // A QuantizedColor takes the highest 3 bits of R, G, and B, and concatenates @@ -171,5 +257,101 @@ std::unique_ptr<SkBitmap> BlockMeanAverage(const SkBitmap& image, return target; } +std::string GetHashFromBlurredImage( + VisualFeatures::BlurredImage blurred_image) { + DCHECK_EQ(blurred_image.data().size(), + 3u * blurred_image.width() * blurred_image.height()); + // Convert the blurred image to grayscale. + std::vector<uint8_t> luma_values; + luma_values.resize(blurred_image.width() * blurred_image.height()); + for (size_t i = 0; i < luma_values.size(); i++) { + luma_values[i] = color_utils::GetLuma(SkColorSetRGB( + static_cast<unsigned char>(blurred_image.data()[3 * i]), + static_cast<unsigned char>(blurred_image.data()[3 * i + 1]), + static_cast<unsigned char>(blurred_image.data()[3 * i + 2]))); + } + + uint8_t median_luma = GetMedian(luma_values); + + std::string output; + + // The server-side hash generation writes a Varint here, rather than just an + // int. These encodings coincide as long as the width is at most 127. If + // we begin using larger widths, we need to implement the Varint encoding used + // on the server. + DCHECK_LT(blurred_image.width(), 128); + output.push_back(static_cast<unsigned char>(blurred_image.width())); + + // Write the output bitstring. + unsigned char next_byte = 0; + int bits_encoded_so_far = 0; + for (const uint8_t luma_value : luma_values) { + next_byte <<= 1; + if (luma_value >= median_luma) + next_byte |= 1; + + ++bits_encoded_so_far; + if (bits_encoded_so_far == 8) { + output.push_back(next_byte); + bits_encoded_so_far = 0; + } + } + + if (bits_encoded_so_far != 0) { + next_byte <<= 8 - bits_encoded_so_far; + output.push_back(next_byte); + } + + return output; +} + +base::Optional<VisionMatchResult> IsVisualMatch(const SkBitmap& image, + const VisualTarget& target) { + VisualFeatures::BlurredImage blurred_image; + if (!GetBlurredImage(image, &blurred_image)) + return base::nullopt; + std::string hash = GetHashFromBlurredImage(blurred_image); + size_t hash_distance; + bool has_hash_distance = GetHashDistance(hash, target.hash(), &hash_distance); + + VisualFeatures::ColorHistogram histogram; + if (!GetHistogramForImage(image, &histogram)) + return base::nullopt; + + opencv::PointDistribution point_distribution = + HistogramBinsToPointDistribution(histogram.bins()); + base::Optional<double> color_distance = opencv::EMD( + point_distribution, HistogramBinsToPointDistribution(target.bins())); + + for (const MatchRule& match_rule : target.match_config().match_rule()) { + bool is_match = true; + if (match_rule.has_hash_distance()) { + is_match &= + (has_hash_distance && hash_distance <= match_rule.hash_distance()); + } + + if (match_rule.has_color_distance()) { + is_match &= (color_distance.has_value() && + color_distance.value() <= match_rule.color_distance()); + } + + for (const MatchRule::ColorRange& color_range : match_rule.color_range()) { + is_match &= ImageHasColorInRange(image, color_range); + } + + if (is_match) { + VisionMatchResult result; + result.set_matched_target_digest(target.digest()); + if (has_hash_distance) + result.set_vision_matched_phash_score(hash_distance); + if (color_distance.has_value()) + result.set_vision_matched_emd_score(color_distance.value()); + return result; + } + } + + return base::nullopt; +} + } // namespace visual_utils } // namespace safe_browsing diff --git a/chromium/components/safe_browsing/content/password_protection/visual_utils.h b/chromium/components/safe_browsing/content/password_protection/visual_utils.h index 6d2d24fdf40..34b6764efa5 100644 --- a/chromium/components/safe_browsing/content/password_protection/visual_utils.h +++ b/chromium/components/safe_browsing/content/password_protection/visual_utils.h @@ -7,6 +7,8 @@ #include <string> +#include "base/optional.h" +#include "components/safe_browsing/core/proto/client_model.pb.h" #include "components/safe_browsing/core/proto/csd.pb.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -39,6 +41,14 @@ bool GetBlurredImage(const SkBitmap& image, std::unique_ptr<SkBitmap> BlockMeanAverage(const SkBitmap& image, int block_size); +// Returns the hash used to compare blurred images. +std::string GetHashFromBlurredImage(VisualFeatures::BlurredImage blurred_image); + +// Returns whether the given |image| is a match for the |target|. Returns +// nullopt in the case of no match, and the VisionMatchResult if it is a match. +base::Optional<VisionMatchResult> IsVisualMatch(const SkBitmap& image, + const VisualTarget& target); + } // namespace visual_utils } // namespace safe_browsing diff --git a/chromium/components/safe_browsing/content/password_protection/visual_utils_unittest.cc b/chromium/components/safe_browsing/content/password_protection/visual_utils_unittest.cc index 140c97884d2..752e65dbe3e 100644 --- a/chromium/components/safe_browsing/content/password_protection/visual_utils_unittest.cc +++ b/chromium/components/safe_browsing/content/password_protection/visual_utils_unittest.cc @@ -252,5 +252,264 @@ TEST_F(VisualUtilsTest, BlockMeanAveragePartialBlocks) { EXPECT_EQ(*blocks->getAddr32(1, 1), kBlue); } +TEST_F(VisualUtilsTest, IsVisualMatchHash) { + { + // An all-white image should hash to all 1-bits. + for (int x = 0; x < 1000; x++) + for (int y = 0; y < 1000; y++) + *bitmap_.getAddr32(x, y) = kWhite; + + std::vector<unsigned char> target_hash; + target_hash.push_back('\x30'); + for (int i = 0; i < 288; i++) + target_hash.push_back('\xff'); + + VisualTarget target; + target.set_hash(target_hash.data(), target_hash.size()); + target.mutable_match_config()->add_match_rule()->set_hash_distance(0.0); + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + } + + { + // Make the top quarter black, and the corresponding bits of the hash should + // be 0. + for (int x = 0; x < 1000; x++) + for (int y = 0; y < 250; y++) + *bitmap_.getAddr32(x, y) = kBlack; + + std::vector<unsigned char> target_hash; + target_hash.push_back('\x30'); + for (int i = 0; i < 72; i++) + target_hash.push_back('\x00'); + for (int i = 0; i < 216; i++) + target_hash.push_back('\xff'); + VisualTarget target; + target.set_hash(target_hash.data(), target_hash.size()); + + target.mutable_match_config()->add_match_rule()->set_hash_distance(0.0); + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + } +} + +TEST_F(VisualUtilsTest, IsVisualMatchHashPartialMatch) { + // Make the top quarter black, and the corresponding bits of the hash should + // be 0. + for (int x = 0; x < 1000; x++) + for (int y = 0; y < 250; y++) + *bitmap_.getAddr32(x, y) = kBlack; + for (int x = 0; x < 1000; x++) + for (int y = 250; y < 1000; y++) + *bitmap_.getAddr32(x, y) = kWhite; + + std::vector<unsigned char> target_hash; + target_hash.push_back('\x30'); + for (int i = 0; i < 75; i++) + target_hash.push_back('\x00'); + for (int i = 0; i < 213; i++) + target_hash.push_back('\xff'); + + VisualTarget target; + target.set_hash(target_hash.data(), target_hash.size()); + target.mutable_match_config()->add_match_rule()->set_hash_distance(23.0); + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); + target.mutable_match_config()->add_match_rule()->set_hash_distance(24.0); + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); +} + +TEST_F(VisualUtilsTest, IsVisualMatchHashStrideComparison) { + for (int x = 0; x < 1000; x++) + for (int y = 0; y < 1000; y++) + *bitmap_.getAddr32(x, y) = kWhite; + + std::vector<unsigned char> target_hash; + target_hash.push_back('\x30'); + for (int i = 0; i < 288; i++) + target_hash.push_back('\xff'); + + VisualTarget target; + target.set_hash(target_hash.data(), target_hash.size()); + target.mutable_match_config()->add_match_rule()->set_hash_distance(0.0); + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + + target_hash[0] = '\x00'; + target.set_hash(target_hash.data(), target_hash.size()); + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); +} + +TEST_F(VisualUtilsTest, IsVisualMatchHistogramOnly) { + for (int x = 0; x < 1000; x++) + for (int y = 0; y < 1000; y++) + *bitmap_.getAddr32(x, y) = kWhite; + + { + VisualTarget target; + VisualFeatures::ColorHistogramBin* bin = target.add_bins(); + // Our coordinates range from 0 to 999, giving an average of 0.4995 instead + // of 0.5. + bin->set_centroid_x(0.4995); + bin->set_centroid_y(0.4995); + bin->set_quantized_r(7); + bin->set_quantized_g(7); + bin->set_quantized_b(7); + bin->set_weight(1.0); + target.mutable_match_config()->add_match_rule()->set_color_distance(0.0); + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + } + + { + // Move the target histogram to have centroid 0,0. This leads to a distance + // just under 0.5 between the actual and target histograms. + VisualTarget target; + VisualFeatures::ColorHistogramBin* bin = target.add_bins(); + bin->set_centroid_x(0); + bin->set_centroid_y(0); + bin->set_quantized_r(7); + bin->set_quantized_g(7); + bin->set_quantized_b(7); + bin->set_weight(1.0); + + MatchRule* match_rule = target.mutable_match_config()->add_match_rule(); + match_rule->set_color_distance(0.5); + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + + match_rule->set_color_distance(0.4); + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); + } + + { + // Change the target histogram color slightly. This leads to a distance + // of roughly 0.12 between the actual and target histogram. + VisualTarget target; + VisualFeatures::ColorHistogramBin* bin = target.add_bins(); + bin->set_centroid_x(0.4995); + bin->set_centroid_y(0.4995); + bin->set_quantized_r(6); + bin->set_quantized_g(6); + bin->set_quantized_b(6); + bin->set_weight(1.0); + + MatchRule* match_rule = target.mutable_match_config()->add_match_rule(); + match_rule->set_color_distance(0.2); + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + + match_rule->set_color_distance(0.1); + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); + } +} + +TEST_F(VisualUtilsTest, IsVisualMatchColorRange) { + for (int x = 0; x < 1000; x++) + for (int y = 0; y < 1000; y++) + *bitmap_.getAddr32(x, y) = kWhite; + *bitmap_.getAddr32(0, 0) = kBlue; + + SkScalar hsv[3]; + SkColorToHSV(bitmap_.getColor(0, 0), hsv); + SkScalar target_hue = hsv[0]; + VisualTarget target; + MatchRule::ColorRange* color_range = + target.mutable_match_config()->add_match_rule()->add_color_range(); + color_range->set_low(target_hue); + color_range->set_high(target_hue); + + // Blue hue present + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + + // Color range too high + color_range->set_low(target_hue + 1); + color_range->set_high(target_hue + 1); + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); + + // Color range too low + color_range->set_low(target_hue - 1); + color_range->set_high(target_hue - 1); + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); + + // No blue hue present + *bitmap_.getAddr32(0, 0) = kWhite; + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); +} + +TEST_F(VisualUtilsTest, IsVisualMatchMultipleColorRanges) { + for (int x = 0; x < 1000; x++) + for (int y = 0; y < 1000; y++) + *bitmap_.getAddr32(x, y) = kWhite; + *bitmap_.getAddr32(0, 0) = kBlue; + *bitmap_.getAddr32(1, 0) = kGreen; + + SkScalar hsv[3]; + SkColorToHSV(bitmap_.getColor(0, 0), hsv); + SkScalar blue_hue = hsv[0]; + VisualTarget target; + MatchRule* match_rule = target.mutable_match_config()->add_match_rule(); + MatchRule::ColorRange* color_range = match_rule->add_color_range(); + color_range->set_low(blue_hue); + color_range->set_high(blue_hue); + + SkColorToHSV(bitmap_.getColor(1, 0), hsv); + SkScalar green_hue = hsv[0]; + color_range = match_rule->add_color_range(); + color_range->set_low(green_hue); + color_range->set_high(green_hue); + + // Both hues present + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + + // No blue hue present + *bitmap_.getAddr32(0, 0) = kWhite; + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); + + // No green hue present + *bitmap_.getAddr32(0, 0) = kBlue; + *bitmap_.getAddr32(1, 0) = kWhite; + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); + + // Neither hue present + *bitmap_.getAddr32(0, 0) = kWhite; + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); +} + +TEST_F(VisualUtilsTest, IsVisualMatchMultipleMatchRules) { + for (int x = 0; x < 1000; x++) + for (int y = 0; y < 1000; y++) + *bitmap_.getAddr32(x, y) = kWhite; + *bitmap_.getAddr32(0, 0) = kBlue; + *bitmap_.getAddr32(1, 0) = kGreen; + + // Create a target with two match rules, one matching blue pixels and one + // matching green. + VisualTarget target; + MatchRule* match_rule_blue = target.mutable_match_config()->add_match_rule(); + MatchRule::ColorRange* color_range = match_rule_blue->add_color_range(); + SkScalar hsv[3]; + SkColorToHSV(bitmap_.getColor(0, 0), hsv); + SkScalar blue_hue = hsv[0]; + color_range->set_low(blue_hue); + color_range->set_high(blue_hue); + + MatchRule* match_rule_green = target.mutable_match_config()->add_match_rule(); + color_range = match_rule_green->add_color_range(); + SkColorToHSV(bitmap_.getColor(1, 0), hsv); + SkScalar green_hue = hsv[0]; + color_range->set_low(green_hue); + color_range->set_high(green_hue); + + // Both hues present + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + + // No blue hue present + *bitmap_.getAddr32(0, 0) = kWhite; + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + + // No green hue present + *bitmap_.getAddr32(0, 0) = kBlue; + *bitmap_.getAddr32(1, 0) = kWhite; + EXPECT_TRUE(IsVisualMatch(bitmap_, target).has_value()); + + // Neither hue present + *bitmap_.getAddr32(0, 0) = kWhite; + EXPECT_FALSE(IsVisualMatch(bitmap_, target).has_value()); +} + } // namespace visual_utils } // namespace safe_browsing diff --git a/chromium/components/safe_browsing/content/renderer/websocket_sb_handshake_throttle.cc b/chromium/components/safe_browsing/content/renderer/websocket_sb_handshake_throttle.cc index 4b92b8fcffa..d9fef3975a1 100644 --- a/chromium/components/safe_browsing/content/renderer/websocket_sb_handshake_throttle.cc +++ b/chromium/components/safe_browsing/content/renderer/websocket_sb_handshake_throttle.cc @@ -23,22 +23,9 @@ namespace safe_browsing { WebSocketSBHandshakeThrottle::WebSocketSBHandshakeThrottle( mojom::SafeBrowsing* safe_browsing, int render_frame_id) - : render_frame_id_(render_frame_id), - safe_browsing_(safe_browsing), - result_(Result::UNKNOWN) {} + : render_frame_id_(render_frame_id), safe_browsing_(safe_browsing) {} -WebSocketSBHandshakeThrottle::~WebSocketSBHandshakeThrottle() { - // ThrottleHandshake() should always be called, but since that is done all the - // way over in Blink, just avoid logging if it is not called rather than - // DCHECK()ing. - if (start_time_.is_null()) - return; - if (result_ == Result::UNKNOWN) { - result_ = Result::ABANDONED; - UMA_HISTOGRAM_TIMES("SafeBrowsing.WebSocket.Elapsed.Abandoned", - base::TimeTicks::Now() - start_time_); - } -} +WebSocketSBHandshakeThrottle::~WebSocketSBHandshakeThrottle() = default; void WebSocketSBHandshakeThrottle::ThrottleHandshake( const blink::WebURL& url, @@ -48,7 +35,8 @@ void WebSocketSBHandshakeThrottle::ThrottleHandshake( completion_callback_ = std::move(completion_callback); url_ = url; int load_flags = 0; - start_time_ = base::TimeTicks::Now(); + DCHECK_EQ(state_, State::kInitial); + state_ = State::kStarted; safe_browsing_->CreateCheckerAndCheck( render_frame_id_, url_checker_.BindNewPipeAndPassReceiver(), url, "GET", net::HttpRequestHeaders(), load_flags, @@ -65,17 +53,14 @@ void WebSocketSBHandshakeThrottle::ThrottleHandshake( void WebSocketSBHandshakeThrottle::OnCompleteCheck(bool proceed, bool showed_interstitial) { - DCHECK(!start_time_.is_null()); - base::TimeDelta elapsed = base::TimeTicks::Now() - start_time_; + DCHECK_EQ(state_, State::kStarted); if (proceed) { - result_ = Result::SAFE; - UMA_HISTOGRAM_TIMES("SafeBrowsing.WebSocket.Elapsed.Safe", elapsed); + state_ = State::kSafe; std::move(completion_callback_).Run(base::nullopt); } else { // When the insterstitial is dismissed the page is navigated and this object // is destroyed before reaching here. - result_ = Result::BLOCKED; - UMA_HISTOGRAM_TIMES("SafeBrowsing.WebSocket.Elapsed.Blocked", elapsed); + state_ = State::kBlocked; std::move(completion_callback_) .Run(blink::WebString::FromUTF8(base::StringPrintf( "WebSocket connection to %s failed safe browsing check", @@ -93,7 +78,8 @@ void WebSocketSBHandshakeThrottle::OnCheckResult( return; } - // TODO(yzshen): Notify the network service to pause processing response body. + // TODO(yzshen): Notify the network service to stop reading from the + // WebSocket. if (!notifier_receiver_) { notifier_receiver_ = std::make_unique<mojo::Receiver<mojom::UrlCheckNotifier>>(this); @@ -102,14 +88,12 @@ void WebSocketSBHandshakeThrottle::OnCheckResult( } void WebSocketSBHandshakeThrottle::OnMojoDisconnect() { - DCHECK_EQ(result_, Result::UNKNOWN); + DCHECK(state_ == State::kStarted); url_checker_.reset(); notifier_receiver_.reset(); - // Make the destructor record NOT_SUPPORTED in the result histogram. - result_ = Result::NOT_SUPPORTED; - // Don't record the time elapsed because it's unlikely to be meaningful. + state_ = State::kNotSupported; std::move(completion_callback_).Run(base::nullopt); // |this| is destroyed here. } diff --git a/chromium/components/safe_browsing/content/renderer/websocket_sb_handshake_throttle.h b/chromium/components/safe_browsing/content/renderer/websocket_sb_handshake_throttle.h index 53f7761c78b..c703942d2e0 100644 --- a/chromium/components/safe_browsing/content/renderer/websocket_sb_handshake_throttle.h +++ b/chromium/components/safe_browsing/content/renderer/websocket_sb_handshake_throttle.h @@ -35,14 +35,12 @@ class WebSocketSBHandshakeThrottle : public blink::WebSocketHandshakeThrottle, completion_callback) override; private: - // These values are logged to UMA so do not renumber or reuse. - enum class Result { - UNKNOWN = 0, - SAFE = 1, - BLOCKED = 2, - ABANDONED = 3, - NOT_SUPPORTED = 4, - RESULT_COUNT + enum class State { + kInitial, + kStarted, + kSafe, + kBlocked, + kNotSupported, }; // mojom::UrlCheckNotifier implementation. @@ -60,8 +58,10 @@ class WebSocketSBHandshakeThrottle : public blink::WebSocketHandshakeThrottle, mojo::Remote<mojom::SafeBrowsingUrlChecker> url_checker_; mojom::SafeBrowsing* safe_browsing_; std::unique_ptr<mojo::Receiver<mojom::UrlCheckNotifier>> notifier_receiver_; - base::TimeTicks start_time_; - Result result_; + + // |state_| is used to validate that events happen in the right order. It + // isn't used to control the behaviour of the class. + State state_ = State::kInitial; base::WeakPtrFactory<WebSocketSBHandshakeThrottle> weak_factory_{this}; diff --git a/chromium/components/safe_browsing/content/triggers/ad_popup_trigger.cc b/chromium/components/safe_browsing/content/triggers/ad_popup_trigger.cc index ce197ab32b7..3206c87eb4e 100644 --- a/chromium/components/safe_browsing/content/triggers/ad_popup_trigger.cc +++ b/chromium/components/safe_browsing/content/triggers/ad_popup_trigger.cc @@ -14,7 +14,6 @@ #include "base/rand_util.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" -#include "base/task/post_task.h" #include "components/safe_browsing/content/triggers/trigger_util.h" #include "components/safe_browsing/core/features.h" #include "components/safe_browsing/core/triggers/trigger_manager.h" @@ -68,8 +67,7 @@ AdPopupTrigger::AdPopupTrigger( prefs_(prefs), url_loader_factory_(url_loader_factory), history_service_(history_service), - task_runner_( - base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})) {} + task_runner_(content::GetUIThreadTaskRunner({})) {} AdPopupTrigger::~AdPopupTrigger() {} diff --git a/chromium/components/safe_browsing/content/triggers/ad_redirect_trigger.cc b/chromium/components/safe_browsing/content/triggers/ad_redirect_trigger.cc index a281138194a..842274ef16f 100644 --- a/chromium/components/safe_browsing/content/triggers/ad_redirect_trigger.cc +++ b/chromium/components/safe_browsing/content/triggers/ad_redirect_trigger.cc @@ -14,7 +14,6 @@ #include "base/rand_util.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" -#include "base/task/post_task.h" #include "components/safe_browsing/content/triggers/trigger_util.h" #include "components/safe_browsing/core/features.h" #include "components/safe_browsing/core/triggers/trigger_manager.h" @@ -67,8 +66,7 @@ AdRedirectTrigger::AdRedirectTrigger( prefs_(prefs), url_loader_factory_(url_loader_factory), history_service_(history_service), - task_runner_( - base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})) {} + task_runner_(content::GetUIThreadTaskRunner({})) {} AdRedirectTrigger::~AdRedirectTrigger() {} diff --git a/chromium/components/safe_browsing/content/triggers/ad_sampler_trigger.cc b/chromium/components/safe_browsing/content/triggers/ad_sampler_trigger.cc index afefe48ba55..7cf7372f043 100644 --- a/chromium/components/safe_browsing/content/triggers/ad_sampler_trigger.cc +++ b/chromium/components/safe_browsing/content/triggers/ad_sampler_trigger.cc @@ -14,7 +14,6 @@ #include "base/rand_util.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" -#include "base/task/post_task.h" #include "components/safe_browsing/content/triggers/trigger_util.h" #include "components/safe_browsing/core/features.h" #include "components/safe_browsing/core/triggers/trigger_manager.h" @@ -94,8 +93,7 @@ AdSamplerTrigger::AdSamplerTrigger( prefs_(prefs), url_loader_factory_(url_loader_factory), history_service_(history_service), - task_runner_( - base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})) {} + task_runner_(content::GetUIThreadTaskRunner({})) {} AdSamplerTrigger::~AdSamplerTrigger() {} diff --git a/chromium/components/safe_browsing/content/triggers/suspicious_site_trigger.cc b/chromium/components/safe_browsing/content/triggers/suspicious_site_trigger.cc index e531a18b18b..f144ab0f24b 100644 --- a/chromium/components/safe_browsing/content/triggers/suspicious_site_trigger.cc +++ b/chromium/components/safe_browsing/content/triggers/suspicious_site_trigger.cc @@ -8,7 +8,6 @@ #include "base/memory/ptr_util.h" #include "base/metrics/histogram_macros.h" #include "base/single_thread_task_runner.h" -#include "base/task/post_task.h" #include "components/history/core/browser/history_service.h" #include "components/prefs/pref_service.h" #include "components/safe_browsing/core/triggers/trigger_manager.h" @@ -68,8 +67,7 @@ SuspiciousSiteTrigger::SuspiciousSiteTrigger( prefs_(prefs), url_loader_factory_(url_loader_factory), history_service_(history_service), - task_runner_( - base::CreateSingleThreadTaskRunner({content::BrowserThread::UI})) {} + task_runner_(content::GetUIThreadTaskRunner({})) {} SuspiciousSiteTrigger::~SuspiciousSiteTrigger() {} diff --git a/chromium/components/safe_browsing/content/web_ui/BUILD.gn b/chromium/components/safe_browsing/content/web_ui/BUILD.gn index aa148b1849d..1658cb6b147 100644 --- a/chromium/components/safe_browsing/content/web_ui/BUILD.gn +++ b/chromium/components/safe_browsing/content/web_ui/BUILD.gn @@ -12,6 +12,7 @@ static_library("web_ui") { deps = [ "//base", + "//components/enterprise/common/proto:connectors_proto", "//components/password_manager/core/browser:hash_password_manager", "//components/resources:components_resources_grit", "//components/resources:components_scaled_resources_grit", diff --git a/chromium/components/safe_browsing/content/web_ui/DEPS b/chromium/components/safe_browsing/content/web_ui/DEPS index 4019a99d985..c4dfe28ac40 100644 --- a/chromium/components/safe_browsing/content/web_ui/DEPS +++ b/chromium/components/safe_browsing/content/web_ui/DEPS @@ -1,4 +1,5 @@ include_rules = [ + "+components/enterprise/common/proto/connectors.pb.h", "+components/grit/components_resources.h", "+components/password_manager/core/browser/hash_password_manager.h", "+components/user_prefs", diff --git a/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.css b/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.css index 6e6c6e0b70d..2d864d2f6aa 100644 --- a/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.css +++ b/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.css @@ -4,7 +4,7 @@ body { color: rgb(48, 57, 66); - margin:15px; + margin: 15px; } p { white-space: pre-wrap; @@ -13,25 +13,36 @@ p { background-color: #fbfbfb; border: 1px solid #cecece; border-radius: 3px; - padding: 19px; line-height: 1.5; + padding: 19px; } #sb-title { font-size: 2em; margin-bottom: 0.8em; } -h1, h2, h3, p { +h1, +h2, +h3, +p { font-weight: normal; line-height: 1.5; } table.request-response { - table-layout:fixed; - width: 100%; - word-break:break-all; - white-space:pre-wrap; border: 1px solid #cecece; border-radius: 3px; + table-layout: fixed; + white-space: pre-wrap; + width: 100%; + word-break: break-all; } table.request-response td { width: 50%; } +.bold-span { + font-weight: bold; +} +.result-container { + font-weight: normal; + line-height: 1.5; + white-space: normal; +} diff --git a/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.html b/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.html index df4d006c887..75689ec8485 100644 --- a/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.html +++ b/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.html @@ -35,21 +35,21 @@ <tabpanel> <h2>Experiments</h2> <div class="content"> - <p id="experiments-list"></p> + <p id="experiments-list" class="result-container"></p> </div> <h2>Preferences</h2> <div class="content"> - <p id="preferences-list"></p> + <p id="preferences-list" class="result-container"></p> </div> <h2>Safe Browsing Cookie</h2> <div class="content"> - <p id="cookie-panel"></p> + <p id="cookie-panel" class="result-container"></p> </div> </tabpanel> <tabpanel> <h2>Database Manager</h2> <div class="content"> - <p id="database-info-list"></p> + <p id="database-info-list" class="result-container"></p> </div> </tabpanel> <tabpanel> @@ -92,7 +92,7 @@ </tabpanel> <tabpanel> <h2>RT Lookup Pings</h2> - <p id="rt-lookup-experiment-enabled"></p> + <p id="rt-lookup-experiment-enabled" class="result-container"></p> <table id="rt-lookup-ping-list" class="request-response"></table> </tabpanel> <tabpanel> @@ -123,6 +123,24 @@ </tabpanel> </tabpanels> </tabbox> + <template id="result-template"> + <div> + <span class="bold-span"></span> + <span></span> + </div> + </template> + <template id="cookie-template"> + <div> + <span class="bold-span">Value: </span> + <span class="result"></span> + </div> + <span class="bold-span">Created: </span> + <span class="result"></span> + </template> + <template id="rt-lookup-template"> + <span class="bold-span">RT Lookup Experiment Enabled: </span> + <span id="experiment-bool"></span> + </template> <script src="safe_browsing.js"></script> </body> </html> diff --git a/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.js b/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.js index bdb119528f1..f5ec1b4274d 100644 --- a/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.js +++ b/chromium/components/safe_browsing/content/web_ui/resources/safe_browsing.js @@ -161,62 +161,79 @@ cr.define('safe_browsing', function() { function addExperiments(result) { const resLength = result.length; - let experimentsListFormatted = ''; for (let i = 0; i < resLength; i += 2) { - experimentsListFormatted += "<div><b>" + result[i + 1] + - "</b>: " + result[i] + "</div>"; + const experimentsListFormatted = + $('result-template').content.cloneNode(true); + experimentsListFormatted.querySelectorAll('span')[0].textContent = + result[i + 1] + ': '; + experimentsListFormatted.querySelectorAll('span')[1].textContent = + result[i]; + $('experiments-list').appendChild(experimentsListFormatted); } - $('experiments-list').innerHTML = experimentsListFormatted; } function addPrefs(result) { const resLength = result.length; - let preferencesListFormatted = ""; for (let i = 0; i < resLength; i += 2) { - preferencesListFormatted += "<div><b>" + result[i + 1] + "</b>: " + - result[i] + "</div>"; + const preferencesListFormatted = + $('result-template').content.cloneNode(true); + preferencesListFormatted.querySelectorAll('span')[0].textContent = + result[i + 1] + ': '; + preferencesListFormatted.querySelectorAll('span')[1].textContent = + result[i]; + $('preferences-list').appendChild(preferencesListFormatted); } - $('preferences-list').innerHTML = preferencesListFormatted; } function addCookie(result) { - const cookieFormatted = '<b>Value:</b> ' + result[0] + '\n' + - '<b>Created:</b> ' + (new Date(result[1])).toLocaleString(); - $('cookie-panel').innerHTML = cookieFormatted; + const cookieFormatted = $('cookie-template').content.cloneNode(true); + cookieFormatted.querySelectorAll('.result')[0].textContent = result[0]; + cookieFormatted.querySelectorAll('.result')[1].textContent = + (new Date(result[1])).toLocaleString(); + $('cookie-panel').appendChild(cookieFormatted); } function addSavedPasswords(result) { const resLength = result.length; - let savedPasswordFormatted = ""; for (let i = 0; i < resLength; i += 2) { - savedPasswordFormatted += "<div>" + result[i]; - if (result[i+1]) { - savedPasswordFormatted += " (GAIA password)"; - } else { - savedPasswordFormatted += " (Enterprise password)"; - } - savedPasswordFormatted += "</div>"; + const savedPasswordFormatted = document.createElement('div'); + const suffix = result[i + 1] ? 'GAIA password' : 'Enterprise password'; + savedPasswordFormatted.textContent = `${result[i]} (${suffix})`; + $('saved-passwords').appendChild(savedPasswordFormatted); } - - $('saved-passwords').innerHTML = savedPasswordFormatted; } function addDatabaseManagerInfo(result) { const resLength = result.length; - let preferencesListFormatted = ""; for (let i = 0; i < resLength; i += 2) { - preferencesListFormatted += "<div><b>" + result[i] + "</b>: " + - result[i + 1] + "</div>"; + const preferencesListFormatted = + $('result-template').content.cloneNode(true); + preferencesListFormatted.querySelectorAll('span')[0].textContent = + result[i] + ': '; + const value = result[i + 1]; + if (Array.isArray(value)) { + const blockQuote = document.createElement('blockquote'); + value.forEach(item => { + const div = document.createElement('div'); + div.textContent = item; + blockQuote.appendChild(div); + }); + preferencesListFormatted.querySelectorAll('span')[1].appendChild( + blockQuote); + } else { + preferencesListFormatted.querySelectorAll('span')[1].textContent = + value; + } + $('database-info-list').appendChild(preferencesListFormatted); } - $('database-info-list').innerHTML = preferencesListFormatted; } function addFullHashCacheInfo(result) { - $('full-hash-cache-info').innerHTML = result; + $('full-hash-cache-info').textContent = result; } function addSentClientDownloadRequestsInfo(result) { @@ -307,8 +324,9 @@ cr.define('safe_browsing', function() { } function addRTLookupExperimentEnabled(enabled) { - const enabledFormatted = '<b>RT Lookup Experiment Enabled:</b> ' + enabled; - $('rt-lookup-experiment-enabled').innerHTML = enabledFormatted; + const enabledFormatted = $('rt-lookup-template').content.cloneNode(true); + enabledFormatted.querySelector('#experiment-bool').textContent = enabled; + $('rt-lookup-experiment-enabled').appendChild(enabledFormatted); } function addLogMessage(result) { @@ -339,7 +357,8 @@ cr.define('safe_browsing', function() { cr.sendWithPromise('getReferrerChain', $('referrer-chain-url').value) .then((response) => { - $('referrer-chain-content').innerHTML = response; + $('referrer-chain-content').innerHTML = trustedTypes.emptyHTML; + $('referrer-chain-content').textContent = response; }); } diff --git a/chromium/components/safe_browsing/content/web_ui/safe_browsing_ui.cc b/chromium/components/safe_browsing/content/web_ui/safe_browsing_ui.cc index 8dd703da6b7..c04f24f0556 100644 --- a/chromium/components/safe_browsing/content/web_ui/safe_browsing_ui.cc +++ b/chromium/components/safe_browsing/content/web_ui/safe_browsing_ui.cc @@ -14,6 +14,7 @@ #include "base/base64url.h" #include "base/bind.h" #include "base/callback.h" +#include "base/i18n/number_formatting.h" #include "base/i18n/time_formatting.h" #include "base/json/json_string_value_serializer.h" #include "base/memory/ref_counted.h" @@ -21,9 +22,9 @@ #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" -#include "base/task/post_task.h" #include "base/time/time.h" #include "base/values.h" +#include "components/enterprise/common/proto/connectors.pb.h" #include "components/grit/components_resources.h" #include "components/grit/components_scaled_resources.h" #include "components/password_manager/core/browser/hash_password_manager.h" @@ -219,8 +220,8 @@ void WebUIInfoSingleton::LogMessage(const std::string& message) { base::Time timestamp = base::Time::Now(); log_messages_.push_back(std::make_pair(timestamp, message)); - base::PostTask(FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(&WebUIInfoSingleton::NotifyLogMessageListeners, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&WebUIInfoSingleton::NotifyLogMessageListeners, timestamp, message)); } @@ -271,6 +272,26 @@ void WebUIInfoSingleton::AddToDeepScanRequests( request.request_token(), deep_scan_requests_[request.request_token()]); } +void WebUIInfoSingleton::AddToDeepScanRequests( + const enterprise_connectors::ContentAnalysisRequest& request) { + if (!HasListener()) + return; + + // Only update the request time the first time we see a token. + if (deep_scan_requests_.find(request.request_token()) == + deep_scan_requests_.end()) { + deep_scan_requests_[request.request_token()].request_time = + base::Time::Now(); + } + + deep_scan_requests_[request.request_token()].content_analysis_request = + request; + + for (auto* webui_listener : webui_instances_) + webui_listener->NotifyDeepScanJsListener( + request.request_token(), deep_scan_requests_[request.request_token()]); +} + void WebUIInfoSingleton::AddToDeepScanResponses( const std::string& token, const std::string& status, @@ -286,6 +307,21 @@ void WebUIInfoSingleton::AddToDeepScanResponses( webui_listener->NotifyDeepScanJsListener(token, deep_scan_requests_[token]); } +void WebUIInfoSingleton::AddToDeepScanResponses( + const std::string& token, + const std::string& status, + const enterprise_connectors::ContentAnalysisResponse& response) { + if (!HasListener()) + return; + + deep_scan_requests_[token].response_time = base::Time::Now(); + deep_scan_requests_[token].response_status = status; + deep_scan_requests_[token].content_analysis_response = response; + + for (auto* webui_listener : webui_instances_) + webui_listener->NotifyDeepScanJsListener(token, deep_scan_requests_[token]); +} + void WebUIInfoSingleton::ClearDeepScans() { base::flat_map<std::string, DeepScanDebugData>().swap(deep_scan_requests_); } @@ -369,34 +405,33 @@ void AddStoreInfo(const DatabaseManagerInfo::DatabaseInfo::StoreInfo store_info, database_info_list->Append(base::Value("Unknown store")); } - std::string store_info_string = "<blockquote>"; + base::Value store_info_list(base::Value::Type::LIST); if (store_info.has_file_size_bytes()) { - store_info_string += - "Size (in bytes): " + std::to_string(store_info.file_size_bytes()) + - "<br>"; + store_info_list.Append( + "Size (in bytes): " + + base::UTF16ToUTF8(base::FormatNumber(store_info.file_size_bytes()))); } if (store_info.has_update_status()) { - store_info_string += - "Update status: " + std::to_string(store_info.update_status()) + "<br>"; + store_info_list.Append( + "Update status: " + + base::UTF16ToUTF8(base::FormatNumber(store_info.update_status()))); } if (store_info.has_last_apply_update_time_millis()) { - store_info_string += "Last update time: " + - UserReadableTimeFromMillisSinceEpoch( - store_info.last_apply_update_time_millis()) - .GetString() + - "<br>"; + store_info_list.Append("Last update time: " + + UserReadableTimeFromMillisSinceEpoch( + store_info.last_apply_update_time_millis()) + .GetString()); } if (store_info.has_checks_attempted()) { - store_info_string += "Number of database checks: " + - std::to_string(store_info.checks_attempted()) + "<br>"; + store_info_list.Append( + "Number of database checks: " + + base::UTF16ToUTF8(base::FormatNumber(store_info.checks_attempted()))); } - store_info_string += "</blockquote>"; - - database_info_list->Append(base::Value(store_info_string)); + database_info_list->Append(std::move(store_info_list)); } void AddDatabaseInfo(const DatabaseManagerInfo::DatabaseInfo database_info, @@ -1304,6 +1339,52 @@ base::Value SerializeReportingEvent(const base::Value& event) { } #if BUILDFLAG(FULL_SAFE_BROWSING) +std::string SerializeContentAnalysisRequest( + const enterprise_connectors::ContentAnalysisRequest& request) { + base::DictionaryValue request_dict; + + request_dict.SetKey("device_token", base::Value(request.device_token())); + request_dict.SetKey("fcm_notification_token", + base::Value(request.fcm_notification_token())); + switch (request.analysis_connector()) { + case enterprise_connectors::ANALYSIS_CONNECTOR_UNSPECIFIED: + request_dict.SetStringKey("analysis_connector", "UNSPECIFIED"); + break; + case enterprise_connectors::FILE_ATTACHED: + request_dict.SetStringKey("analysis_connector", "FILE_ATTACHED"); + break; + case enterprise_connectors::FILE_DOWNLOADED: + request_dict.SetStringKey("analysis_connector", "FILE_DOWNLOADED"); + break; + case enterprise_connectors::BULK_DATA_ENTRY: + request_dict.SetStringKey("analysis_connector", "BULK_DATA_ENTRY"); + break; + } + + if (request.has_request_data()) { + base::DictionaryValue request_data; + request_data.SetStringKey("url", request.request_data().url()); + request_data.SetStringKey("filename", request.request_data().filename()); + request_data.SetStringKey("digest", request.request_data().digest()); + // TODO(domfc): Improve this once csd is populated for this proto. + request_data.SetStringKey("csd", + request.request_data().csd().SerializeAsString()); + request_dict.SetKey("request_data", std::move(request_data)); + } + + base::ListValue tags; + for (const std::string& tag : request.tags()) + tags.Append(base::Value(tag)); + request_dict.SetKey("tags", std::move(tags)); + request_dict.SetKey("request_token", base::Value(request.request_token())); + + std::string request_serialized; + JSONStringValueSerializer serializer(&request_serialized); + serializer.set_pretty_print(true); + serializer.Serialize(request_dict); + return request_serialized; +} + std::string SerializeDeepScanningRequest( const DeepScanningClientRequest& request) { base::DictionaryValue request_dict; @@ -1362,6 +1443,68 @@ std::string SerializeDeepScanningRequest( return request_serialized; } +std::string SerializeContentAnalysisResponse( + const enterprise_connectors::ContentAnalysisResponse& response) { + base::DictionaryValue response_dict; + + response_dict.SetStringKey("token", response.request_token()); + + base::ListValue result_values; + for (const auto& result : response.results()) { + base::DictionaryValue result_value; + switch (result.status()) { + case enterprise_connectors::ContentAnalysisResponse::Result:: + STATUS_UNKNOWN: + result_value.SetStringKey("status", "STATUS_UNKNOWN"); + break; + case enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS: + result_value.SetStringKey("status", "SUCCESS"); + break; + case enterprise_connectors::ContentAnalysisResponse::Result::FAILURE: + result_value.SetStringKey("status", "FAILURE"); + break; + } + result_value.SetStringKey("tag", result.tag()); + + base::ListValue triggered_rules; + for (const auto& rule : result.triggered_rules()) { + base::DictionaryValue rule_value; + + switch (rule.action()) { + case enterprise_connectors::ContentAnalysisResponse::Result:: + TriggeredRule::ACTION_UNSPECIFIED: + rule_value.SetStringKey("action", "ACTION_UNSPECIFIED"); + break; + case enterprise_connectors::ContentAnalysisResponse::Result:: + TriggeredRule::REPORT_ONLY: + rule_value.SetStringKey("action", "REPORT_ONLY"); + break; + case enterprise_connectors::ContentAnalysisResponse::Result:: + TriggeredRule::WARN: + rule_value.SetStringKey("action", "WARN"); + break; + case enterprise_connectors::ContentAnalysisResponse::Result:: + TriggeredRule::BLOCK: + rule_value.SetStringKey("action", "BLOCK"); + break; + } + + rule_value.SetStringKey("rule_name", rule.rule_name()); + rule_value.SetStringKey("rule_id", rule.rule_id()); + triggered_rules.Append(std::move(rule_value)); + } + result_value.SetKey("triggered_rules", std::move(triggered_rules)); + result_values.Append(std::move(result_value)); + } + response_dict.SetKey("results", std::move(result_values)); + + std::string response_serialized; + JSONStringValueSerializer serializer(&response_serialized); + serializer.set_pretty_print(true); + serializer.Serialize(response_dict); + return response_serialized; +} + std::string SerializeDeepScanningResponse( const DeepScanningClientResponse& response) { base::DictionaryValue response_dict; @@ -1471,6 +1614,9 @@ base::Value SerializeDeepScanDebugData(const std::string& token, if (data.request.has_value()) { value.SetStringKey("request", SerializeDeepScanningRequest(data.request.value())); + } else if (data.content_analysis_request.has_value()) { + value.SetStringKey("request", SerializeContentAnalysisRequest( + data.content_analysis_request.value())); } if (!data.response_time.is_null()) { @@ -1484,6 +1630,9 @@ base::Value SerializeDeepScanDebugData(const std::string& token, if (data.response.has_value()) { value.SetStringKey("response", SerializeDeepScanningResponse(data.response.value())); + } else if (data.content_analysis_response.has_value()) { + value.SetStringKey("response", SerializeContentAnalysisResponse( + data.content_analysis_response.value())); } return std::move(value); diff --git a/chromium/components/safe_browsing/content/web_ui/safe_browsing_ui.h b/chromium/components/safe_browsing/content/web_ui/safe_browsing_ui.h index d7b296688be..8e10f4700ff 100644 --- a/chromium/components/safe_browsing/content/web_ui/safe_browsing_ui.h +++ b/chromium/components/safe_browsing/content/web_ui/safe_browsing_ui.h @@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/macros.h" +#include "components/enterprise/common/proto/connectors.pb.h" #include "components/safe_browsing/buildflags.h" #include "components/safe_browsing/core/browser/safe_browsing_network_context.h" #include "components/safe_browsing/core/proto/csd.pb.h" @@ -42,10 +43,14 @@ struct DeepScanDebugData { base::Time request_time; base::Optional<DeepScanningClientRequest> request; + base::Optional<enterprise_connectors::ContentAnalysisRequest> + content_analysis_request; base::Time response_time; std::string response_status; base::Optional<DeepScanningClientResponse> response; + base::Optional<enterprise_connectors::ContentAnalysisResponse> + content_analysis_response; }; #endif @@ -327,12 +332,18 @@ class WebUIInfoSingleton { // identifier that can be used in |AddToDeepScanResponses| to correlate a ping // and response. void AddToDeepScanRequests(const DeepScanningClientRequest& request); + void AddToDeepScanRequests( + const enterprise_connectors::ContentAnalysisRequest& request); // Add the new response to |deep_scan_requests_| and send it to all the open // chrome://safe-browsing tabs. void AddToDeepScanResponses(const std::string& token, const std::string& status, const DeepScanningClientResponse& response); + void AddToDeepScanResponses( + const std::string& token, + const std::string& status, + const enterprise_connectors::ContentAnalysisResponse& response); // Clear the list of deep scan requests and responses. void ClearDeepScans(); diff --git a/chromium/components/safe_browsing/core/BUILD.gn b/chromium/components/safe_browsing/core/BUILD.gn index 550f077ab5e..65ef314d0e7 100644 --- a/chromium/components/safe_browsing/core/BUILD.gn +++ b/chromium/components/safe_browsing/core/BUILD.gn @@ -16,7 +16,17 @@ static_library("features") { ] } +# Because csd.proto is included from a proto file outside its own directory, +# proto_in_dir is required so that import can follow the Chromium style guide +# that all imports start from the source root. +# +# However, due to the way that protoc generates C++ code, this directive is +# also required in all build targets for protos that import csd.proto, including +# the protos in the same directory. Those protos also need to import csd.proto +# using a path from the source root, otherwise they won't compile. + proto_library("csd_proto") { + proto_in_dir = "//" sources = [ "proto/csd.proto" ] } @@ -25,6 +35,7 @@ proto_library("webui_proto") { } proto_library("realtimeapi_proto") { + proto_in_dir = "//" sources = [ "proto/realtimeapi.proto" ] deps = [ ":csd_proto" ] } @@ -34,6 +45,7 @@ proto_library("webprotect_proto") { } proto_library("client_model_proto") { + proto_in_dir = "//" sources = [ "proto/client_model.proto" ] deps = [ ":csd_proto" ] } @@ -98,6 +110,7 @@ source_set("verdict_cache_manager_unittest") { ":realtimeapi_proto", ":verdict_cache_manager", "//base", + "//base/test:test_support", "//components/content_settings/core/browser", "//components/safe_browsing/core/common:test_support", "//components/sync_preferences:test_support", diff --git a/chromium/components/safe_browsing/core/browser/BUILD.gn b/chromium/components/safe_browsing/core/browser/BUILD.gn index f2d6ceef6a4..658c9328194 100644 --- a/chromium/components/safe_browsing/core/browser/BUILD.gn +++ b/chromium/components/safe_browsing/core/browser/BUILD.gn @@ -20,7 +20,7 @@ jumbo_source_set("browser") { "//components/safe_browsing/core/db:database_manager", "//components/safe_browsing/core/db:util", "//components/safe_browsing/core/realtime:policy_engine", - "//components/safe_browsing/core/realtime:url_lookup_service", + "//components/safe_browsing/core/realtime:url_lookup_service_base", "//components/safe_browsing/core/web_ui:constants", "//components/security_interstitials/core:unsafe_resource", "//net:extras", diff --git a/chromium/components/safe_browsing/core/browser/safe_browsing_token_fetcher.cc b/chromium/components/safe_browsing/core/browser/safe_browsing_token_fetcher.cc index f0c2b40af94..f2148fa26e0 100644 --- a/chromium/components/safe_browsing/core/browser/safe_browsing_token_fetcher.cc +++ b/chromium/components/safe_browsing/core/browser/safe_browsing_token_fetcher.cc @@ -24,7 +24,7 @@ namespace { const char kAPIScope[] = "https://www.googleapis.com/auth/chrome-safe-browsing"; -const int kTimeoutDelaySeconds = 5 * 60; +const int kTimeoutDelaySeconds = 1; } // namespace diff --git a/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc b/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc index 5d5875349f3..3c4861b3d9e 100644 --- a/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc +++ b/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc @@ -15,7 +15,7 @@ #include "components/safe_browsing/core/common/thread_utils.h" #include "components/safe_browsing/core/features.h" #include "components/safe_browsing/core/realtime/policy_engine.h" -#include "components/safe_browsing/core/realtime/url_lookup_service.h" +#include "components/safe_browsing/core/realtime/url_lookup_service_base.h" #include "components/safe_browsing/core/web_ui/constants.h" #include "components/security_interstitials/core/unsafe_resource.h" #include "mojo/public/cpp/bindings/pending_receiver.h" @@ -102,8 +102,9 @@ SafeBrowsingUrlCheckerImpl::SafeBrowsingUrlCheckerImpl( scoped_refptr<UrlCheckerDelegate> url_checker_delegate, const base::RepeatingCallback<content::WebContents*()>& web_contents_getter, bool real_time_lookup_enabled, - bool enhanced_protection_enabled, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service_on_ui) + bool can_rt_check_subresource_url, + bool can_check_db, + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service_on_ui) : headers_(headers), load_flags_(load_flags), resource_type_(static_cast<ResourceType>(resource_type)), @@ -112,9 +113,12 @@ SafeBrowsingUrlCheckerImpl::SafeBrowsingUrlCheckerImpl( url_checker_delegate_(std::move(url_checker_delegate)), database_manager_(url_checker_delegate_->GetDatabaseManager()), real_time_lookup_enabled_(real_time_lookup_enabled), - enhanced_protection_enabled_(enhanced_protection_enabled), + can_rt_check_subresource_url_(can_rt_check_subresource_url), + can_check_db_(can_check_db), url_lookup_service_on_ui_(url_lookup_service_on_ui) { DCHECK(!web_contents_getter_.is_null()); + DCHECK(!can_rt_check_subresource_url_ || real_time_lookup_enabled_); + DCHECK(real_time_lookup_enabled_ || can_check_db_); } SafeBrowsingUrlCheckerImpl::SafeBrowsingUrlCheckerImpl( @@ -127,7 +131,9 @@ SafeBrowsingUrlCheckerImpl::SafeBrowsingUrlCheckerImpl( web_state_getter_(web_state_getter), url_checker_delegate_(url_checker_delegate), database_manager_(url_checker_delegate_->GetDatabaseManager()), - real_time_lookup_enabled_(false) { + real_time_lookup_enabled_(false), + can_rt_check_subresource_url_(false), + can_check_db_(true) { DCHECK(!web_state_getter_.is_null()); } @@ -135,7 +141,9 @@ SafeBrowsingUrlCheckerImpl::~SafeBrowsingUrlCheckerImpl() { DCHECK(CurrentlyOnThread(ThreadID::IO)); if (state_ == STATE_CHECKING_URL) { - database_manager_->CancelCheck(this); + if (can_check_db_) { + database_manager_->CancelCheck(this); + } const GURL& url = urls_[next_index_].url; TRACE_EVENT_ASYNC_END1("safe_browsing", "CheckUrl", this, "url", url.spec()); @@ -269,7 +277,9 @@ void SafeBrowsingUrlCheckerImpl::OnUrlResult(const GURL& url, void SafeBrowsingUrlCheckerImpl::OnTimeout() { RecordCheckUrlTimeout(/*timed_out=*/true); - database_manager_->CancelCheck(this); + if (can_check_db_) { + database_manager_->CancelCheck(this); + } // Any pending callbacks on this URL check should be skipped. weak_factory_.InvalidateWeakPtrs(); @@ -362,7 +372,9 @@ void SafeBrowsingUrlCheckerImpl::ProcessUrls() { resource_type_); safe_synchronously = false; AsyncMatch match = - database_manager_->CheckUrlForHighConfidenceAllowlist(url, this); + can_check_db_ + ? database_manager_->CheckUrlForHighConfidenceAllowlist(url, this) + : AsyncMatch::NO_MATCH; UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.RT.LocalMatch.Result", match); switch (match) { case AsyncMatch::ASYNC: @@ -381,8 +393,8 @@ void SafeBrowsingUrlCheckerImpl::ProcessUrls() { /*did_match_allowlist=*/true)); break; case AsyncMatch::NO_MATCH: - // No match found locally. Queue the call to - // |OnCheckUrlForHighConfidenceAllowlist| to perform the full URL + // No match found locally or |can_check_db_| is false. Queue the call + // to |OnCheckUrlForHighConfidenceAllowlist| to perform the full URL // lookup. base::PostTask( FROM_HERE, CreateTaskTraits(ThreadID::IO), @@ -393,8 +405,13 @@ void SafeBrowsingUrlCheckerImpl::ProcessUrls() { break; } } else { - safe_synchronously = database_manager_->CheckBrowseUrl( - url, url_checker_delegate_->GetThreatTypes(), this); + // TODO(crbug.com/1085261): Add a metric to track how often + // |can_check_db_| is false. + safe_synchronously = + can_check_db_ + ? database_manager_->CheckBrowseUrl( + url, url_checker_delegate_->GetThreatTypes(), this) + : true; } if (safe_synchronously) { @@ -487,7 +504,7 @@ void SafeBrowsingUrlCheckerImpl::OnCheckUrlForHighConfidenceAllowlist( bool is_expected_resource_type = (ResourceType::kMainFrame == resource_type_) || ((ResourceType::kSubFrame == resource_type_) && - enhanced_protection_enabled_); + can_rt_check_subresource_url_); DCHECK(is_expected_resource_type); const GURL& url = urls_[next_index_].url; @@ -513,7 +530,7 @@ void SafeBrowsingUrlCheckerImpl::SetWebUIToken(int token) { void SafeBrowsingUrlCheckerImpl::StartLookupOnUIThread( base::WeakPtr<SafeBrowsingUrlCheckerImpl> weak_checker_on_io, const GURL& url, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service_on_ui, + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service_on_ui, scoped_refptr<SafeBrowsingDatabaseManager> database_manager) { DCHECK(CurrentlyOnThread(ThreadID::UI)); bool is_lookup_service_available = @@ -540,7 +557,8 @@ void SafeBrowsingUrlCheckerImpl::StartLookupOnUIThread( void SafeBrowsingUrlCheckerImpl::PerformHashBasedCheck(const GURL& url) { DCHECK(CurrentlyOnThread(ThreadID::IO)); - if (database_manager_->CheckBrowseUrl( + if (!can_check_db_ || + database_manager_->CheckBrowseUrl( url, url_checker_delegate_->GetThreatTypes(), this)) { // No match found in the local database. Safe to call |OnUrlResult| here // directly. diff --git a/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h b/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h index 6a76e9c06bc..af0ab7be5a3 100644 --- a/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h +++ b/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h @@ -35,7 +35,7 @@ enum class ResourceType; class UrlCheckerDelegate; -class RealTimeUrlLookupService; +class RealTimeUrlLookupServiceBase; // A SafeBrowsingUrlCheckerImpl instance is used to perform SafeBrowsing check // for a URL and its redirect URLs. It implements Mojo interface so that it can @@ -72,10 +72,10 @@ class SafeBrowsingUrlCheckerImpl : public mojom::SafeBrowsingUrlChecker, // indicates whether or not the profile has enabled real time URL lookups, as // computed by the RealTimePolicyEngine. This must be computed in advance, // since this class only exists on the IO thread. - // TODO(crbug.com/1050859): Move |real_time_lookup_enabled|, - // |cache_manager_on_ui| and |identity_manager_on_ui| into - // url_lookup_service_on_ui, and reduce the number of parameters in this - // constructor. + // |can_rt_check_subresource_url| indicates whether or not the profile has + // enabled real time URL lookups for subresource URLs. + // |real_time_lookup_enabled| must be true if |can_rt_check_subresource_url| + // is true. SafeBrowsingUrlCheckerImpl( const net::HttpRequestHeaders& headers, int load_flags, @@ -85,8 +85,9 @@ class SafeBrowsingUrlCheckerImpl : public mojom::SafeBrowsingUrlChecker, const base::RepeatingCallback<content::WebContents*()>& web_contents_getter, bool real_time_lookup_enabled, - bool enhanced_protection_enabled, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service_on_ui); + bool can_rt_check_subresource_url, + bool can_check_db, + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service_on_ui); // Constructor that takes only a ResourceType and a UrlCheckerDelegate, // omitting other arguments that never have non-default values on iOS. @@ -178,7 +179,7 @@ class SafeBrowsingUrlCheckerImpl : public mojom::SafeBrowsingUrlChecker, static void StartLookupOnUIThread( base::WeakPtr<SafeBrowsingUrlCheckerImpl> weak_checker_on_io, const GURL& url, - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service_on_ui, + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service_on_ui, scoped_refptr<SafeBrowsingDatabaseManager> database_manager); // Called when the |request| from the real-time lookup service is sent. @@ -255,12 +256,17 @@ class SafeBrowsingUrlCheckerImpl : public mojom::SafeBrowsingUrlChecker, // Whether real time lookup is enabled for this request. bool real_time_lookup_enabled_; - // Whether enhanced protection is enabled for this profile. - bool enhanced_protection_enabled_; + // Whether non mainframe url can be checked for this profile. + bool can_rt_check_subresource_url_; + + // Whether safe browsing database can be checked. It is set to false when + // enterprise real time URL lookup is enabled and safe browsing is disabled + // for this profile. + bool can_check_db_; // This object is used to perform real time url check. Can only be accessed in // UI thread. - base::WeakPtr<RealTimeUrlLookupService> url_lookup_service_on_ui_; + base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service_on_ui_; base::WeakPtrFactory<SafeBrowsingUrlCheckerImpl> weak_factory_{this}; diff --git a/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl_unittest.cc b/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl_unittest.cc index 7b01f2d8d18..92e1fe44b0c 100644 --- a/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl_unittest.cc +++ b/chromium/components/safe_browsing/core/browser/safe_browsing_url_checker_impl_unittest.cc @@ -163,7 +163,8 @@ class MockRealTimeUrlLookupService : public RealTimeUrlLookupService { /*pref_service=*/nullptr, ChromeUserPopulation::NOT_MANAGED, /*is_under_advanced_protection=*/false, - /*is_off_the_record=*/false) {} + /*is_off_the_record=*/false, + /*variations_service=*/nullptr) {} // Returns the threat type previously set by |SetThreatTypeForUrl|. It crashes // if the threat type for the |gurl| is not set in advance. void StartLookup(const GURL& gurl, @@ -221,7 +222,8 @@ class SafeBrowsingUrlCheckerTest : public PlatformTest { } std::unique_ptr<SafeBrowsingUrlCheckerImpl> CreateSafeBrowsingUrlChecker( - bool real_time_lookup_enabled) { + bool real_time_lookup_enabled, + bool can_check_safe_browsing_db) { base::MockCallback<base::RepeatingCallback<content::WebContents*()>> mock_web_contents_getter; return std::make_unique<SafeBrowsingUrlCheckerImpl>( @@ -229,7 +231,7 @@ class SafeBrowsingUrlCheckerTest : public PlatformTest { blink::mojom::ResourceType::kMainFrame, /*has_user_gesture=*/false, url_checker_delegate_, mock_web_contents_getter.Get(), real_time_lookup_enabled, - /*enhanced_protection_enabled=*/false, + /*can_rt_check_subresource_url=*/false, can_check_safe_browsing_db, real_time_lookup_enabled ? url_lookup_service_->GetWeakPtr() : nullptr); } @@ -241,8 +243,8 @@ class SafeBrowsingUrlCheckerTest : public PlatformTest { }; TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_SafeUrl) { - auto safe_browsing_url_checker = - CreateSafeBrowsingUrlChecker(/*real_time_lookup_enabled=*/false); + auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker( + /*real_time_lookup_enabled=*/false, /*can_check_safe_browsing_db=*/true); GURL url("https://example.test/"); database_manager_->SetThreatTypeForUrl(url, SB_THREAT_TYPE_SAFE, @@ -260,8 +262,8 @@ TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_SafeUrl) { } TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_DangerousUrl) { - auto safe_browsing_url_checker = - CreateSafeBrowsingUrlChecker(/*real_time_lookup_enabled=*/false); + auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker( + /*real_time_lookup_enabled=*/false, /*can_check_safe_browsing_db=*/true); GURL url("https://example.test/"); database_manager_->SetThreatTypeForUrl(url, SB_THREAT_TYPE_URL_PHISHING, @@ -279,8 +281,8 @@ TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_DangerousUrl) { } TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_RedirectUrlsSafe) { - auto safe_browsing_url_checker = - CreateSafeBrowsingUrlChecker(/*real_time_lookup_enabled=*/false); + auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker( + /*real_time_lookup_enabled=*/false, /*can_check_safe_browsing_db=*/true); GURL origin_url("https://example.test/"); database_manager_->SetThreatTypeForUrl(origin_url, SB_THREAT_TYPE_SAFE, @@ -311,8 +313,8 @@ TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_RedirectUrlsSafe) { TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_RedirectUrlsOriginDangerousRedirectSafe) { - auto safe_browsing_url_checker = - CreateSafeBrowsingUrlChecker(/*real_time_lookup_enabled=*/false); + auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker( + /*real_time_lookup_enabled=*/false, /*can_check_safe_browsing_db=*/true); GURL origin_url("https://example.test/"); database_manager_->SetThreatTypeForUrl( @@ -347,8 +349,8 @@ TEST_F(SafeBrowsingUrlCheckerTest, } TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_RealTimeEnabledAllowlistMatch) { - auto safe_browsing_url_checker = - CreateSafeBrowsingUrlChecker(/*real_time_lookup_enabled=*/true); + auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker( + /*real_time_lookup_enabled=*/true, /*can_check_safe_browsing_db=*/true); GURL url("https://example.test/"); database_manager_->SetAllowlistResultForUrl(url, AsyncMatch::MATCH); @@ -371,8 +373,8 @@ TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_RealTimeEnabledAllowlistMatch) { } TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_RealTimeEnabledSafeUrl) { - auto safe_browsing_url_checker = - CreateSafeBrowsingUrlChecker(/*real_time_lookup_enabled=*/true); + auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker( + /*real_time_lookup_enabled=*/true, /*can_check_safe_browsing_db=*/true); GURL url("https://example.test/"); database_manager_->SetAllowlistResultForUrl(url, AsyncMatch::NO_MATCH); @@ -390,4 +392,23 @@ TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_RealTimeEnabledSafeUrl) { task_environment_->RunUntilIdle(); } +TEST_F(SafeBrowsingUrlCheckerTest, + CheckUrl_RealTimeEnabledSafeBrowsingDisabled) { + auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker( + /*real_time_lookup_enabled=*/true, /*can_check_safe_browsing_db=*/false); + + GURL url("https://example.test/"); + url_lookup_service_->SetThreatTypeForUrl(url, SB_THREAT_TYPE_URL_PHISHING); + + base::MockCallback<SafeBrowsingUrlCheckerImpl::NativeCheckUrlCallback> + callback; + // Should still show blocking page because real time lookup is enabled. + EXPECT_CALL(*url_checker_delegate_, + StartDisplayingBlockingPageHelper(_, _, _, _, _)) + .Times(1); + safe_browsing_url_checker->CheckUrl(url, "GET", callback.Get()); + + task_environment_->RunUntilIdle(); +} + } // namespace safe_browsing diff --git a/chromium/components/safe_browsing/core/common/safe_browsing_prefs.cc b/chromium/components/safe_browsing/core/common/safe_browsing_prefs.cc index 2b86cf129b1..c66170bc2e6 100644 --- a/chromium/components/safe_browsing/core/common/safe_browsing_prefs.cc +++ b/chromium/components/safe_browsing/core/common/safe_browsing_prefs.cc @@ -19,16 +19,6 @@ namespace { -// The Extended Reporting pref that is currently active, used for UMA metrics. -// These values are written to logs. New enum values can be added, but -// existing enums must never be renumbered or deleted and reused. -enum ActiveExtendedReportingPref { - SBER1_PREF = 0, - SBER2_PREF = 1, - // New prefs must be added before MAX_SBER_PREF - MAX_SBER_PREF -}; - // Update the correct UMA metric based on which pref was changed and which UI // the change was made on. void RecordExtendedReportingPrefChanged( @@ -249,7 +239,7 @@ void SetExtendedReportingPrefAndMetric( RecordExtendedReportingPrefChanged(*prefs, location); } -void SetExtendedReportingPref(PrefService* prefs, bool value) { +void SetExtendedReportingPrefForTests(PrefService* prefs, bool value) { prefs->SetBoolean(prefs::kSafeBrowsingScoutReportingEnabled, value); } @@ -260,67 +250,6 @@ void SetEnhancedProtectionPref(PrefService* prefs, bool value) { prefs->SetBoolean(prefs::kSafeBrowsingEnhanced, value); } -void UpdateMetricsAfterSecurityInterstitial(const PrefService& prefs, - bool on_show_pref_existed, - bool on_show_pref_value) { - const bool cur_pref_value = IsExtendedReportingEnabled(prefs); - - if (!on_show_pref_existed) { - if (!ExtendedReportingPrefExists(prefs)) { - // User seeing pref for the first time, didn't touch the checkbox (left it - // unchecked). - UMA_HISTOGRAM_ENUMERATION( - "SafeBrowsing.Pref.Scout.Decision.First_LeftUnchecked", SBER2_PREF, - MAX_SBER_PREF); - return; - } - - // Pref currently exists so user did something to the checkbox - if (cur_pref_value) { - // User turned the pref on. - UMA_HISTOGRAM_ENUMERATION( - "SafeBrowsing.Pref.Scout.Decision.First_Enabled", SBER2_PREF, - MAX_SBER_PREF); - return; - } - - // Otherwise, user turned the pref off, but because it didn't exist when - // the interstitial was first shown, they must have turned it on and then - // off before the interstitial was closed. - UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.Pref.Scout.Decision.First_Disabled", - SBER2_PREF, MAX_SBER_PREF); - return; - } - - // At this point, the pref existed when the interstitial was shown so this is - // a repeat appearance of the opt-in. Existence can't be removed during an - // interstitial so no need to check whether the pref currently exists. - if (on_show_pref_value && cur_pref_value) { - // User left the pref on. - UMA_HISTOGRAM_ENUMERATION( - "SafeBrowsing.Pref.Scout.Decision.Repeat_LeftEnabled", SBER2_PREF, - MAX_SBER_PREF); - return; - } else if (on_show_pref_value && !cur_pref_value) { - // User turned the pref off. - UMA_HISTOGRAM_ENUMERATION( - "SafeBrowsing.Pref.Scout.Decision.Repeat_Disabled", SBER2_PREF, - MAX_SBER_PREF); - return; - } else if (!on_show_pref_value && cur_pref_value) { - // User turned the pref on. - UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.Pref.Scout.Decision.Repeat_Enabled", - SBER2_PREF, MAX_SBER_PREF); - return; - } else { - // Both on_show and cur values are false - user left the pref off. - UMA_HISTOGRAM_ENUMERATION( - "SafeBrowsing.Pref.Scout.Decision.Repeat_LeftDisabled", SBER2_PREF, - MAX_SBER_PREF); - return; - } -} - void UpdatePrefsBeforeSecurityInterstitial(PrefService* prefs) { // Remember that this user saw an interstitial. prefs->SetBoolean(prefs::kSafeBrowsingSawInterstitialScoutReporting, true); diff --git a/chromium/components/safe_browsing/core/common/safe_browsing_prefs.h b/chromium/components/safe_browsing/core/common/safe_browsing_prefs.h index 656ee9bc2ec..f0dac127bc6 100644 --- a/chromium/components/safe_browsing/core/common/safe_browsing_prefs.h +++ b/chromium/components/safe_browsing/core/common/safe_browsing_prefs.h @@ -171,7 +171,6 @@ enum PasswordProtectionTrigger { // Password protection is off. PASSWORD_PROTECTION_OFF = 0, // Password protection triggered by password reuse event. - // Not used for now. PASSWORD_REUSE = 1, // Password protection triggered by password reuse event on phishing page. PHISHING_REUSE = 2, @@ -294,7 +293,7 @@ void SetExtendedReportingPrefAndMetric(PrefService* prefs, ExtendedReportingOptInLocation location); // This variant is used to simplify test code by omitting the location. -void SetExtendedReportingPref(PrefService* prefs, bool value); +void SetExtendedReportingPrefForTests(PrefService* prefs, bool value); // Sets the currently active Safe Browsing Enhanced Protection to the specified // value. diff --git a/chromium/components/safe_browsing/core/common/safe_browsing_prefs_unittest.cc b/chromium/components/safe_browsing/core/common/safe_browsing_prefs_unittest.cc index 59ee1f19b28..924977760c2 100644 --- a/chromium/components/safe_browsing/core/common/safe_browsing_prefs_unittest.cc +++ b/chromium/components/safe_browsing/core/common/safe_browsing_prefs_unittest.cc @@ -127,18 +127,14 @@ TEST_F(SafeBrowsingPrefsTest, EnhancedProtection) { EXPECT_FALSE(IsEnhancedProtectionEnabled(prefs_)); SetEnhancedProtectionPref(&prefs_, true); - // If experiment is not on, the pref is not turned on. - EXPECT_FALSE(IsEnhancedProtectionEnabled(prefs_)); { base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitAndEnableFeature( - safe_browsing::kEnhancedProtection); + scoped_feature_list.InitAndEnableFeature(kEnhancedProtection); EXPECT_TRUE(IsEnhancedProtectionEnabled(prefs_)); } { base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitAndEnableFeature( - safe_browsing::kEnhancedProtection); + scoped_feature_list.InitAndEnableFeature(kEnhancedProtection); prefs_.SetBoolean(prefs::kSafeBrowsingEnabled, false); EXPECT_FALSE(IsEnhancedProtectionEnabled(prefs_)); } diff --git a/chromium/components/safe_browsing/core/db/v4_local_database_manager.cc b/chromium/components/safe_browsing/core/db/v4_local_database_manager.cc index 4fbf277960b..94b469c5a2f 100644 --- a/chromium/components/safe_browsing/core/db/v4_local_database_manager.cc +++ b/chromium/components/safe_browsing/core/db/v4_local_database_manager.cc @@ -652,8 +652,7 @@ void V4LocalDatabaseManager::DeleteUnusedStoreFiles() { continue; } task_runner_->PostTask( - FROM_HERE, base::BindOnce(base::IgnoreResult(&base::DeleteFile), - store_path, false /* recursive */)); + FROM_HERE, base::BindOnce(base::GetDeleteFileCallback(), store_path)); } else { NOTREACHED() << "Trying to delete a store file that's in use: " << store_filename_to_delete; diff --git a/chromium/components/safe_browsing/core/db/v4_protocol_manager_util_unittest.cc b/chromium/components/safe_browsing/core/db/v4_protocol_manager_util_unittest.cc index 7b6cbb1b076..b0073821500 100644 --- a/chromium/components/safe_browsing/core/db/v4_protocol_manager_util_unittest.cc +++ b/chromium/components/safe_browsing/core/db/v4_protocol_manager_util_unittest.cc @@ -7,6 +7,7 @@ #include <vector> #include "base/base64.h" +#include "base/logging.h" #include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" diff --git a/chromium/components/safe_browsing/core/db/v4_store.cc b/chromium/components/safe_browsing/core/db/v4_store.cc index 2d12ab5a29c..43a59cfdc80 100644 --- a/chromium/components/safe_browsing/core/db/v4_store.cc +++ b/chromium/components/safe_browsing/core/db/v4_store.cc @@ -10,6 +10,7 @@ #include "base/base64.h" #include "base/bind.h" #include "base/files/file_util.h" +#include "base/logging.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/stl_util.h" @@ -64,43 +65,17 @@ void RecordEnumWithAndWithoutSuffix(const std::string& metric, int32_t value, int32_t maximum, const base::FilePath& file_path) { - // The histograms below are an expansion of the UMA_HISTOGRAM_ENUMERATION - // macro adapted to allow for a dynamically suffixed histogram name. - // Note: The factory creates and owns the histogram. - base::HistogramBase* histogram = base::LinearHistogram::FactoryGet( - metric + kResult, 1, maximum, maximum + 1, - base::HistogramBase::kUmaTargetedHistogramFlag); - if (histogram) { - histogram->Add(value); - } - + base::UmaHistogramExactLinear(metric + kResult, value, maximum); std::string suffix = GetUmaSuffixForStore(file_path); - base::HistogramBase* histogram_suffix = base::LinearHistogram::FactoryGet( - metric + kResult + suffix, 1, maximum, maximum + 1, - base::HistogramBase::kUmaTargetedHistogramFlag); - if (histogram_suffix) { - histogram_suffix->Add(value); - } + base::UmaHistogramExactLinear(metric + kResult + suffix, value, maximum); } void RecordBooleanWithAndWithoutSuffix(const std::string& metric, bool value, const base::FilePath& file_path) { - // The histograms below are an expansion of the UMA_HISTOGRAM_BOOLEAN - // macro adapted to allow for a dynamically suffixed histogram name. - // Note: The factory creates and owns the histogram. - base::HistogramBase* histogram = base::BooleanHistogram::FactoryGet( - metric, base::HistogramBase::kUmaTargetedHistogramFlag); - if (histogram) { - histogram->Add(value); - } - + base::UmaHistogramBoolean(metric, value); std::string suffix = GetUmaSuffixForStore(file_path); - base::HistogramBase* histogram_suffix = base::BooleanHistogram::FactoryGet( - metric + suffix, base::HistogramBase::kUmaTargetedHistogramFlag); - if (histogram_suffix) { - histogram_suffix->Add(value); - } + base::UmaHistogramBoolean(metric + suffix, value); } void RecordCountWithAndWithoutSuffix(const std::string& metric, @@ -841,14 +816,8 @@ bool V4Store::VerifyChecksum() { int64_t V4Store::RecordAndReturnFileSize(const std::string& base_metric) { std::string suffix = GetUmaSuffixForStore(store_path_); - // Histogram properties as in UMA_HISTOGRAM_COUNTS_1M macro. - base::HistogramBase* histogram = base::Histogram::FactoryGet( - base_metric + suffix, 1, 1000000, 50, - base::HistogramBase::kUmaTargetedHistogramFlag); - if (histogram) { - const int64_t file_size_kilobytes = file_size_ / 1024; - histogram->Add(file_size_kilobytes); - } + const int64_t file_size_kilobytes = file_size_ / 1024; + base::UmaHistogramCounts1M(base_metric + suffix, file_size_kilobytes); return file_size_; } diff --git a/chromium/components/safe_browsing/core/db/v4_store_fuzzer.cc b/chromium/components/safe_browsing/core/db/v4_store_fuzzer.cc index 01ceb181c5e..fd7acd7a092 100644 --- a/chromium/components/safe_browsing/core/db/v4_store_fuzzer.cc +++ b/chromium/components/safe_browsing/core/db/v4_store_fuzzer.cc @@ -7,6 +7,7 @@ #include <string> #include "base/files/file_path.h" +#include "base/logging.h" #include "base/test/test_simple_task_runner.h" #include "components/safe_browsing/core/db/v4_protocol_manager_util.h" #include "components/safe_browsing/core/db/v4_store.h" diff --git a/chromium/components/safe_browsing/core/db/v4_store_unittest.cc b/chromium/components/safe_browsing/core/db/v4_store_unittest.cc index 372a2f08eb2..86ad27613de 100644 --- a/chromium/components/safe_browsing/core/db/v4_store_unittest.cc +++ b/chromium/components/safe_browsing/core/db/v4_store_unittest.cc @@ -8,6 +8,7 @@ #include "base/bind.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" +#include "base/logging.h" #include "base/run_loop.h" #include "base/test/test_simple_task_runner.h" #include "base/time/time.h" diff --git a/chromium/components/safe_browsing/core/db/v4_update_protocol_manager.cc b/chromium/components/safe_browsing/core/db/v4_update_protocol_manager.cc index c36864b66c6..4eeb85f7ea4 100644 --- a/chromium/components/safe_browsing/core/db/v4_update_protocol_manager.cc +++ b/chromium/components/safe_browsing/core/db/v4_update_protocol_manager.cc @@ -213,6 +213,7 @@ void V4UpdateProtocolManager::ScheduleNextUpdateAfterInterval( DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(interval >= base::TimeDelta()); + next_update_time_ = Time::Now() + interval; // Unschedule any current timer. update_timer_.Stop(); update_timer_.Start(FROM_HERE, interval, this, @@ -441,11 +442,11 @@ void V4UpdateProtocolManager::CollectUpdateInfo( if (last_response_time_.ToJavaTime()) { update_info->set_last_update_time_millis(last_response_time_.ToJavaTime()); + } - // We should only find the next update if the last_response is valid. - base::Time next_update = last_response_time_ + next_update_interval_; - if (next_update.ToJavaTime()) - update_info->set_next_update_time_millis(next_update.ToJavaTime()); + if (next_update_time_) { + update_info->set_next_update_time_millis( + next_update_time_.value().ToJavaTime()); } } diff --git a/chromium/components/safe_browsing/core/db/v4_update_protocol_manager.h b/chromium/components/safe_browsing/core/db/v4_update_protocol_manager.h index 068b245469e..79c39d0996e 100644 --- a/chromium/components/safe_browsing/core/db/v4_update_protocol_manager.h +++ b/chromium/components/safe_browsing/core/db/v4_update_protocol_manager.h @@ -18,6 +18,7 @@ #include "base/gtest_prod_util.h" #include "base/macros.h" +#include "base/optional.h" #include "base/sequence_checker.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -178,6 +179,10 @@ class V4UpdateProtocolManager { // The server can set it by setting the minimum_wait_duration. base::TimeDelta next_update_interval_; + // The time when the next update is scheduled to be requested. This is valid + // only when |update_timer_| is running. + base::Optional<base::Time> next_update_time_ = base::nullopt; + // The config of the client making Pver4 requests. const V4ProtocolConfig config_; diff --git a/chromium/components/safe_browsing/core/features.cc b/chromium/components/safe_browsing/core/features.cc index 45ff1fd627a..0417501fcfa 100644 --- a/chromium/components/safe_browsing/core/features.cc +++ b/chromium/components/safe_browsing/core/features.cc @@ -41,38 +41,42 @@ const base::Feature kDelayedWarnings{"SafeBrowsingDelayedWarnings", const base::Feature kDownloadRequestWithToken{ "SafeBrowsingDownloadRequestWithToken", base::FEATURE_ENABLED_BY_DEFAULT}; -const base::Feature kEnhancedProtection{"SafeBrowsingEnhancedProtection", - base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kEnhancedProtection { + "SafeBrowsingEnhancedProtection", +#if BUILDFLAG(FULL_SAFE_BROWSING) + base::FEATURE_ENABLED_BY_DEFAULT +#else + base::FEATURE_DISABLED_BY_DEFAULT +#endif +}; const base::Feature kMalwareScanEnabled{"SafeBrowsingMalwareScanEnabled", base::FEATURE_ENABLED_BY_DEFAULT}; const base::Feature kPasswordProtectionForSavedPasswords{ "SafeBrowsingPasswordProtectionForSavedPasswords", - base::FEATURE_ENABLED_BY_DEFAULT}; + base::FEATURE_DISABLED_BY_DEFAULT}; const base::Feature kPasswordProtectionShowDomainsForSavedPasswords{ "SafeBrowsingPasswordProtectionShowDomainsForSavedPasswords", - base::FEATURE_ENABLED_BY_DEFAULT}; + base::FEATURE_DISABLED_BY_DEFAULT}; -#if BUILDFLAG(FULL_SAFE_BROWSING) -const base::Feature kPasswordProtectionForSignedInUsers{ - "SafeBrowsingPasswordProtectionForSignedInUsers", - base::FEATURE_ENABLED_BY_DEFAULT}; -#else const base::Feature kPasswordProtectionForSignedInUsers{ "SafeBrowsingPasswordProtectionForSignedInUsers", base::FEATURE_DISABLED_BY_DEFAULT}; -#endif const base::Feature kPromptAppForDeepScanning{ "SafeBrowsingPromptAppForDeepScanning", base::FEATURE_DISABLED_BY_DEFAULT}; const base::Feature kRealTimeUrlLookupEnabled{ - "SafeBrowsingRealTimeUrlLookupEnabled", base::FEATURE_DISABLED_BY_DEFAULT}; + "SafeBrowsingRealTimeUrlLookupEnabled", base::FEATURE_ENABLED_BY_DEFAULT}; const base::Feature kRealTimeUrlLookupEnabledForAllAndroidDevices{ "SafeBrowsingRealTimeUrlLookupEnabledForAllAndroidDevices", + base::FEATURE_ENABLED_BY_DEFAULT}; + +const base::Feature kRealTimeUrlLookupEnabledForEnterprise{ + "SafeBrowsingRealTimeUrlLookupEnabledForEnterprise", base::FEATURE_DISABLED_BY_DEFAULT}; const base::Feature kRealTimeUrlLookupEnabledForEP{ @@ -137,6 +141,7 @@ constexpr struct { {&kRealTimeUrlLookupEnabled, true}, {&kRealTimeUrlLookupEnabledForAllAndroidDevices, true}, {&kRealTimeUrlLookupEnabledForEP, true}, + {&kRealTimeUrlLookupEnabledForEnterprise, true}, {&kRealTimeUrlLookupEnabledForEPWithToken, true}, {&kRealTimeUrlLookupEnabledWithToken, true}, {&kRealTimeUrlLookupNonMainframeEnabledForEP, true}, diff --git a/chromium/components/safe_browsing/core/features.h b/chromium/components/safe_browsing/core/features.h index 805ab14cc6d..25c49599c11 100644 --- a/chromium/components/safe_browsing/core/features.h +++ b/chromium/components/safe_browsing/core/features.h @@ -84,6 +84,12 @@ extern const base::Feature kRealTimeUrlLookupEnabled; // This flag is in effect only if |kRealTimeUrlLookupEnabled| is true. extern const base::Feature kRealTimeUrlLookupEnabledForAllAndroidDevices; +// Controls whether to do real time URL lookup for enterprise users. If both +// this feature and the enterprise policies are enabled, the enterprise real +// time URL lookup will be enabled and the consumer real time URL lookup will be +// disabled. +extern const base::Feature kRealTimeUrlLookupEnabledForEnterprise; + // Controls whether the real time URL lookup is enabled for Enhanced Protection // users. extern const base::Feature kRealTimeUrlLookupEnabledForEP; diff --git a/chromium/components/safe_browsing/core/proto/client_model.proto b/chromium/components/safe_browsing/core/proto/client_model.proto index ab507222548..6f6d975cba2 100644 --- a/chromium/components/safe_browsing/core/proto/client_model.proto +++ b/chromium/components/safe_browsing/core/proto/client_model.proto @@ -18,7 +18,7 @@ option optimize_for = LITE_RUNTIME; package safe_browsing; -import 'csd.proto'; +import "components/safe_browsing/core/proto/csd.proto"; // This protocol buffer represents a machine learning model that is used in // client-side phishing detection (in Chrome). The client extracts a set diff --git a/chromium/components/safe_browsing/core/proto/realtimeapi.proto b/chromium/components/safe_browsing/core/proto/realtimeapi.proto index 99661be049d..32ca4e2cebd 100644 --- a/chromium/components/safe_browsing/core/proto/realtimeapi.proto +++ b/chromium/components/safe_browsing/core/proto/realtimeapi.proto @@ -11,7 +11,7 @@ option optimize_for = LITE_RUNTIME; package safe_browsing; -import "csd.proto"; +import "components/safe_browsing/core/proto/csd.proto"; message RTLookupRequest { // If applicable, URL submitted from Chrome. diff --git a/chromium/components/safe_browsing/core/proto/webprotect.proto b/chromium/components/safe_browsing/core/proto/webprotect.proto index 7c1e4280f08..283f6ba07e6 100644 --- a/chromium/components/safe_browsing/core/proto/webprotect.proto +++ b/chromium/components/safe_browsing/core/proto/webprotect.proto @@ -69,6 +69,9 @@ message DeepScanningClientRequest { message MalwareDeepScanningVerdict { reserved 2; + // These values are persisted to UMA logs in the + // SafeBrowsingMalwareDeepScanningVerdict enum. Entries should not be + // renumbered and numeric values should never be reused. enum Verdict { VERDICT_UNSPECIFIED = 0; CLEAN = 1; diff --git a/chromium/components/safe_browsing/core/realtime/BUILD.gn b/chromium/components/safe_browsing/core/realtime/BUILD.gn index cf3a9933cb4..f1aeb3530f9 100644 --- a/chromium/components/safe_browsing/core/realtime/BUILD.gn +++ b/chromium/components/safe_browsing/core/realtime/BUILD.gn @@ -2,6 +2,27 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +static_library("policy_engine") { + sources = [ + "policy_engine.cc", + "policy_engine.h", + ] + + deps = [ + "//base:base", + "//components/prefs", + "//components/safe_browsing/core:features", + "//components/safe_browsing/core:realtimeapi_proto", + "//components/safe_browsing/core/common", + "//components/safe_browsing/core/common:safe_browsing_prefs", + "//components/signin/public/identity_manager", + "//components/sync", + "//components/unified_consent", + "//components/user_prefs", + "//components/variations/service", + ] +} + static_library("url_lookup_service") { sources = [ "url_lookup_service.cc", @@ -10,6 +31,7 @@ static_library("url_lookup_service") { deps = [ ":policy_engine", + ":url_lookup_service_base", "//base:base", "//components/prefs", "//components/safe_browsing:buildflags", @@ -22,28 +44,30 @@ static_library("url_lookup_service") { "//components/safe_browsing/core/db:v4_protocol_manager_util", "//components/signin/public/identity_manager", "//components/sync", + "//components/variations/service:service", "//services/network/public/cpp:cpp", "//url:url", ] } -static_library("policy_engine") { +static_library("url_lookup_service_base") { sources = [ - "policy_engine.cc", - "policy_engine.h", + "url_lookup_service_base.cc", + "url_lookup_service_base.h", ] deps = [ "//base:base", "//components/prefs", - "//components/safe_browsing/core:features", + "//components/safe_browsing:buildflags", "//components/safe_browsing/core:realtimeapi_proto", - "//components/safe_browsing/core/common", + "//components/safe_browsing/core:verdict_cache_manager", "//components/safe_browsing/core/common:safe_browsing_prefs", + "//components/safe_browsing/core/common:thread_utils", + "//components/safe_browsing/core/db:v4_protocol_manager_util", "//components/signin/public/identity_manager", - "//components/sync", - "//components/unified_consent", - "//components/user_prefs", + "//services/network/public/cpp:cpp", + "//url:url", ] } @@ -69,8 +93,10 @@ source_set("unit_tests") { "//components/sync_preferences:test_support", "//components/unified_consent", "//components/user_prefs", + "//components/variations/service:service", "//services/network:test_support", "//services/network/public/cpp:cpp", "//testing/gtest", + "//third_party/googletest:gmock", ] } diff --git a/chromium/components/safe_browsing/core/realtime/policy_engine.cc b/chromium/components/safe_browsing/core/realtime/policy_engine.cc index 4493293cac0..1b5e90cc625 100644 --- a/chromium/components/safe_browsing/core/realtime/policy_engine.cc +++ b/chromium/components/safe_browsing/core/realtime/policy_engine.cc @@ -6,6 +6,7 @@ #include "base/feature_list.h" #include "base/metrics/histogram_macros.h" +#include "base/stl_util.h" #include "build/build_config.h" #include "components/prefs/pref_service.h" #include "components/safe_browsing/core/common/safe_browsing_prefs.h" @@ -20,6 +21,7 @@ #include "components/sync/driver/sync_user_settings.h" #include "components/unified_consent/pref_names.h" #include "components/user_prefs/user_prefs.h" +#include "components/variations/service/variations_service.h" #if defined(OS_ANDROID) #include "base/metrics/field_trial_params.h" @@ -28,11 +30,27 @@ namespace safe_browsing { +namespace { + +// This is a list of countries where real-time checks need to be disabled. +static const std::vector<std::string> GetExcludedCountries() { + // The endpoint isn't available + return {"cn"}; +} + +} // namespace + #if defined(OS_ANDROID) const int kDefaultMemoryThresholdMb = 4096; #endif // static +bool RealTimePolicyEngine::IsInExcludedCountry( + const std::string& country_code) { + return base::Contains(GetExcludedCountries(), country_code); +} + +// static bool RealTimePolicyEngine::IsUrlLookupEnabled() { if (!base::FeatureList::IsEnabled(kRealTimeUrlLookupEnabled)) return false; @@ -85,11 +103,18 @@ bool RealTimePolicyEngine::IsPrimaryAccountSignedIn( } // static -bool RealTimePolicyEngine::CanPerformFullURLLookup(PrefService* pref_service, - bool is_off_the_record) { +bool RealTimePolicyEngine::CanPerformFullURLLookup( + PrefService* pref_service, + bool is_off_the_record, + variations::VariationsService* variations_service) { if (is_off_the_record) return false; + // |variations_service| can be nullptr in tests. + if (variations_service && + IsInExcludedCountry(variations_service->GetStoredPermanentCountry())) + return false; + if (IsUrlLookupEnabledForEp() && IsUserEpOptedIn(pref_service)) return true; @@ -101,8 +126,10 @@ bool RealTimePolicyEngine::CanPerformFullURLLookupWithToken( PrefService* pref_service, bool is_off_the_record, syncer::SyncService* sync_service, - signin::IdentityManager* identity_manager) { - if (!CanPerformFullURLLookup(pref_service, is_off_the_record)) { + signin::IdentityManager* identity_manager, + variations::VariationsService* variations_service) { + if (!CanPerformFullURLLookup(pref_service, is_off_the_record, + variations_service)) { return false; } @@ -131,15 +158,36 @@ bool RealTimePolicyEngine::CanPerformFullURLLookupWithToken( } // static +bool RealTimePolicyEngine::CanPerformEnterpriseFullURLLookup( + bool has_valid_dm_token, + bool is_off_the_record) { + if (is_off_the_record) { + return false; + } + + if (!base::FeatureList::IsEnabled(kRealTimeUrlLookupEnabledForEnterprise)) { + return false; + } + + if (!has_valid_dm_token) { + return false; + } + + // TODO(crbug.com/1085261): Check the enterprise real time URL check policy. + return false; +} + +// static bool RealTimePolicyEngine::CanPerformFullURLLookupForResourceType( ResourceType resource_type, - bool enhanced_protection_enabled) { + bool can_rt_check_subresource_url) { UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.RT.ResourceTypes.Requested", resource_type); if (resource_type == ResourceType::kMainFrame) { return true; } - if (resource_type == ResourceType::kSubFrame && enhanced_protection_enabled && + if (resource_type == ResourceType::kSubFrame && + can_rt_check_subresource_url && base::FeatureList::IsEnabled( kRealTimeUrlLookupNonMainframeEnabledForEP)) { return true; diff --git a/chromium/components/safe_browsing/core/realtime/policy_engine.h b/chromium/components/safe_browsing/core/realtime/policy_engine.h index 93e2ce208a2..f6150432f01 100644 --- a/chromium/components/safe_browsing/core/realtime/policy_engine.h +++ b/chromium/components/safe_browsing/core/realtime/policy_engine.h @@ -5,6 +5,8 @@ #ifndef COMPONENTS_SAFE_BROWSING_CORE_REALTIME_POLICY_ENGINE_H_ #define COMPONENTS_SAFE_BROWSING_CORE_REALTIME_POLICY_ENGINE_H_ +#include <string> + #include "build/build_config.h" class PrefService; @@ -17,6 +19,10 @@ namespace signin { class IdentityManager; } +namespace variations { +class VariationsService; +} + namespace safe_browsing { enum class ResourceType; @@ -39,16 +45,20 @@ class RealTimePolicyEngine { RealTimePolicyEngine() = delete; ~RealTimePolicyEngine() = delete; - // Return true if full URL lookups are enabled for |resource_type|. + // Return true if full URL lookups are enabled for |resource_type|. If + // |can_rt_check_subresource_url| is set to false, return true only if + // |resource_type| is |kMainFrame|. static bool CanPerformFullURLLookupForResourceType( ResourceType resource_type, - bool enhanced_protection_enabled); + bool can_rt_check_subresource_url); // Return true if the feature to enable full URL lookups is enabled and the // allowlist fetch is enabled for the profile represented by // |pref_service|. - static bool CanPerformFullURLLookup(PrefService* pref_service, - bool is_off_the_record); + static bool CanPerformFullURLLookup( + PrefService* pref_service, + bool is_off_the_record, + variations::VariationsService* variations_service); // Return true if the OAuth token should be associated with the URL lookup // pings. @@ -56,12 +66,18 @@ class RealTimePolicyEngine { PrefService* pref_service, bool is_off_the_record, syncer::SyncService* sync_service, - signin::IdentityManager* identity_manager); + signin::IdentityManager* identity_manager, + variations::VariationsService* variations_service); + + static bool CanPerformEnterpriseFullURLLookup(bool has_valid_dm_token, + bool is_off_the_record); friend class SafeBrowsingService; friend class SafeBrowsingUIHandler; private: + static bool IsInExcludedCountry(const std::string& country_code); + // Is the feature to perform real-time URL lookup enabled? static bool IsUrlLookupEnabled(); diff --git a/chromium/components/safe_browsing/core/realtime/policy_engine_unittest.cc b/chromium/components/safe_browsing/core/realtime/policy_engine_unittest.cc index 668cff6d0b7..17bd531707b 100644 --- a/chromium/components/safe_browsing/core/realtime/policy_engine_unittest.cc +++ b/chromium/components/safe_browsing/core/realtime/policy_engine_unittest.cc @@ -40,8 +40,8 @@ class RealTimePolicyEngineTest : public PlatformTest { } bool CanPerformFullURLLookup(bool is_off_the_record) { - return RealTimePolicyEngine::CanPerformFullURLLookup(&pref_service_, - is_off_the_record); + return RealTimePolicyEngine::CanPerformFullURLLookup( + &pref_service_, is_off_the_record, /*variations_service=*/nullptr); } bool CanPerformFullURLLookupWithToken( @@ -49,7 +49,18 @@ class RealTimePolicyEngineTest : public PlatformTest { syncer::SyncService* sync_service, signin::IdentityManager* identity_manager) { return RealTimePolicyEngine::CanPerformFullURLLookupWithToken( - &pref_service_, is_off_the_record, sync_service, identity_manager); + &pref_service_, is_off_the_record, sync_service, identity_manager, + /*variations_service=*/nullptr); + } + + bool CanPerformEnterpriseFullURLLookup(bool has_valid_dm_token, + bool is_off_the_record) { + return RealTimePolicyEngine::CanPerformEnterpriseFullURLLookup( + has_valid_dm_token, is_off_the_record); + } + + bool IsInExcludedCountry(const std::string& country_code) { + return RealTimePolicyEngine::IsInExcludedCountry(country_code); } std::unique_ptr<base::test::TaskEnvironment> task_environment_; @@ -84,7 +95,8 @@ TEST_F(RealTimePolicyEngineTest, TestCanPerformFullURLLookup_SmallMemorySize) { {{kRealTimeUrlLookupMemoryThresholdMb, base::NumberToString( memory_size_threshold)}}}}, - /* disabled_features */ {}); + /* disabled_features */ { + {kRealTimeUrlLookupEnabledForAllAndroidDevices, {}}}); pref_service_.SetUserPref( unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, std::make_unique<base::Value>(true)); @@ -163,11 +175,17 @@ TEST_F(RealTimePolicyEngineTest, TestCanPerformFullURLLookup_EnabledUserOptin) { TEST_F(RealTimePolicyEngineTest, TestCanPerformFullURLLookup_EnhancedProtection) { - base::test::ScopedFeatureList feature_list; pref_service_.SetBoolean(prefs::kSafeBrowsingEnhanced, true); - ASSERT_FALSE(CanPerformFullURLLookup(/* is_off_the_record */ false)); - feature_list.InitAndEnableFeature(kEnhancedProtection); - ASSERT_TRUE(CanPerformFullURLLookup(/* is_off_the_record */ false)); + { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndDisableFeature(kEnhancedProtection); + ASSERT_FALSE(CanPerformFullURLLookup(/* is_off_the_record */ false)); + } + { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(kEnhancedProtection); + ASSERT_TRUE(CanPerformFullURLLookup(/* is_off_the_record */ false)); + } } TEST_F(RealTimePolicyEngineTest, @@ -323,12 +341,37 @@ TEST_F(RealTimePolicyEngineTest, /*is_off_the_record=*/false, &sync_service, identity_manager)); } -TEST_F(RealTimePolicyEngineTest, - TestCanPerformFullURLLookup_EnabledMainFrameOnlyForNonEpUser) { +TEST_F(RealTimePolicyEngineTest, TestCanPerformEnterpriseFullURLLookup) { + // Is off the record profile. + { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(kRealTimeUrlLookupEnabledForEnterprise); + EXPECT_FALSE(CanPerformEnterpriseFullURLLookup(/*has_valid_dm_token=*/true, + /*is_off_the_record=*/true)); + } + // Feature flag disabled. + { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndDisableFeature(kRealTimeUrlLookupEnabledForEnterprise); + EXPECT_FALSE(CanPerformEnterpriseFullURLLookup( + /*has_valid_dm_token=*/true, /*is_off_the_record=*/false)); + } + // No valid DM token. + { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(kRealTimeUrlLookupEnabledForEnterprise); + EXPECT_FALSE(CanPerformEnterpriseFullURLLookup( + /*has_valid_dm_token=*/false, /*is_off_the_record=*/false)); + } +} + +TEST_F( + RealTimePolicyEngineTest, + TestCanPerformFullURLLookup_EnabledMainFrameOnlyForSubresourceDisabledUser) { for (int i = 0; i <= static_cast<int>(ResourceType::kMaxValue); i++) { ResourceType resource_type = static_cast<ResourceType>(i); bool enabled = RealTimePolicyEngine::CanPerformFullURLLookupForResourceType( - resource_type, /*enhanced_protection_enabled=*/false); + resource_type, /*can_rt_check_subresource_url=*/false); switch (resource_type) { case ResourceType::kMainFrame: EXPECT_TRUE(enabled); @@ -340,12 +383,13 @@ TEST_F(RealTimePolicyEngineTest, } } -TEST_F(RealTimePolicyEngineTest, - TestCanPerformFullURLLookup_EnabledNonMainFrameForEpUser) { +TEST_F( + RealTimePolicyEngineTest, + TestCanPerformFullURLLookup_EnabledNonMainFrameForSubresourceEnabledUser) { for (int i = 0; i <= static_cast<int>(ResourceType::kMaxValue); i++) { ResourceType resource_type = static_cast<ResourceType>(i); bool enabled = RealTimePolicyEngine::CanPerformFullURLLookupForResourceType( - resource_type, /*enhanced_protection_enabled=*/true); + resource_type, /*can_rt_check_subresource_url=*/true); switch (resource_type) { case ResourceType::kMainFrame: case ResourceType::kSubFrame: @@ -358,4 +402,18 @@ TEST_F(RealTimePolicyEngineTest, } } +TEST_F(RealTimePolicyEngineTest, TestIsInExcludedCountry) { + const std::string non_excluded_countries[] = {"be", "br", "ca", "de", "es", + "fr", "ie", "in", "jp", "nl", + "ru", "se", "us"}; + for (auto country : non_excluded_countries) { + EXPECT_FALSE(IsInExcludedCountry(country)); + } + + const std::string excluded_countries[] = {"cn"}; + for (auto country : excluded_countries) { + EXPECT_TRUE(IsInExcludedCountry(country)); + } +} + } // namespace safe_browsing diff --git a/chromium/components/safe_browsing/core/realtime/url_lookup_service.cc b/chromium/components/safe_browsing/core/realtime/url_lookup_service.cc index 27c24830aa0..439439b0042 100644 --- a/chromium/components/safe_browsing/core/realtime/url_lookup_service.cc +++ b/chromium/components/safe_browsing/core/realtime/url_lookup_service.cc @@ -18,7 +18,6 @@ #include "components/safe_browsing/core/common/thread_utils.h" #include "components/safe_browsing/core/db/v4_protocol_manager_util.h" #include "components/safe_browsing/core/realtime/policy_engine.h" -#include "components/safe_browsing/core/verdict_cache_manager.h" #include "components/signin/public/identity_manager/consent_level.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "components/sync/driver/sync_service.h" @@ -29,32 +28,11 @@ #include "net/traffic_annotation/network_traffic_annotation.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/shared_url_loader_factory.h" -#include "services/network/public/cpp/simple_url_loader.h" namespace safe_browsing { namespace { -const char kRealTimeLookupUrlPrefix[] = - "https://safebrowsing.google.com/safebrowsing/clientreport/realtime"; - -const size_t kMaxFailuresToEnforceBackoff = 3; - -const size_t kMinBackOffResetDurationInSeconds = 5 * 60; // 5 minutes. -const size_t kMaxBackOffResetDurationInSeconds = 30 * 60; // 30 minutes. - -const size_t kURLLookupTimeoutDurationInSeconds = 10; // 10 seconds. - -// Fragements, usernames and passwords are removed, becuase fragments are only -// used for local navigations and usernames/passwords are too privacy sensitive. -GURL SanitizeURL(const GURL& url) { - GURL::Replacements replacements; - replacements.ClearRef(); - replacements.ClearUsername(); - replacements.ClearPassword(); - return url.ReplaceComponents(replacements); -} - constexpr char kAuthHeaderBearer[] = "Bearer "; } // namespace @@ -68,15 +46,16 @@ RealTimeUrlLookupService::RealTimeUrlLookupService( const ChromeUserPopulation::ProfileManagementStatus& profile_management_status, bool is_under_advanced_protection, - bool is_off_the_record) - : url_loader_factory_(url_loader_factory), - cache_manager_(cache_manager), + bool is_off_the_record, + variations::VariationsService* variations_service) + : RealTimeUrlLookupServiceBase(url_loader_factory, cache_manager), identity_manager_(identity_manager), sync_service_(sync_service), pref_service_(pref_service), profile_management_status_(profile_management_status), is_under_advanced_protection_(is_under_advanced_protection), - is_off_the_record_(is_off_the_record) { + is_off_the_record_(is_off_the_record), + variations_(variations_service) { token_fetcher_ = std::make_unique<SafeBrowsingTokenFetcher>(identity_manager_); } @@ -140,43 +119,8 @@ void RealTimeUrlLookupService::SendRequest( ChromeUserPopulation::UserPopulation_MAX + 1); std::string req_data; request->SerializeToString(&req_data); - net::NetworkTrafficAnnotationTag traffic_annotation = - net::DefineNetworkTrafficAnnotation("safe_browsing_realtime_url_lookup", - R"( - semantics { - sender: "Safe Browsing" - description: - "When Safe Browsing can't detect that a URL is safe based on its " - "local database, it sends the top-level URL to Google to verify it " - "before showing a warning to the user." - trigger: - "When a main frame URL fails to match the local hash-prefix " - "database of known safe URLs and a valid result from a prior " - "lookup is not already cached, this will be sent." - data: "The main frame URL that did not match the local safelist." - destination: GOOGLE_OWNED_SERVICE - } - policy { - cookies_allowed: YES - cookies_store: "Safe Browsing cookie store" - setting: - "Users can disable Safe Browsing real time URL checks by " - "unchecking 'Protect you and your device from dangerous sites' in " - "Chromium settings under Privacy, or by unchecking 'Make searches " - "and browsing better (Sends URLs of pages you visit to Google)' in " - "Chromium settings under Privacy." - chrome_policy { - UrlKeyedAnonymizedDataCollectionEnabled { - policy_options {mode: MANDATORY} - UrlKeyedAnonymizedDataCollectionEnabled: false - } - } - })"); - auto resource_request = std::make_unique<network::ResourceRequest>(); - resource_request->url = GURL(kRealTimeLookupUrlPrefix); - resource_request->load_flags = net::LOAD_DISABLE_CACHE; - resource_request->method = "POST"; + auto resource_request = GetResourceRequest(); if (access_token_info.has_value()) { resource_request->headers.SetHeader( net::HttpRequestHeaders::kAuthorization, @@ -185,19 +129,8 @@ void RealTimeUrlLookupService::SendRequest( base::UmaHistogramBoolean("SafeBrowsing.RT.HasTokenInRequest", access_token_info.has_value()); - std::unique_ptr<network::SimpleURLLoader> owned_loader = - network::SimpleURLLoader::Create(std::move(resource_request), - traffic_annotation); - network::SimpleURLLoader* loader = owned_loader.get(); - owned_loader->AttachStringForUpload(req_data, "application/octet-stream"); - owned_loader->SetTimeoutDuration( - base::TimeDelta::FromSeconds(kURLLookupTimeoutDurationInSeconds)); - owned_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( - url_loader_factory_.get(), - base::BindOnce(&RealTimeUrlLookupService::OnURLLoaderComplete, - GetWeakPtr(), url, loader, base::TimeTicks::Now())); - - pending_requests_[owned_loader.release()] = std::move(response_callback); + SendRequestInternal(std::move(resource_request), req_data, url, + std::move(response_callback)); base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::IO), base::BindOnce(std::move(request_callback), std::move(request), @@ -206,124 +139,8 @@ void RealTimeUrlLookupService::SendRequest( : "")); } -std::unique_ptr<RTLookupResponse> -RealTimeUrlLookupService::GetCachedRealTimeUrlVerdict(const GURL& url) { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - std::unique_ptr<RTLookupResponse::ThreatInfo> cached_threat_info = - std::make_unique<RTLookupResponse::ThreatInfo>(); - - base::UmaHistogramBoolean("SafeBrowsing.RT.HasValidCacheManager", - !!cache_manager_); - - base::TimeTicks get_cache_start_time = base::TimeTicks::Now(); - - RTLookupResponse::ThreatInfo::VerdictType verdict_type = - cache_manager_ ? cache_manager_->GetCachedRealTimeUrlVerdict( - url, cached_threat_info.get()) - : RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED; - - base::UmaHistogramSparse("SafeBrowsing.RT.GetCacheResult", verdict_type); - UMA_HISTOGRAM_TIMES("SafeBrowsing.RT.GetCache.Time", - base::TimeTicks::Now() - get_cache_start_time); - - if (verdict_type == RTLookupResponse::ThreatInfo::SAFE || - verdict_type == RTLookupResponse::ThreatInfo::DANGEROUS) { - auto cache_response = std::make_unique<RTLookupResponse>(); - RTLookupResponse::ThreatInfo* new_threat_info = - cache_response->add_threat_info(); - *new_threat_info = *cached_threat_info; - return cache_response; - } - return nullptr; -} - -void RealTimeUrlLookupService::Shutdown() { - for (auto& pending : pending_requests_) { - // Treat all pending requests as safe. - auto response = std::make_unique<RTLookupResponse>(); - std::move(pending.second) - .Run(/* is_rt_lookup_successful */ true, std::move(response)); - delete pending.first; - } - pending_requests_.clear(); -} - RealTimeUrlLookupService::~RealTimeUrlLookupService() {} -void RealTimeUrlLookupService::OnURLLoaderComplete( - const GURL& url, - network::SimpleURLLoader* url_loader, - base::TimeTicks request_start_time, - std::unique_ptr<std::string> response_body) { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - - auto it = pending_requests_.find(url_loader); - DCHECK(it != pending_requests_.end()) << "Request not found"; - - UMA_HISTOGRAM_TIMES("SafeBrowsing.RT.Network.Time", - base::TimeTicks::Now() - request_start_time); - - int net_error = url_loader->NetError(); - int response_code = 0; - if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) - response_code = url_loader->ResponseInfo()->headers->response_code(); - V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( - "SafeBrowsing.RT.Network.Result", net_error, response_code); - - auto response = std::make_unique<RTLookupResponse>(); - bool is_rt_lookup_successful = (net_error == net::OK) && - (response_code == net::HTTP_OK) && - response->ParseFromString(*response_body); - base::UmaHistogramBoolean("SafeBrowsing.RT.IsLookupSuccessful", - is_rt_lookup_successful); - is_rt_lookup_successful ? HandleLookupSuccess() : HandleLookupError(); - - MayBeCacheRealTimeUrlVerdict(url, *response); - - UMA_HISTOGRAM_COUNTS_100("SafeBrowsing.RT.ThreatInfoSize", - response->threat_info_size()); - - base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::IO), - base::BindOnce(std::move(it->second), is_rt_lookup_successful, - std::move(response))); - - delete it->first; - pending_requests_.erase(it); -} - -void RealTimeUrlLookupService::MayBeCacheRealTimeUrlVerdict( - const GURL& url, - RTLookupResponse response) { - if (response.threat_info_size() > 0) { - base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::UI), - base::BindOnce(&VerdictCacheManager::CacheRealTimeUrlVerdict, - base::Unretained(cache_manager_), url, - response, base::Time::Now(), - /* store_old_cache */ false)); - } -} - -// static -bool RealTimeUrlLookupService::CanCheckUrl(const GURL& url) { - if (!url.SchemeIsHTTPOrHTTPS()) { - return false; - } - - if (net::IsLocalhost(url)) { - // Includes: "//localhost/", "//localhost.localdomain/", "//127.0.0.1/" - return false; - } - - net::IPAddress ip_address; - if (url.HostIsIPAddress() && ip_address.AssignFromIPLiteral(url.host()) && - !ip_address.IsPubliclyRoutable()) { - // Includes: "//192.168.1.1/", "//172.16.2.2/", "//10.1.1.1/" - return false; - } - - return true; -} - std::unique_ptr<RTLookupRequest> RealTimeUrlLookupService::FillRequestProto( const GURL& url) { auto request = std::make_unique<RTLookupRequest>(); @@ -357,103 +174,66 @@ bool RealTimeUrlLookupService::IsHistorySyncEnabled() { syncer::HISTORY_DELETE_DIRECTIVES); } -size_t RealTimeUrlLookupService::GetBackoffDurationInSeconds() const { - return did_successful_lookup_since_last_backoff_ - ? kMinBackOffResetDurationInSeconds - : std::min(kMaxBackOffResetDurationInSeconds, - 2 * next_backoff_duration_secs_); -} - -void RealTimeUrlLookupService::HandleLookupError() { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - consecutive_failures_++; - - // Any successful lookup clears both |consecutive_failures_| as well as - // |did_successful_lookup_since_last_backoff_|. - // On a failure, the following happens: - // 1) if |consecutive_failures_| < |kMaxFailuresToEnforceBackoff|: - // Do nothing more. - // 2) if already in the backoff mode: - // Do nothing more. This can happen if we had some outstanding real time - // requests in flight when we entered the backoff mode. - // 3) if |did_successful_lookup_since_last_backoff_| is true: - // Enter backoff mode for |kMinBackOffResetDurationInSeconds| seconds. - // 4) if |did_successful_lookup_since_last_backoff_| is false: - // This indicates that we've had |kMaxFailuresToEnforceBackoff| since - // exiting the last backoff with no successful lookups since so do an - // exponential backoff. - - if (consecutive_failures_ < kMaxFailuresToEnforceBackoff) - return; - - if (IsInBackoffMode()) { - return; - } - - // Enter backoff mode, calculate duration. - next_backoff_duration_secs_ = GetBackoffDurationInSeconds(); - backoff_timer_.Start( - FROM_HERE, base::TimeDelta::FromSeconds(next_backoff_duration_secs_), - this, &RealTimeUrlLookupService::ResetFailures); - did_successful_lookup_since_last_backoff_ = false; -} - -void RealTimeUrlLookupService::HandleLookupSuccess() { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - ResetFailures(); - - // |did_successful_lookup_since_last_backoff_| is set to true only when we - // complete a lookup successfully. - did_successful_lookup_since_last_backoff_ = true; -} - -bool RealTimeUrlLookupService::IsInBackoffMode() const { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - bool in_backoff = backoff_timer_.IsRunning(); - UMA_HISTOGRAM_BOOLEAN("SafeBrowsing.RT.Backoff.State", in_backoff); - return in_backoff; -} - -void RealTimeUrlLookupService::ResetFailures() { - DCHECK(CurrentlyOnThread(ThreadID::UI)); - consecutive_failures_ = 0; - backoff_timer_.Stop(); -} - bool RealTimeUrlLookupService::CanPerformFullURLLookup() const { - return RealTimePolicyEngine::CanPerformFullURLLookup(pref_service_, - is_off_the_record_); + return RealTimePolicyEngine::CanPerformFullURLLookup( + pref_service_, is_off_the_record_, variations_); } bool RealTimeUrlLookupService::CanPerformFullURLLookupWithToken() const { return RealTimePolicyEngine::CanPerformFullURLLookupWithToken( - pref_service_, is_off_the_record_, sync_service_, identity_manager_); + pref_service_, is_off_the_record_, sync_service_, identity_manager_, + variations_); } -bool RealTimeUrlLookupService::IsUserEpOptedIn() const { +bool RealTimeUrlLookupService::CanCheckSubresourceURL() const { return IsEnhancedProtectionEnabled(*pref_service_); } -// static -SBThreatType RealTimeUrlLookupService::GetSBThreatTypeForRTThreatType( - RTLookupResponse::ThreatInfo::ThreatType rt_threat_type) { - switch (rt_threat_type) { - case RTLookupResponse::ThreatInfo::WEB_MALWARE: - return SB_THREAT_TYPE_URL_MALWARE; - case RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING: - return SB_THREAT_TYPE_URL_PHISHING; - case RTLookupResponse::ThreatInfo::UNWANTED_SOFTWARE: - return SB_THREAT_TYPE_URL_UNWANTED; - case RTLookupResponse::ThreatInfo::UNCLEAR_BILLING: - return SB_THREAT_TYPE_BILLING; - case RTLookupResponse::ThreatInfo::THREAT_TYPE_UNSPECIFIED: - NOTREACHED() << "Unexpected RTLookupResponse::ThreatType encountered"; - return SB_THREAT_TYPE_SAFE; - } +bool RealTimeUrlLookupService::CanCheckSafeBrowsingDb() const { + // Always return true, because consumer real time URL check only works when + // safe browsing is enabled. + return true; +} + +net::NetworkTrafficAnnotationTag +RealTimeUrlLookupService::GetTrafficAnnotationTag() const { + return net::DefineNetworkTrafficAnnotation( + "safe_browsing_realtime_url_lookup", + R"( + semantics { + sender: "Safe Browsing" + description: + "When Safe Browsing can't detect that a URL is safe based on its " + "local database, it sends the top-level URL to Google to verify it " + "before showing a warning to the user." + trigger: + "When a main frame URL fails to match the local hash-prefix " + "database of known safe URLs and a valid result from a prior " + "lookup is not already cached, this will be sent." + data: "The main frame URL that did not match the local safelist." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: YES + cookies_store: "Safe Browsing cookie store" + setting: + "Users can disable Safe Browsing real time URL checks by " + "unchecking 'Protect you and your device from dangerous sites' in " + "Chromium settings under Privacy, or by unchecking 'Make searches " + "and browsing better (Sends URLs of pages you visit to Google)' in " + "Chromium settings under Privacy." + chrome_policy { + UrlKeyedAnonymizedDataCollectionEnabled { + policy_options {mode: MANDATORY} + UrlKeyedAnonymizedDataCollectionEnabled: false + } + } + })"); } -base::WeakPtr<RealTimeUrlLookupService> RealTimeUrlLookupService::GetWeakPtr() { - return weak_factory_.GetWeakPtr(); +GURL RealTimeUrlLookupService::GetRealTimeLookupUrl() const { + return GURL( + "https://safebrowsing.google.com/safebrowsing/clientreport/realtime"); } } // namespace safe_browsing diff --git a/chromium/components/safe_browsing/core/realtime/url_lookup_service.h b/chromium/components/safe_browsing/core/realtime/url_lookup_service.h index 70aa7bdec50..34197d63420 100644 --- a/chromium/components/safe_browsing/core/realtime/url_lookup_service.h +++ b/chromium/components/safe_browsing/core/realtime/url_lookup_service.h @@ -14,15 +14,18 @@ #include "base/optional.h" #include "base/time/time.h" #include "base/timer/timer.h" -#include "components/keyed_service/core/keyed_service.h" #include "components/safe_browsing/core/db/v4_protocol_manager_util.h" #include "components/safe_browsing/core/proto/csd.pb.h" #include "components/safe_browsing/core/proto/realtimeapi.pb.h" +#include "components/safe_browsing/core/realtime/url_lookup_service_base.h" #include "components/signin/public/identity_manager/access_token_info.h" #include "url/gurl.h" +namespace net { +struct NetworkTrafficAnnotationTag; +} + namespace network { -class SimpleURLLoader; class SharedURLLoaderFactory; } // namespace network @@ -34,24 +37,20 @@ namespace syncer { class SyncService; } +namespace variations { +class VariationsService; +} + class PrefService; namespace safe_browsing { -using RTLookupRequestCallback = - base::OnceCallback<void(std::unique_ptr<RTLookupRequest>, std::string)>; - -using RTLookupResponseCallback = - base::OnceCallback<void(bool, std::unique_ptr<RTLookupResponse>)>; - -class VerdictCacheManager; - class SafeBrowsingTokenFetcher; -// This class implements the logic to decide whether the real time lookup -// feature is enabled for a given user/profile. -// TODO(crbug.com/1050859): Add RTLookupService check flow. -class RealTimeUrlLookupService : public KeyedService { +// This class implements the real time lookup feature for a given user/profile. +// It is separated from the base class for logic that is related to consumer +// users.(See: go/chrome-protego-enterprise-dd) +class RealTimeUrlLookupService : public RealTimeUrlLookupServiceBase { public: // |cache_manager|, |identity_manager|, |sync_service| and |pref_service| may // be null in tests. @@ -64,58 +63,26 @@ class RealTimeUrlLookupService : public KeyedService { const ChromeUserPopulation::ProfileManagementStatus& profile_management_status, bool is_under_advanced_protection, - bool is_off_the_record); + bool is_off_the_record, + variations::VariationsService* variations_service); ~RealTimeUrlLookupService() override; - // Returns true if |url|'s scheme can be checked. - static bool CanCheckUrl(const GURL& url); - - // Returns true if real time URL lookup is enabled. The check is based on - // pref settings of the associated profile, whether the profile is an off the - // record profile and the finch flag. - bool CanPerformFullURLLookup() const; - - // Returns true if real time URL lookup with token is enabled. - bool CanPerformFullURLLookupWithToken() const; - - // Returns true if the real time lookups are currently in backoff mode due to - // too many prior errors. If this happens, the checking falls back to - // local hash-based method. - bool IsInBackoffMode() const; - - // Returns true if this profile has opted-in to Enhanced Protection. - bool IsUserEpOptedIn() const; - - // Start the full URL lookup for |url|, call |request_callback| on the same - // thread when request is sent, call |response_callback| on the same thread - // when response is received. - // Note that |request_callback| is not called if there's a valid entry in the - // cache for |url|. - // This function is overridden in unit tests. - virtual void StartLookup(const GURL& url, - RTLookupRequestCallback request_callback, - RTLookupResponseCallback response_callback); + // RealTimeUrlLookupServiceBase: + bool CanPerformFullURLLookup() const override; - // KeyedService: - // Called before the actual deletion of the object. - void Shutdown() override; + bool CanCheckSubresourceURL() const override; - // Returns the SBThreatType for a given - // RTLookupResponse::ThreatInfo::ThreatType - static SBThreatType GetSBThreatTypeForRTThreatType( - RTLookupResponse::ThreatInfo::ThreatType rt_threat_type); + bool CanCheckSafeBrowsingDb() const override; - // Helper function to return a weak pointer. - base::WeakPtr<RealTimeUrlLookupService> GetWeakPtr(); + void StartLookup(const GURL& url, + RTLookupRequestCallback request_callback, + RTLookupResponseCallback response_callback) override; private: - using PendingRTLookupRequests = - base::flat_map<network::SimpleURLLoader*, RTLookupResponseCallback>; + // RealTimeUrlLookupServiceBase: + net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const override; - // Called to get cache from |cache_manager|. Returns the cached response if - // there's a cache hit; nullptr otherwise. - std::unique_ptr<RTLookupResponse> GetCachedRealTimeUrlVerdict( - const GURL& url); + GURL GetRealTimeLookupUrl() const override; // Called when the access token is obtained from |token_fetcher_|. void OnGetAccessToken( @@ -133,72 +100,12 @@ class RealTimeUrlLookupService : public KeyedService { RTLookupRequestCallback request_callback, RTLookupResponseCallback response_callback); - // Called to get cache from |cache_manager|. If a cache is found, return true. - // Otherwise, return false. - bool GetCachedRealTimeUrlVerdict(const GURL& url, - RTLookupResponse* out_response); - - // Called to post a task to store the response keyed by the |url| in - // |cache_manager|. - void MayBeCacheRealTimeUrlVerdict(const GURL& url, RTLookupResponse response); - bool IsHistorySyncEnabled(); - // Returns the duration of the next backoff. Starts at - // |kMinBackOffResetDurationInSeconds| and increases exponentially until it - // reaches |kMaxBackOffResetDurationInSeconds|. - size_t GetBackoffDurationInSeconds() const; - - // Called when the request to remote endpoint fails. May initiate or extend - // backoff. - void HandleLookupError(); - - // Called when the request to remote endpoint succeeds. Resets error count and - // ends backoff. - void HandleLookupSuccess(); - - // Resets the error count and ends backoff mode. Functionally same as - // |HandleLookupSuccess| for now. - void ResetFailures(); - - // Called when the response from the real-time lookup remote endpoint is - // received. |url_loader| is the unowned loader that was used to send the - // request. |request_start_time| is the time when the request was sent. - // |response_body| is the response received. |url| is used for calling - // |MayBeCacheRealTimeUrlVerdict|. - void OnURLLoaderComplete(const GURL& url, - network::SimpleURLLoader* url_loader, - base::TimeTicks request_start_time, - std::unique_ptr<std::string> response_body); - std::unique_ptr<RTLookupRequest> FillRequestProto(const GURL& url); - PendingRTLookupRequests pending_requests_; - - // Count of consecutive failures to complete URL lookup requests. When it - // reaches |kMaxFailuresToEnforceBackoff|, we enter the backoff mode. It gets - // reset when we complete a lookup successfully or when the backoff reset - // timer fires. - size_t consecutive_failures_ = 0; - - // If true, represents that one or more real time lookups did complete - // successfully since the last backoff or Chrome never entered the breakoff; - // if false and Chrome re-enters backoff period, the backoff duration is - // increased exponentially (capped at |kMaxBackOffResetDurationInSeconds|). - bool did_successful_lookup_since_last_backoff_ = true; - - // The current duration of backoff. Increases exponentially until it reaches - // |kMaxBackOffResetDurationInSeconds|. - size_t next_backoff_duration_secs_ = 0; - - // If this timer is running, backoff is in effect. - base::OneShotTimer backoff_timer_; - - // The URLLoaderFactory we use to issue network requests. - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; - - // Unowned object used for getting and storing real time url check cache. - VerdictCacheManager* cache_manager_; + // Returns true if real time URL lookup with GAIA token is enabled. + bool CanPerformFullURLLookupWithToken() const; // Unowned object used for getting access token when real time url check with // token is enabled. @@ -223,6 +130,10 @@ class RealTimeUrlLookupService : public KeyedService { // |url_lookup_service| is an off the record profile. bool is_off_the_record_; + // Unowned. For checking whether real-time checks can be enabled in a given + // location. + variations::VariationsService* variations_; + friend class RealTimeUrlLookupServiceTest; base::WeakPtrFactory<RealTimeUrlLookupService> weak_factory_{this}; diff --git a/chromium/components/safe_browsing/core/realtime/url_lookup_service_base.cc b/chromium/components/safe_browsing/core/realtime/url_lookup_service_base.cc new file mode 100644 index 00000000000..be6cc99e8b9 --- /dev/null +++ b/chromium/components/safe_browsing/core/realtime/url_lookup_service_base.cc @@ -0,0 +1,284 @@ +// Copyright 2020 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 "components/safe_browsing/core/realtime/url_lookup_service_base.h" + +#include "base/base64url.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/strcat.h" +#include "base/strings/string_piece.h" +#include "base/task/post_task.h" +#include "base/time/time.h" +#include "components/safe_browsing/core/common/thread_utils.h" +#include "components/safe_browsing/core/verdict_cache_manager.h" +#include "net/base/ip_address.h" +#include "net/base/load_flags.h" +#include "net/base/url_util.h" +#include "net/http/http_status_code.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/public/cpp/simple_url_loader.h" + +namespace safe_browsing { + +namespace { + +const size_t kMaxFailuresToEnforceBackoff = 3; + +const size_t kMinBackOffResetDurationInSeconds = 5 * 60; // 5 minutes. +const size_t kMaxBackOffResetDurationInSeconds = 30 * 60; // 30 minutes. + +const size_t kURLLookupTimeoutDurationInSeconds = 10; // 10 seconds. + +} // namespace + +RealTimeUrlLookupServiceBase::RealTimeUrlLookupServiceBase( + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + VerdictCacheManager* cache_manager) + : url_loader_factory_(url_loader_factory), cache_manager_(cache_manager) {} + +RealTimeUrlLookupServiceBase::~RealTimeUrlLookupServiceBase() = default; + +// static +bool RealTimeUrlLookupServiceBase::CanCheckUrl(const GURL& url) { + if (!url.SchemeIsHTTPOrHTTPS()) { + return false; + } + + if (net::IsLocalhost(url)) { + // Includes: "//localhost/", "//localhost.localdomain/", "//127.0.0.1/" + return false; + } + + net::IPAddress ip_address; + if (url.HostIsIPAddress() && ip_address.AssignFromIPLiteral(url.host()) && + !ip_address.IsPubliclyRoutable()) { + // Includes: "//192.168.1.1/", "//172.16.2.2/", "//10.1.1.1/" + return false; + } + + return true; +} + +// static +SBThreatType RealTimeUrlLookupServiceBase::GetSBThreatTypeForRTThreatType( + RTLookupResponse::ThreatInfo::ThreatType rt_threat_type) { + switch (rt_threat_type) { + case RTLookupResponse::ThreatInfo::WEB_MALWARE: + return SB_THREAT_TYPE_URL_MALWARE; + case RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING: + return SB_THREAT_TYPE_URL_PHISHING; + case RTLookupResponse::ThreatInfo::UNWANTED_SOFTWARE: + return SB_THREAT_TYPE_URL_UNWANTED; + case RTLookupResponse::ThreatInfo::UNCLEAR_BILLING: + return SB_THREAT_TYPE_BILLING; + case RTLookupResponse::ThreatInfo::THREAT_TYPE_UNSPECIFIED: + NOTREACHED() << "Unexpected RTLookupResponse::ThreatType encountered"; + return SB_THREAT_TYPE_SAFE; + } +} + +// static +GURL RealTimeUrlLookupServiceBase::SanitizeURL(const GURL& url) { + GURL::Replacements replacements; + replacements.ClearRef(); + replacements.ClearUsername(); + replacements.ClearPassword(); + return url.ReplaceComponents(replacements); +} + +base::WeakPtr<RealTimeUrlLookupServiceBase> +RealTimeUrlLookupServiceBase::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + +size_t RealTimeUrlLookupServiceBase::GetBackoffDurationInSeconds() const { + return did_successful_lookup_since_last_backoff_ + ? kMinBackOffResetDurationInSeconds + : std::min(kMaxBackOffResetDurationInSeconds, + 2 * next_backoff_duration_secs_); +} + +void RealTimeUrlLookupServiceBase::HandleLookupError() { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + consecutive_failures_++; + + // Any successful lookup clears both |consecutive_failures_| as well as + // |did_successful_lookup_since_last_backoff_|. + // On a failure, the following happens: + // 1) if |consecutive_failures_| < |kMaxFailuresToEnforceBackoff|: + // Do nothing more. + // 2) if already in the backoff mode: + // Do nothing more. This can happen if we had some outstanding real time + // requests in flight when we entered the backoff mode. + // 3) if |did_successful_lookup_since_last_backoff_| is true: + // Enter backoff mode for |kMinBackOffResetDurationInSeconds| seconds. + // 4) if |did_successful_lookup_since_last_backoff_| is false: + // This indicates that we've had |kMaxFailuresToEnforceBackoff| since + // exiting the last backoff with no successful lookups since so do an + // exponential backoff. + + if (consecutive_failures_ < kMaxFailuresToEnforceBackoff) + return; + + if (IsInBackoffMode()) { + return; + } + + // Enter backoff mode, calculate duration. + next_backoff_duration_secs_ = GetBackoffDurationInSeconds(); + backoff_timer_.Start( + FROM_HERE, base::TimeDelta::FromSeconds(next_backoff_duration_secs_), + this, &RealTimeUrlLookupServiceBase::ResetFailures); + did_successful_lookup_since_last_backoff_ = false; +} + +void RealTimeUrlLookupServiceBase::HandleLookupSuccess() { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + ResetFailures(); + + // |did_successful_lookup_since_last_backoff_| is set to true only when we + // complete a lookup successfully. + did_successful_lookup_since_last_backoff_ = true; +} + +bool RealTimeUrlLookupServiceBase::IsInBackoffMode() const { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + bool in_backoff = backoff_timer_.IsRunning(); + UMA_HISTOGRAM_BOOLEAN("SafeBrowsing.RT.Backoff.State", in_backoff); + return in_backoff; +} + +void RealTimeUrlLookupServiceBase::ResetFailures() { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + consecutive_failures_ = 0; + backoff_timer_.Stop(); +} + +std::unique_ptr<RTLookupResponse> +RealTimeUrlLookupServiceBase::GetCachedRealTimeUrlVerdict(const GURL& url) { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + std::unique_ptr<RTLookupResponse::ThreatInfo> cached_threat_info = + std::make_unique<RTLookupResponse::ThreatInfo>(); + + base::UmaHistogramBoolean("SafeBrowsing.RT.HasValidCacheManager", + !!cache_manager_); + + base::TimeTicks get_cache_start_time = base::TimeTicks::Now(); + + RTLookupResponse::ThreatInfo::VerdictType verdict_type = + cache_manager_ ? cache_manager_->GetCachedRealTimeUrlVerdict( + url, cached_threat_info.get()) + : RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED; + + base::UmaHistogramSparse("SafeBrowsing.RT.GetCacheResult", verdict_type); + UMA_HISTOGRAM_TIMES("SafeBrowsing.RT.GetCache.Time", + base::TimeTicks::Now() - get_cache_start_time); + + if (verdict_type == RTLookupResponse::ThreatInfo::SAFE || + verdict_type == RTLookupResponse::ThreatInfo::DANGEROUS) { + auto cache_response = std::make_unique<RTLookupResponse>(); + RTLookupResponse::ThreatInfo* new_threat_info = + cache_response->add_threat_info(); + *new_threat_info = *cached_threat_info; + return cache_response; + } + return nullptr; +} + +void RealTimeUrlLookupServiceBase::MayBeCacheRealTimeUrlVerdict( + const GURL& url, + RTLookupResponse response) { + if (response.threat_info_size() > 0) { + base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::UI), + base::BindOnce(&VerdictCacheManager::CacheRealTimeUrlVerdict, + base::Unretained(cache_manager_), url, + response, base::Time::Now(), + /* store_old_cache */ false)); + } +} + +std::unique_ptr<network::ResourceRequest> +RealTimeUrlLookupServiceBase::GetResourceRequest() { + auto resource_request = std::make_unique<network::ResourceRequest>(); + resource_request->url = GetRealTimeLookupUrl(); + resource_request->load_flags = net::LOAD_DISABLE_CACHE; + resource_request->method = "POST"; + return resource_request; +} + +void RealTimeUrlLookupServiceBase::SendRequestInternal( + std::unique_ptr<network::ResourceRequest> resource_request, + const std::string& req_data, + const GURL& url, + RTLookupResponseCallback response_callback) { + std::unique_ptr<network::SimpleURLLoader> owned_loader = + network::SimpleURLLoader::Create(std::move(resource_request), + GetTrafficAnnotationTag()); + network::SimpleURLLoader* loader = owned_loader.get(); + owned_loader->AttachStringForUpload(req_data, "application/octet-stream"); + owned_loader->SetTimeoutDuration( + base::TimeDelta::FromSeconds(kURLLookupTimeoutDurationInSeconds)); + owned_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( + url_loader_factory_.get(), + base::BindOnce(&RealTimeUrlLookupServiceBase::OnURLLoaderComplete, + GetWeakPtr(), url, loader, base::TimeTicks::Now())); + + pending_requests_[owned_loader.release()] = std::move(response_callback); +} + +void RealTimeUrlLookupServiceBase::OnURLLoaderComplete( + const GURL& url, + network::SimpleURLLoader* url_loader, + base::TimeTicks request_start_time, + std::unique_ptr<std::string> response_body) { + DCHECK(CurrentlyOnThread(ThreadID::UI)); + + auto it = pending_requests_.find(url_loader); + DCHECK(it != pending_requests_.end()) << "Request not found"; + + UMA_HISTOGRAM_TIMES("SafeBrowsing.RT.Network.Time", + base::TimeTicks::Now() - request_start_time); + + int net_error = url_loader->NetError(); + int response_code = 0; + if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) + response_code = url_loader->ResponseInfo()->headers->response_code(); + V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( + "SafeBrowsing.RT.Network.Result", net_error, response_code); + + auto response = std::make_unique<RTLookupResponse>(); + bool is_rt_lookup_successful = (net_error == net::OK) && + (response_code == net::HTTP_OK) && + response->ParseFromString(*response_body); + base::UmaHistogramBoolean("SafeBrowsing.RT.IsLookupSuccessful", + is_rt_lookup_successful); + is_rt_lookup_successful ? HandleLookupSuccess() : HandleLookupError(); + + MayBeCacheRealTimeUrlVerdict(url, *response); + + UMA_HISTOGRAM_COUNTS_100("SafeBrowsing.RT.ThreatInfoSize", + response->threat_info_size()); + + base::PostTask(FROM_HERE, CreateTaskTraits(ThreadID::IO), + base::BindOnce(std::move(it->second), is_rt_lookup_successful, + std::move(response))); + + delete it->first; + pending_requests_.erase(it); +} + +void RealTimeUrlLookupServiceBase::Shutdown() { + for (auto& pending : pending_requests_) { + // Treat all pending requests as safe. + auto response = std::make_unique<RTLookupResponse>(); + std::move(pending.second) + .Run(/* is_rt_lookup_successful */ true, std::move(response)); + delete pending.first; + } + pending_requests_.clear(); +} + +} // namespace safe_browsing diff --git a/chromium/components/safe_browsing/core/realtime/url_lookup_service_base.h b/chromium/components/safe_browsing/core/realtime/url_lookup_service_base.h new file mode 100644 index 00000000000..8300147dbee --- /dev/null +++ b/chromium/components/safe_browsing/core/realtime/url_lookup_service_base.h @@ -0,0 +1,191 @@ +// Copyright 2020 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. + +#ifndef COMPONENTS_SAFE_BROWSING_CORE_REALTIME_URL_LOOKUP_SERVICE_BASE_H_ +#define COMPONENTS_SAFE_BROWSING_CORE_REALTIME_URL_LOOKUP_SERVICE_BASE_H_ + +#include <memory> +#include <string> + +#include "base/callback.h" +#include "base/containers/flat_map.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/safe_browsing/core/db/v4_protocol_manager_util.h" +#include "components/safe_browsing/core/proto/realtimeapi.pb.h" +#include "url/gurl.h" + +namespace net { +struct NetworkTrafficAnnotationTag; +} + +namespace network { +struct ResourceRequest; +class SimpleURLLoader; +class SharedURLLoaderFactory; +} // namespace network + +namespace safe_browsing { + +using RTLookupRequestCallback = + base::OnceCallback<void(std::unique_ptr<RTLookupRequest>, std::string)>; + +using RTLookupResponseCallback = + base::OnceCallback<void(bool, std::unique_ptr<RTLookupResponse>)>; + +class VerdictCacheManager; + +// This base class implements the backoff and cache logic for real time URL +// lookup feature. +class RealTimeUrlLookupServiceBase : public KeyedService { + public: + explicit RealTimeUrlLookupServiceBase( + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + VerdictCacheManager* cache_manager); + ~RealTimeUrlLookupServiceBase() override; + + // Returns true if |url|'s scheme can be checked. + static bool CanCheckUrl(const GURL& url); + + // Returns the SBThreatType for a given + // RTLookupResponse::ThreatInfo::ThreatType + static SBThreatType GetSBThreatTypeForRTThreatType( + RTLookupResponse::ThreatInfo::ThreatType rt_threat_type); + + // Returns true if the real time lookups are currently in backoff mode due to + // too many prior errors. If this happens, the checking falls back to + // local hash-based method. + bool IsInBackoffMode() const; + + // Helper function to return a weak pointer. + base::WeakPtr<RealTimeUrlLookupServiceBase> GetWeakPtr(); + + // Returns true if real time URL lookup is enabled. The check is based on + // pref settings of the associated profile, whether the profile is an off the + // record profile and the finch flag. + virtual bool CanPerformFullURLLookup() const = 0; + + // Returns true if this profile has opted-in to check subresource URLs. + virtual bool CanCheckSubresourceURL() const = 0; + + // Returns whether safe browsing database can be checked when real time URL + // check is enabled. + virtual bool CanCheckSafeBrowsingDb() const = 0; + + // Start the full URL lookup for |url|, call |request_callback| on the same + // thread when request is sent, call |response_callback| on the same thread + // when response is received. + // Note that |request_callback| is not called if there's a valid entry in the + // cache for |url|. + // This function is overridden in unit tests. + virtual void StartLookup(const GURL& url, + RTLookupRequestCallback request_callback, + RTLookupResponseCallback response_callback) = 0; + + // KeyedService: + // Called before the actual deletion of the object. + void Shutdown() override; + + protected: + // Fragments, usernames and passwords are removed, because fragments are only + // used for local navigations and usernames/passwords are too privacy + // sensitive. + static GURL SanitizeURL(const GURL& url); + + // Returns the traffic annotation tag that is attached in the simple URL + // loader. + virtual net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() const = 0; + + // Returns the endpoint that the URL lookup will be sent to. + virtual GURL GetRealTimeLookupUrl() const = 0; + + // Returns the duration of the next backoff. Starts at + // |kMinBackOffResetDurationInSeconds| and increases exponentially until it + // reaches |kMaxBackOffResetDurationInSeconds|. + size_t GetBackoffDurationInSeconds() const; + + // Called when the request to remote endpoint fails. May initiate or extend + // backoff. + void HandleLookupError(); + + // Called when the request to remote endpoint succeeds. Resets error count and + // ends backoff. + void HandleLookupSuccess(); + + // Resets the error count and ends backoff mode. Functionally same as + // |HandleLookupSuccess| for now. + void ResetFailures(); + + // Called to get cache from |cache_manager|. Returns the cached response if + // there's a cache hit; nullptr otherwise. + std::unique_ptr<RTLookupResponse> GetCachedRealTimeUrlVerdict( + const GURL& url); + + // Called to post a task to store the response keyed by the |url| in + // |cache_manager|. + void MayBeCacheRealTimeUrlVerdict(const GURL& url, RTLookupResponse response); + + // Get a resource request with URL, load_flags and method set. + std::unique_ptr<network::ResourceRequest> GetResourceRequest(); + + void SendRequestInternal( + std::unique_ptr<network::ResourceRequest> resource_request, + const std::string& req_data, + const GURL& url, + RTLookupResponseCallback response_callback); + + private: + using PendingRTLookupRequests = + base::flat_map<network::SimpleURLLoader*, RTLookupResponseCallback>; + + // Called when the response from the real-time lookup remote endpoint is + // received. |url_loader| is the unowned loader that was used to send the + // request. |request_start_time| is the time when the request was sent. + // |response_body| is the response received. |url| is used for calling + // |MayBeCacheRealTimeUrlVerdict|. + void OnURLLoaderComplete(const GURL& url, + network::SimpleURLLoader* url_loader, + base::TimeTicks request_start_time, + std::unique_ptr<std::string> response_body); + + // Count of consecutive failures to complete URL lookup requests. When it + // reaches |kMaxFailuresToEnforceBackoff|, we enter the backoff mode. It gets + // reset when we complete a lookup successfully or when the backoff reset + // timer fires. + size_t consecutive_failures_ = 0; + + // If true, represents that one or more real time lookups did complete + // successfully since the last backoff or Chrome never entered the breakoff; + // if false and Chrome re-enters backoff period, the backoff duration is + // increased exponentially (capped at |kMaxBackOffResetDurationInSeconds|). + bool did_successful_lookup_since_last_backoff_ = true; + + // The current duration of backoff. Increases exponentially until it reaches + // |kMaxBackOffResetDurationInSeconds|. + size_t next_backoff_duration_secs_ = 0; + + // If this timer is running, backoff is in effect. + base::OneShotTimer backoff_timer_; + + // The URLLoaderFactory we use to issue network requests. + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; + + // Unowned object used for getting and storing real time url check cache. + VerdictCacheManager* cache_manager_; + + // All requests that are sent but haven't received a response yet. + PendingRTLookupRequests pending_requests_; + + base::WeakPtrFactory<RealTimeUrlLookupServiceBase> weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(RealTimeUrlLookupServiceBase); + +}; // class RealTimeUrlLookupServiceBase + +} // namespace safe_browsing + +#endif // COMPONENTS_SAFE_BROWSING_CORE_REALTIME_URL_LOOKUP_SERVICE_BASE_H_ diff --git a/chromium/components/safe_browsing/core/realtime/url_lookup_service_unittest.cc b/chromium/components/safe_browsing/core/realtime/url_lookup_service_unittest.cc index 2bcffa75a45..8ca8d0135dc 100644 --- a/chromium/components/safe_browsing/core/realtime/url_lookup_service_unittest.cc +++ b/chromium/components/safe_browsing/core/realtime/url_lookup_service_unittest.cc @@ -67,7 +67,7 @@ class RealTimeUrlLookupServiceTest : public PlatformTest { identity_test_env_->identity_manager(), &test_sync_service_, &test_pref_service_, ChromeUserPopulation::NOT_MANAGED, /*is_under_advanced_protection=*/true, - /*is_off_the_record=*/false); + /*is_off_the_record=*/false, /*variations_service=*/nullptr); } void TearDown() override { @@ -76,7 +76,7 @@ class RealTimeUrlLookupServiceTest : public PlatformTest { } bool CanCheckUrl(const GURL& url) { - return RealTimeUrlLookupService::CanCheckUrl(url); + return RealTimeUrlLookupServiceBase::CanCheckUrl(url); } void HandleLookupError() { rt_service_->HandleLookupError(); } void HandleLookupSuccess() { rt_service_->HandleLookupSuccess(); } @@ -500,16 +500,16 @@ TEST_F(RealTimeUrlLookupServiceTest, TestExponentialBackoffWithResetOnSuccess) { TEST_F(RealTimeUrlLookupServiceTest, TestGetSBThreatTypeForRTThreatType) { EXPECT_EQ(SB_THREAT_TYPE_URL_MALWARE, - RealTimeUrlLookupService::GetSBThreatTypeForRTThreatType( + RealTimeUrlLookupServiceBase::GetSBThreatTypeForRTThreatType( RTLookupResponse::ThreatInfo::WEB_MALWARE)); EXPECT_EQ(SB_THREAT_TYPE_URL_PHISHING, - RealTimeUrlLookupService::GetSBThreatTypeForRTThreatType( + RealTimeUrlLookupServiceBase::GetSBThreatTypeForRTThreatType( RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING)); EXPECT_EQ(SB_THREAT_TYPE_URL_UNWANTED, - RealTimeUrlLookupService::GetSBThreatTypeForRTThreatType( + RealTimeUrlLookupServiceBase::GetSBThreatTypeForRTThreatType( RTLookupResponse::ThreatInfo::UNWANTED_SOFTWARE)); EXPECT_EQ(SB_THREAT_TYPE_BILLING, - RealTimeUrlLookupService::GetSBThreatTypeForRTThreatType( + RealTimeUrlLookupServiceBase::GetSBThreatTypeForRTThreatType( RTLookupResponse::ThreatInfo::UNCLEAR_BILLING)); } diff --git a/chromium/components/safe_browsing/core/verdict_cache_manager.cc b/chromium/components/safe_browsing/core/verdict_cache_manager.cc index 64694b39f3e..64eee7e22ce 100644 --- a/chromium/components/safe_browsing/core/verdict_cache_manager.cc +++ b/chromium/components/safe_browsing/core/verdict_cache_manager.cc @@ -5,6 +5,7 @@ #include "components/safe_browsing/core/verdict_cache_manager.h" #include "base/base64.h" +#include "base/metrics/histogram_functions.h" #include "base/optional.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" @@ -544,6 +545,9 @@ void VerdictCacheManager::CacheRealTimeUrlVerdict( hostname, GURL(), ContentSettingsType::SAFE_BROWSING_URL_CHECK_DATA, std::string(), std::move(cache_dictionary)); } + base::UmaHistogramCounts10000( + "SafeBrowsing.RT.CacheManager.RealTimeVerdictCount", + stored_verdict_count_real_time_url_check_); } RTLookupResponse::ThreatInfo::VerdictType @@ -608,6 +612,9 @@ void VerdictCacheManager::CleanUpExpiredPhishGuardVerdicts() { } void VerdictCacheManager::CleanUpExpiredRealTimeUrlCheckVerdicts() { + if (stored_verdict_count_real_time_url_check_ == 0) { + return; + } ContentSettingsForOneType safe_browsing_url_check_data_settings; content_settings_->GetSettingsForOneType( ContentSettingsType::SAFE_BROWSING_URL_CHECK_DATA, std::string(), @@ -796,8 +803,8 @@ size_t VerdictCacheManager::GetRealTimeUrlCheckVerdictCountForURL( const GURL& url) { std::unique_ptr<base::DictionaryValue> cache_dictionary = base::DictionaryValue::From(content_settings_->GetWebsiteSetting( - url, GURL(), ContentSettingsType::PASSWORD_PROTECTION, std::string(), - nullptr)); + url, GURL(), ContentSettingsType::SAFE_BROWSING_URL_CHECK_DATA, + std::string(), nullptr)); if (!cache_dictionary || cache_dictionary->empty()) return 0; base::Value* verdict_dictionary = diff --git a/chromium/components/safe_browsing/core/verdict_cache_manager.h b/chromium/components/safe_browsing/core/verdict_cache_manager.h index 915b6577374..1865adf6c69 100644 --- a/chromium/components/safe_browsing/core/verdict_cache_manager.h +++ b/chromium/components/safe_browsing/core/verdict_cache_manager.h @@ -128,8 +128,8 @@ class VerdictCacheManager : public history::HistoryServiceObserver, // This method is only used for testing. size_t GetRealTimeUrlCheckVerdictCountForURL(const GURL& url); - // This method is only used for testing. - int GetStoredRealTimeUrlCheckVerdictCount() { + // This method is only used for testing and logging metrics. + int stored_verdict_count_real_time_url_check() { return stored_verdict_count_real_time_url_check_; } @@ -141,7 +141,7 @@ class VerdictCacheManager : public history::HistoryServiceObserver, base::Optional<size_t> stored_verdict_count_password_entry_; // Number of verdict stored for this profile for real time url check pings. - // This is only used for testing. + // This is used for testing, logging metrics and cleaning up during shutdown. int stored_verdict_count_real_time_url_check_ = 0; ScopedObserver<history::HistoryService, history::HistoryServiceObserver> diff --git a/chromium/components/safe_browsing/core/verdict_cache_manager_unittest.cc b/chromium/components/safe_browsing/core/verdict_cache_manager_unittest.cc index 1f254d48600..db01ae4b247 100644 --- a/chromium/components/safe_browsing/core/verdict_cache_manager_unittest.cc +++ b/chromium/components/safe_browsing/core/verdict_cache_manager_unittest.cc @@ -6,6 +6,7 @@ #include "base/base64.h" #include "base/memory/scoped_refptr.h" +#include "base/test/metrics/histogram_tester.h" #include "base/values.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/safe_browsing/core/common/test_task_environment.h" @@ -326,6 +327,9 @@ TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdict) { ASSERT_EQ(2u, cache_manager_->GetStoredPhishGuardVerdictCount( LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE)); + // Prepare 2 verdicts for SAFE_BROWSING_URL_CHECK_DATA: + // (1) "www.example.com/" expired + // (2) "www.example.com/path" valid RTLookupResponse response; AddThreatInfoToResponse(response, RTLookupResponse::ThreatInfo::DANGEROUS, RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING, 0, @@ -338,7 +342,7 @@ TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdict) { cache_manager_->CacheRealTimeUrlVerdict(GURL("https://www.example.com/"), response, base::Time::Now(), /* store_old_cache */ false); - ASSERT_EQ(2, cache_manager_->GetStoredRealTimeUrlCheckVerdictCount()); + ASSERT_EQ(2, cache_manager_->stored_verdict_count_real_time_url_check()); cache_manager_->CleanUpExpiredVerdicts(); @@ -346,7 +350,7 @@ TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdict) { LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); ASSERT_EQ(1u, cache_manager_->GetStoredPhishGuardVerdictCount( LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE)); - ASSERT_EQ(1, cache_manager_->GetStoredRealTimeUrlCheckVerdictCount()); + ASSERT_EQ(1, cache_manager_->stored_verdict_count_real_time_url_check()); LoginReputationClientResponse actual_verdict; password_type.set_account_type(ReusedPasswordAccountType::GSUITE); // Has cached PASSWORD_REUSE_EVENT verdict for foo.com/abc/. @@ -388,6 +392,18 @@ TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdict) { GURL("https://bar.com/xyz/index.jsp"), LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, password_type, &actual_verdict)); + + RTLookupResponse::ThreatInfo actual_real_time_threat_info; + // No cached SAFE_BROWSING_URL_CHECK_DATA verdict for www.example.com/. + EXPECT_EQ( + RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED, + cache_manager_->GetCachedRealTimeUrlVerdict( + GURL("https://www.example.com/"), &actual_real_time_threat_info)); + // Has cached SAFE_BROWSING_URL_CHECK_DATA verdict for www.example.com/path. + EXPECT_EQ( + RTLookupResponse::ThreatInfo::DANGEROUS, + cache_manager_->GetCachedRealTimeUrlVerdict( + GURL("https://www.example.com/path"), &actual_real_time_threat_info)); } TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdictWithInvalidEntry) { @@ -443,6 +459,7 @@ TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdictWithInvalidEntry) { } TEST_F(VerdictCacheManagerTest, TestCanRetrieveCachedRealTimeUrlCheckVerdict) { + base::HistogramTester histograms; GURL url("https://www.example.com/path"); RTLookupResponse response; @@ -465,6 +482,9 @@ TEST_F(VerdictCacheManagerTest, TestCanRetrieveCachedRealTimeUrlCheckVerdict) { EXPECT_EQ(60, out_verdict.cache_duration_sec()); EXPECT_EQ(RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING, out_verdict.threat_type()); + histograms.ExpectUniqueSample( + "SafeBrowsing.RT.CacheManager.RealTimeVerdictCount", + /* sample */ 2, /* expected_count */ 1); } TEST_F(VerdictCacheManagerTest, @@ -545,6 +565,7 @@ TEST_F(VerdictCacheManagerTest, cache_manager_->RemoveContentSettingsOnURLsDeleted(false /* all_history */, deleted_urls); + EXPECT_EQ(0, cache_manager_->stored_verdict_count_real_time_url_check()); EXPECT_EQ(RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED, cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict)); } @@ -629,6 +650,7 @@ TEST_F(VerdictCacheManagerTest, TestExactMatching) { } TEST_F(VerdictCacheManagerTest, TestMatchingTypeNotSet) { + base::HistogramTester histograms; std::string cache_expression = "a.example.test/path1"; GURL url("https://a.example.test/path1"); @@ -646,6 +668,9 @@ TEST_F(VerdictCacheManagerTest, TestMatchingTypeNotSet) { // If |cache_expression_match_type| is not set, ignore this cache. EXPECT_EQ(RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED, cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict)); + histograms.ExpectBucketCount( + "SafeBrowsing.RT.CacheManager.RealTimeVerdictCount", + /* sample */ 0, /* expected_count */ 1); new_threat_info->set_cache_expression_match_type( RTLookupResponse::ThreatInfo::EXACT_MATCH); @@ -654,6 +679,9 @@ TEST_F(VerdictCacheManagerTest, TestMatchingTypeNotSet) { // Should be able to get the cache if |cache_expression_match_type| is set. EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS, cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict)); + histograms.ExpectBucketCount( + "SafeBrowsing.RT.CacheManager.RealTimeVerdictCount", + /* sample */ 1, /* expected_count */ 1); } TEST_F(VerdictCacheManagerTest, TestReadOldRealTimeUrlCheckCacheNotCrash) { diff --git a/chromium/components/safe_browsing/ios/browser/BUILD.gn b/chromium/components/safe_browsing/ios/browser/BUILD.gn new file mode 100644 index 00000000000..87ca89f183a --- /dev/null +++ b/chromium/components/safe_browsing/ios/browser/BUILD.gn @@ -0,0 +1,18 @@ +# Copyright 2020 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. + +source_set("allow_list") { + sources = [ + "safe_browsing_url_allow_list.h", + "safe_browsing_url_allow_list.mm", + ] + + deps = [ + "//components/safe_browsing/core/db:v4_protocol_manager_util", + "//ios/web/public", + "//url", + ] + + configs += [ "//build/config/compiler:enable_arc" ] +} diff --git a/chromium/components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h b/chromium/components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h new file mode 100644 index 00000000000..3253e3bdea6 --- /dev/null +++ b/chromium/components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h @@ -0,0 +1,131 @@ +// Copyright 2020 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. + +#ifndef COMPONENTS_SAFE_BROWSING_IOS_BROWSER_SAFE_BROWSING_URL_ALLOW_LIST_H_ +#define COMPONENTS_SAFE_BROWSING_IOS_BROWSER_SAFE_BROWSING_URL_ALLOW_LIST_H_ + +#include <map> +#include <set> + +#include "base/observer_list.h" +#include "base/observer_list_types.h" +#include "components/safe_browsing/core/db/v4_protocol_manager_util.h" +#import "ios/web/public/web_state_user_data.h" +#include "url/gurl.h" + +// SafeBrowsingUrlAllowList tracks the whitelist decisions for URLs for a given +// threat type, as well as decisions that are pending. Decisions are stored for +// URLs with empty paths, meaning that whitelisted threats are allowed for the +// entire domain. +class SafeBrowsingUrlAllowList + : public web::WebStateUserData<SafeBrowsingUrlAllowList> { + public: + // Enum describing the policy for navigations with a particular threat type to + // a URL. + enum class Policy : short { kDisallowed, kPending, kAllowed }; + + // Observer class for the allow list. + class Observer : public base::CheckedObserver { + public: + // Called when the policy for navigations to |url| with |threat_type| is + // updated to |policy|. + virtual void ThreatPolicyUpdated(SafeBrowsingUrlAllowList* allow_list, + const GURL& url, + safe_browsing::SBThreatType threat_type, + Policy policy) {} + + // Called when the policies for navigations to |url| with the threats in + // |threat_types| are updated to |policy|. + virtual void ThreatPolicyBatchUpdated( + SafeBrowsingUrlAllowList* allow_list, + const GURL& url, + const std::set<safe_browsing::SBThreatType>& threat_type, + Policy policy) {} + + // Called when |allow_list| is about to be destroyed. + virtual void SafeBrowsingAllowListDestroyed( + SafeBrowsingUrlAllowList* allow_list) {} + }; + + ~SafeBrowsingUrlAllowList() override; + + // Adds and removes observers. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Returns whether unsafe navigations to |url| are allowed. If |threat_types| + // is non-null, it is populated with the allowed threat types. + bool AreUnsafeNavigationsAllowed( + const GURL& url, + std::set<safe_browsing::SBThreatType>* threat_types = nullptr) const; + + // Allows future unsafe navigations to |url| that encounter threats with + // |threat_type|. + void AllowUnsafeNavigations(const GURL& url, + safe_browsing::SBThreatType threat_type); + + // Prohibits all previously allowed navigations for |url|. + void DisallowUnsafeNavigations(const GURL& url); + + // Returns whether there are pending unsafe navigation decisions for |url|. + // If |threat_types| is non-null, it is populated with the pending threat + // types. + bool IsUnsafeNavigationDecisionPending( + const GURL& url, + std::set<safe_browsing::SBThreatType>* threat_types = nullptr) const; + + // Records that a navigation to |url| has encountered |threat_type|, but the + // user has not yet chosen whether to allow the navigation. + void AddPendingUnsafeNavigationDecision( + const GURL& url, + safe_browsing::SBThreatType threat_type); + + // Removes all pending decisions for |url|. + void RemovePendingUnsafeNavigationDecisions(const GURL& url); + + private: + explicit SafeBrowsingUrlAllowList(web::WebState* web_state); + friend class web::WebStateUserData<SafeBrowsingUrlAllowList>; + WEB_STATE_USER_DATA_KEY_DECL(); + + // Struct storing the threat types that have been allowed and those for + // which the user has not made a decision yet. + struct UnsafeNavigationDecisions { + UnsafeNavigationDecisions(); + ~UnsafeNavigationDecisions(); + std::set<safe_browsing::SBThreatType> allowed_threats; + std::set<safe_browsing::SBThreatType> pending_threats; + }; + + // Returns a reference to the UnsafeNavigationDecisions for |url|. The path + // is stripped from the URLs before accessing |decisions_| to allow unafe + // navigation decisions to be shared across all URLs for a given domain. + UnsafeNavigationDecisions& GetUnsafeNavigationDecisions(const GURL& url); + const UnsafeNavigationDecisions& GetUnsafeNavigationDecisions( + const GURL& url) const; + + // Setter for the policy for navigations to |url| with |threat_type|. + void SetThreatPolicy(const GURL& url, + safe_browsing::SBThreatType threat_type, + Policy policy); + + // Returns whether the list contains any |policy| decisions for |url|. + // Populates |threat_types| with found threats if provided. + bool ContainsThreats( + const GURL& url, + Policy policy, + std::set<safe_browsing::SBThreatType>* threat_types) const; + + // Disallows all threats that for |url| that are currently allowed under + // |policy|. + void RevertPolicy(const GURL& url, Policy policy); + + // The WebState whose allowed navigations are recorded by this list. + web::WebState* web_state_ = nullptr; + // Map storing the whitelist decisions for each URL. + std::map<GURL, UnsafeNavigationDecisions> decisions_; + base::ObserverList<Observer, /*check_empty=*/true> observers_; +}; + +#endif // COMPONENTS_SAFE_BROWSING_IOS_BROWSER_SAFE_BROWSING_URL_ALLOW_LIST_H_ diff --git a/chromium/components/safe_browsing/ios/browser/safe_browsing_url_allow_list.mm b/chromium/components/safe_browsing/ios/browser/safe_browsing_url_allow_list.mm new file mode 100644 index 00000000000..763f01145e0 --- /dev/null +++ b/chromium/components/safe_browsing/ios/browser/safe_browsing_url_allow_list.mm @@ -0,0 +1,159 @@ +// Copyright 2020 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. + +#import "components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h" + +#import "ios/web/public/web_state.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +using safe_browsing::SBThreatType; + +WEB_STATE_USER_DATA_KEY_IMPL(SafeBrowsingUrlAllowList) + +SafeBrowsingUrlAllowList::SafeBrowsingUrlAllowList(web::WebState* web_state) + : web_state_(web_state) {} + +SafeBrowsingUrlAllowList::~SafeBrowsingUrlAllowList() { + for (auto& observer : observers_) { + observer.SafeBrowsingAllowListDestroyed(this); + } +} + +void SafeBrowsingUrlAllowList::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void SafeBrowsingUrlAllowList::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +bool SafeBrowsingUrlAllowList::AreUnsafeNavigationsAllowed( + const GURL& url, + std::set<SBThreatType>* threat_types) const { + return ContainsThreats(url, Policy::kAllowed, threat_types); +} + +void SafeBrowsingUrlAllowList::AllowUnsafeNavigations( + const GURL& url, + SBThreatType threat_type) { + SetThreatPolicy(url, threat_type, Policy::kAllowed); +} + +void SafeBrowsingUrlAllowList::DisallowUnsafeNavigations(const GURL& url) { + RevertPolicy(url, Policy::kAllowed); +} + +bool SafeBrowsingUrlAllowList::IsUnsafeNavigationDecisionPending( + const GURL& url, + std::set<SBThreatType>* threat_types) const { + return ContainsThreats(url, Policy::kPending, threat_types); +} + +void SafeBrowsingUrlAllowList::AddPendingUnsafeNavigationDecision( + const GURL& url, + SBThreatType threat_type) { + SetThreatPolicy(url, threat_type, Policy::kPending); +} + +void SafeBrowsingUrlAllowList::RemovePendingUnsafeNavigationDecisions( + const GURL& url) { + RevertPolicy(url, Policy::kPending); +} + +#pragma mark - Private + +SafeBrowsingUrlAllowList::UnsafeNavigationDecisions& +SafeBrowsingUrlAllowList::GetUnsafeNavigationDecisions(const GURL& url) { + return decisions_[url.GetWithEmptyPath()]; +} + +const SafeBrowsingUrlAllowList::UnsafeNavigationDecisions& +SafeBrowsingUrlAllowList::GetUnsafeNavigationDecisions(const GURL& url) const { + static UnsafeNavigationDecisions kEmptyDecisions; + const auto& it = decisions_.find(url.GetWithEmptyPath()); + if (it == decisions_.end()) + return kEmptyDecisions; + return it->second; +} + +void SafeBrowsingUrlAllowList::SetThreatPolicy( + const GURL& url, + safe_browsing::SBThreatType threat_type, + Policy policy) { + auto& decisions = GetUnsafeNavigationDecisions(url); + if (policy == Policy::kDisallowed) { + decisions.allowed_threats.erase(threat_type); + decisions.pending_threats.erase(threat_type); + } + if (policy == Policy::kPending) { + decisions.allowed_threats.erase(threat_type); + decisions.pending_threats.insert(threat_type); + } + if (policy == Policy::kAllowed) { + decisions.allowed_threats.insert(threat_type); + decisions.pending_threats.erase(threat_type); + } + for (auto& observer : observers_) { + observer.ThreatPolicyUpdated(this, url, threat_type, policy); + } + web_state_->DidChangeVisibleSecurityState(); +} + +bool SafeBrowsingUrlAllowList::ContainsThreats( + const GURL& url, + Policy policy, + std::set<SBThreatType>* threat_types) const { + const std::set<SBThreatType>* threats_with_policy = nullptr; + switch (policy) { + case Policy::kAllowed: + threats_with_policy = &GetUnsafeNavigationDecisions(url).allowed_threats; + break; + case Policy::kPending: + threats_with_policy = &GetUnsafeNavigationDecisions(url).pending_threats; + break; + case Policy::kDisallowed: + break; + } + if (threats_with_policy && !threats_with_policy->empty()) { + if (threat_types) + *threat_types = *threats_with_policy; + return true; + } + return false; +} + +void SafeBrowsingUrlAllowList::RevertPolicy(const GURL& url, Policy policy) { + std::set<SBThreatType>* decisions_to_revert = nullptr; + switch (policy) { + case Policy::kAllowed: + decisions_to_revert = &GetUnsafeNavigationDecisions(url).allowed_threats; + break; + case Policy::kPending: + decisions_to_revert = &GetUnsafeNavigationDecisions(url).pending_threats; + break; + case Policy::kDisallowed: + break; + } + if (!decisions_to_revert) + return; + + std::set<SBThreatType> disallowed_threats; + disallowed_threats.swap(*decisions_to_revert); + for (auto& observer : observers_) { + observer.ThreatPolicyBatchUpdated(this, url, disallowed_threats, + Policy::kDisallowed); + } + web_state_->DidChangeVisibleSecurityState(); +} + +#pragma mark - SafeBrowsingUrlAllowList::UnsafeNavigationDecisions + +SafeBrowsingUrlAllowList::UnsafeNavigationDecisions:: + UnsafeNavigationDecisions() = default; + +SafeBrowsingUrlAllowList::UnsafeNavigationDecisions:: + ~UnsafeNavigationDecisions() = default; |