// Copyright 2015 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 "base/memory/scoped_ptr.h" #include "base/run_loop.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/browser/safe_browsing/safe_browsing_util.h" #include "chrome/browser/safe_browsing/ui_manager.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/testing_profile.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "content/public/test/test_browser_thread_bundle.h" #include "content/public/test/web_contents_tester.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" using content::BrowserThread; static const char* kGoodURL = "https://www.good.com"; static const char* kBadURL = "https://www.malware.com"; static const char* kBadURLWithPath = "https://www.malware.com/index.html"; static const char* kAnotherBadURL = "https://www.badware.com"; static const char* kLandingURL = "https://www.landing.com"; namespace safe_browsing { class SafeBrowsingCallbackWaiter { public: SafeBrowsingCallbackWaiter() {} bool callback_called() const { return callback_called_; } bool proceed() const { return proceed_; } void OnBlockingPageDone(bool proceed) { DCHECK_CURRENTLY_ON(BrowserThread::UI); callback_called_ = true; proceed_ = proceed; loop_.Quit(); } void OnBlockingPageDoneOnIO(bool proceed) { DCHECK_CURRENTLY_ON(BrowserThread::IO); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDone, base::Unretained(this), proceed)); } void WaitForCallback() { DCHECK_CURRENTLY_ON(BrowserThread::UI); loop_.Run(); } private: bool callback_called_ = false; bool proceed_ = false; base::RunLoop loop_; }; class SafeBrowsingUIManagerTest : public ChromeRenderViewHostTestHarness { public: SafeBrowsingUIManagerTest() : ui_manager_(new SafeBrowsingUIManager(NULL)) {} ~SafeBrowsingUIManagerTest() override{}; void SetUp() override { SetThreadBundleOptions(content::TestBrowserThreadBundle::REAL_IO_THREAD); ChromeRenderViewHostTestHarness::SetUp(); } void TearDown() override { ChromeRenderViewHostTestHarness::TearDown(); } bool IsWhitelisted(SafeBrowsingUIManager::UnsafeResource resource) { return ui_manager_->IsWhitelisted(resource); } void AddToWhitelist(SafeBrowsingUIManager::UnsafeResource resource) { ui_manager_->AddToWhitelist(resource); } SafeBrowsingUIManager::UnsafeResource MakeUnsafeResource( const char* url, bool is_subresource) { SafeBrowsingUIManager::UnsafeResource resource; resource.url = GURL(url); resource.is_subresource = is_subresource; resource.render_process_host_id = web_contents()->GetRenderProcessHost()->GetID(); resource.render_frame_id = web_contents()->GetMainFrame()->GetRoutingID(); resource.threat_type = SB_THREAT_TYPE_URL_MALWARE; return resource; } SafeBrowsingUIManager::UnsafeResource MakeUnsafeResourceAndStartNavigation( const char* url) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResource(url, false /* is_subresource */); // The WC doesn't have a URL without a navigation. A main-frame malware // unsafe resource must be a pending navigation. content::WebContentsTester::For(web_contents())->StartNavigation(GURL(url)); return resource; } void SimulateBlockingPageDone( const std::vector& resources, bool proceed) { ui_manager_->OnBlockingPageDone(resources, proceed); } private: scoped_refptr ui_manager_; }; TEST_F(SafeBrowsingUIManagerTest, Whitelist) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResourceAndStartNavigation(kBadURL); AddToWhitelist(resource); EXPECT_TRUE(IsWhitelisted(resource)); } TEST_F(SafeBrowsingUIManagerTest, WhitelistIgnoresSitesNotAdded) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResourceAndStartNavigation(kGoodURL); EXPECT_FALSE(IsWhitelisted(resource)); } TEST_F(SafeBrowsingUIManagerTest, WhitelistIgnoresPath) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResourceAndStartNavigation(kBadURL); AddToWhitelist(resource); EXPECT_TRUE(IsWhitelisted(resource)); content::WebContentsTester::For(web_contents())->CommitPendingNavigation(); SafeBrowsingUIManager::UnsafeResource resource_path = MakeUnsafeResourceAndStartNavigation(kBadURLWithPath); EXPECT_TRUE(IsWhitelisted(resource_path)); } TEST_F(SafeBrowsingUIManagerTest, WhitelistIgnoresThreatType) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResourceAndStartNavigation(kBadURL); AddToWhitelist(resource); EXPECT_TRUE(IsWhitelisted(resource)); SafeBrowsingUIManager::UnsafeResource resource_phishing = MakeUnsafeResource(kBadURL, false /* is_subresource */); resource_phishing.threat_type = SB_THREAT_TYPE_URL_PHISHING; EXPECT_TRUE(IsWhitelisted(resource_phishing)); } TEST_F(SafeBrowsingUIManagerTest, WhitelistWithUnrelatedPendingLoad) { // Commit load of landing page. NavigateAndCommit(GURL(kLandingURL)); { // Simulate subresource malware hit on the landing page. SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResource(kBadURL, true /* is_subresource */); // Start pending load to unrelated site. content::WebContentsTester::For(web_contents()) ->StartNavigation(GURL(kGoodURL)); // Whitelist the resource on the landing page. AddToWhitelist(resource); EXPECT_TRUE(IsWhitelisted(resource)); } // Commit the pending load of unrelated site. content::WebContentsTester::For(web_contents())->CommitPendingNavigation(); { // The unrelated site is not on the whitelist, even if the same subresource // was on it. SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResource(kBadURL, true /* is_subresource */); EXPECT_FALSE(IsWhitelisted(resource)); } // Navigate back to the original landing url. NavigateAndCommit(GURL(kLandingURL)); { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResource(kBadURL, true /* is_subresource */); // Original resource url is whitelisted. EXPECT_TRUE(IsWhitelisted(resource)); } { // A different malware subresource on the same page is also whitelisted. // (The whitelist is by the page url, not the resource url.) SafeBrowsingUIManager::UnsafeResource resource2 = MakeUnsafeResource(kAnotherBadURL, true /* is_subresource */); EXPECT_TRUE(IsWhitelisted(resource2)); } } TEST_F(SafeBrowsingUIManagerTest, UICallbackProceed) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResourceAndStartNavigation(kBadURL); SafeBrowsingCallbackWaiter waiter; resource.callback = base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDone, base::Unretained(&waiter)); resource.callback_thread = BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); std::vector resources; resources.push_back(resource); SimulateBlockingPageDone(resources, true); EXPECT_TRUE(IsWhitelisted(resource)); waiter.WaitForCallback(); EXPECT_TRUE(waiter.callback_called()); EXPECT_TRUE(waiter.proceed()); } TEST_F(SafeBrowsingUIManagerTest, UICallbackDontProceed) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResourceAndStartNavigation(kBadURL); SafeBrowsingCallbackWaiter waiter; resource.callback = base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDone, base::Unretained(&waiter)); resource.callback_thread = BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); std::vector resources; resources.push_back(resource); SimulateBlockingPageDone(resources, false); EXPECT_FALSE(IsWhitelisted(resource)); waiter.WaitForCallback(); EXPECT_TRUE(waiter.callback_called()); EXPECT_FALSE(waiter.proceed()); } TEST_F(SafeBrowsingUIManagerTest, IOCallbackProceed) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResourceAndStartNavigation(kBadURL); SafeBrowsingCallbackWaiter waiter; resource.callback = base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDoneOnIO, base::Unretained(&waiter)); resource.callback_thread = BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO); std::vector resources; resources.push_back(resource); SimulateBlockingPageDone(resources, true); EXPECT_TRUE(IsWhitelisted(resource)); waiter.WaitForCallback(); EXPECT_TRUE(waiter.callback_called()); EXPECT_TRUE(waiter.proceed()); } TEST_F(SafeBrowsingUIManagerTest, IOCallbackDontProceed) { SafeBrowsingUIManager::UnsafeResource resource = MakeUnsafeResourceAndStartNavigation(kBadURL); SafeBrowsingCallbackWaiter waiter; resource.callback = base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDoneOnIO, base::Unretained(&waiter)); resource.callback_thread = BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO); std::vector resources; resources.push_back(resource); SimulateBlockingPageDone(resources, false); EXPECT_FALSE(IsWhitelisted(resource)); waiter.WaitForCallback(); EXPECT_TRUE(waiter.callback_called()); EXPECT_FALSE(waiter.proceed()); } } // namespace safe_browsing