blob: 9a421b543c05eccc0879f79d4d6308256d76638b [file] [log] [blame]
// 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 "chrome/browser/safe_browsing/ui_manager.h"
#include "base/run_loop.h"
#include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.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 "components/safe_browsing_db/safe_browsing_prefs.h"
#include "components/safe_browsing_db/util.h"
#include "components/security_interstitials/core/safe_browsing_error_ui.h"
#include "content/public/browser/navigation_entry.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/browser/web_contents_delegate.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::BindOnce(&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();
SafeBrowsingUIManager::CreateWhitelistForTesting(web_contents());
}
void TearDown() override { ChromeRenderViewHostTestHarness::TearDown(); }
bool IsWhitelisted(security_interstitials::UnsafeResource resource) {
return ui_manager_->IsWhitelisted(resource);
}
void AddToWhitelist(security_interstitials::UnsafeResource resource) {
ui_manager_->AddToWhitelistUrlSet(
SafeBrowsingUIManager::GetMainFrameWhitelistUrlForResourceForTesting(
resource),
web_contents(), false, resource.threat_type);
}
security_interstitials::UnsafeResource MakeUnsafeResource(
const char* url,
bool is_subresource) {
security_interstitials::UnsafeResource resource;
resource.url = GURL(url);
resource.is_subresource = is_subresource;
resource.web_contents_getter =
security_interstitials::UnsafeResource::GetWebContentsGetter(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID());
resource.threat_type = SB_THREAT_TYPE_URL_MALWARE;
return resource;
}
security_interstitials::UnsafeResource MakeUnsafeResourceAndStartNavigation(
const char* url) {
security_interstitials::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<security_interstitials::UnsafeResource>& resources,
bool proceed) {
GURL main_frame_url;
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
if (entry)
main_frame_url = entry->GetURL();
ui_manager_->OnBlockingPageDone(resources, proceed, web_contents(),
main_frame_url);
}
protected:
SafeBrowsingUIManager* ui_manager() { return ui_manager_.get(); }
private:
scoped_refptr<SafeBrowsingUIManager> ui_manager_;
};
TEST_F(SafeBrowsingUIManagerTest, Whitelist) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToWhitelist(resource);
EXPECT_TRUE(IsWhitelisted(resource));
}
TEST_F(SafeBrowsingUIManagerTest, WhitelistIgnoresSitesNotAdded) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kGoodURL);
EXPECT_FALSE(IsWhitelisted(resource));
}
TEST_F(SafeBrowsingUIManagerTest, WhitelistRemembersThreatType) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToWhitelist(resource);
EXPECT_TRUE(IsWhitelisted(resource));
SBThreatType threat_type;
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_TRUE(ui_manager()->IsUrlWhitelistedOrPendingForWebContents(
resource.url, resource.is_subresource, entry,
resource.web_contents_getter.Run(), true, &threat_type));
EXPECT_EQ(resource.threat_type, threat_type);
}
TEST_F(SafeBrowsingUIManagerTest, WhitelistIgnoresPath) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToWhitelist(resource);
EXPECT_TRUE(IsWhitelisted(resource));
content::WebContentsTester::For(web_contents())->CommitPendingNavigation();
security_interstitials::UnsafeResource resource_path =
MakeUnsafeResourceAndStartNavigation(kBadURLWithPath);
EXPECT_TRUE(IsWhitelisted(resource_path));
}
TEST_F(SafeBrowsingUIManagerTest, WhitelistIgnoresThreatType) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToWhitelist(resource);
EXPECT_TRUE(IsWhitelisted(resource));
security_interstitials::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.
security_interstitials::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.
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, true /* is_subresource */);
EXPECT_FALSE(IsWhitelisted(resource));
}
// Navigate back to the original landing url.
NavigateAndCommit(GURL(kLandingURL));
{
security_interstitials::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.)
security_interstitials::UnsafeResource resource2 =
MakeUnsafeResource(kAnotherBadURL, true /* is_subresource */);
EXPECT_TRUE(IsWhitelisted(resource2));
}
}
TEST_F(SafeBrowsingUIManagerTest, UICallbackProceed) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_thread =
BrowserThread::GetTaskRunnerForThread(BrowserThread::UI);
std::vector<security_interstitials::UnsafeResource> 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) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_thread =
BrowserThread::GetTaskRunnerForThread(BrowserThread::UI);
std::vector<security_interstitials::UnsafeResource> 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) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDoneOnIO,
base::Unretained(&waiter));
resource.callback_thread =
BrowserThread::GetTaskRunnerForThread(BrowserThread::IO);
std::vector<security_interstitials::UnsafeResource> 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) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDoneOnIO,
base::Unretained(&waiter));
resource.callback_thread =
BrowserThread::GetTaskRunnerForThread(BrowserThread::IO);
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
SimulateBlockingPageDone(resources, false);
EXPECT_FALSE(IsWhitelisted(resource));
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_FALSE(waiter.proceed());
}
namespace {
// A WebContentsDelegate that records whether
// VisibleSecurityStateChanged() was called.
class SecurityStateWebContentsDelegate : public content::WebContentsDelegate {
public:
SecurityStateWebContentsDelegate() {}
~SecurityStateWebContentsDelegate() override {}
bool visible_security_state_changed() const {
return visible_security_state_changed_;
}
void ClearVisibleSecurityStateChanged() {
visible_security_state_changed_ = false;
}
// WebContentsDelegate:
void VisibleSecurityStateChanged(content::WebContents* source) override {
visible_security_state_changed_ = true;
}
private:
bool visible_security_state_changed_ = false;
DISALLOW_COPY_AND_ASSIGN(SecurityStateWebContentsDelegate);
};
// A test blocking page that does not create windows.
class TestSafeBrowsingBlockingPage : public SafeBrowsingBlockingPage {
public:
TestSafeBrowsingBlockingPage(BaseUIManager* manager,
content::WebContents* web_contents,
const GURL& main_frame_url,
const UnsafeResourceList& unsafe_resources)
: SafeBrowsingBlockingPage(
manager,
web_contents,
main_frame_url,
unsafe_resources,
SafeBrowsingErrorUI::SBErrorDisplayOptions(
BaseBlockingPage::IsMainPageLoadBlocked(unsafe_resources),
false,
false,
false,
false,
false,
BaseBlockingPage::IsMainPageLoadBlocked(unsafe_resources))) {
// Don't delay details at all for the unittest.
SetThreatDetailsProceedDelayForTesting(0);
DontCreateViewForTesting();
}
};
// A factory that creates TestSafeBrowsingBlockingPages.
class TestSafeBrowsingBlockingPageFactory
: public SafeBrowsingBlockingPageFactory {
public:
TestSafeBrowsingBlockingPageFactory() {}
~TestSafeBrowsingBlockingPageFactory() override {}
SafeBrowsingBlockingPage* CreateSafeBrowsingPage(
BaseUIManager* delegate,
content::WebContents* web_contents,
const GURL& main_frame_url,
const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources)
override {
return new TestSafeBrowsingBlockingPage(delegate, web_contents,
main_frame_url, unsafe_resources);
}
};
} // namespace
// Tests that the WebContentsDelegate is notified of a visible security
// state change when a blocking page is shown for a subresource.
TEST_F(SafeBrowsingUIManagerTest,
VisibleSecurityStateChangedForUnsafeSubresource) {
TestSafeBrowsingBlockingPageFactory factory;
SafeBrowsingBlockingPage::RegisterFactory(&factory);
SecurityStateWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
// Simulate a blocking page showing for an unsafe subresource.
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, true /* is_subresource */);
// Needed for showing the blocking page.
resource.threat_source = safe_browsing::ThreatSource::REMOTE;
NavigateAndCommit(GURL("http://example.test"));
delegate.ClearVisibleSecurityStateChanged();
EXPECT_FALSE(delegate.visible_security_state_changed());
ui_manager()->DisplayBlockingPage(resource);
EXPECT_TRUE(delegate.visible_security_state_changed());
// Simulate proceeding through the blocking page.
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::Bind(&SafeBrowsingCallbackWaiter::OnBlockingPageDoneOnIO,
base::Unretained(&waiter));
resource.callback_thread =
BrowserThread::GetTaskRunnerForThread(BrowserThread::IO);
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
delegate.ClearVisibleSecurityStateChanged();
EXPECT_FALSE(delegate.visible_security_state_changed());
SimulateBlockingPageDone(resources, true);
EXPECT_TRUE(delegate.visible_security_state_changed());
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_TRUE(waiter.proceed());
EXPECT_TRUE(IsWhitelisted(resource));
}
} // namespace safe_browsing