// Copyright (c) 2011 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.
//
// This test creates a safebrowsing service using test safebrowsing database
// and a test protocol manager. It is used to test logics in safebrowsing
// service.
#include "base/command_line.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram.h"
#include "crypto/sha2.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/safe_browsing/safe_browsing_database.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/safe_browsing/safe_browsing_util.h"
#include "chrome/browser/safe_browsing/protocol_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/resource_dispatcher_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_view.h"
using base::Histogram;
using base::StatisticsRecorder;
// A SafeBrowingDatabase class that allows us to inject the malicious URLs.
class TestSafeBrowsingDatabase : public SafeBrowsingDatabase {
public:
TestSafeBrowsingDatabase() {}
virtual ~TestSafeBrowsingDatabase() {}
// Initializes the database with the given filename.
virtual void Init(const FilePath& filename) {}
// Deletes the current database and creates a new one.
virtual bool ResetDatabase() {
badurls_.clear();
return true;
}
// Called on the IO thread to check if the given URL is safe or not. If we
// can synchronously determine that the URL is safe, CheckUrl returns true,
// otherwise it returns false.
virtual bool ContainsBrowseUrl(const GURL& url,
std::string* matching_list,
std::vector<SBPrefix>* prefix_hits,
std::vector<SBFullHashResult>* full_hits,
base::Time last_update) {
std::vector<GURL> urls(1, url);
return ContainsUrl(safe_browsing_util::kMalwareList,
safe_browsing_util::kPhishingList,
urls, prefix_hits, full_hits);
}
virtual bool ContainsDownloadUrl(const std::vector<GURL>& urls,
std::vector<SBPrefix>* prefix_hits) {
std::vector<SBFullHashResult> full_hits;
bool found = ContainsUrl(safe_browsing_util::kBinUrlList,
safe_browsing_util::kBinHashList,
urls, prefix_hits, &full_hits);
if (!found)
return false;
DCHECK_LE(1U, prefix_hits->size());
return true;
}
virtual bool ContainsDownloadHashPrefix(const SBPrefix& prefix) {
return download_digest_prefix_.count(prefix) > 0;
}
virtual bool ContainsCsdWhitelistedUrl(const GURL& url) {
return true;
}
virtual bool UpdateStarted(std::vector<SBListChunkRanges>* lists) {
ADD_FAILURE() << "Not implemented.";
return false;
}
virtual void InsertChunks(const std::string& list_name,
const SBChunkList& chunks) {
ADD_FAILURE() << "Not implemented.";
}
virtual void DeleteChunks(const std::vector<SBChunkDelete>& chunk_deletes) {
ADD_FAILURE() << "Not implemented.";
}
virtual void UpdateFinished(bool update_succeeded) {
ADD_FAILURE() << "Not implemented.";
}
virtual void CacheHashResults(const std::vector<SBPrefix>& prefixes,
const std::vector<SBFullHashResult>& full_hits) {
// Do nothing for the cache.
return;
}
// Fill up the database with test URL.
void AddUrl(const GURL& url,
const std::string& list_name,
const std::vector<SBPrefix>& prefix_hits,
const std::vector<SBFullHashResult>& full_hits) {
badurls_[url.spec()].list_name = list_name;
badurls_[url.spec()].prefix_hits = prefix_hits;
badurls_[url.spec()].full_hits = full_hits;
}
// Fill up the database with test hash digest.
void AddDownloadPrefix(SBPrefix prefix) {
download_digest_prefix_.insert(prefix);
}
private:
struct Hits {
std::string list_name;
std::vector<SBPrefix> prefix_hits;
std::vector<SBFullHashResult> full_hits;
};
bool ContainsUrl(const std::string& list_name0,
const std::string& list_name1,
const std::vector<GURL>& urls,
std::vector<SBPrefix>* prefix_hits,
std::vector<SBFullHashResult>* full_hits) {
bool hit = false;
for (size_t i = 0; i < urls.size(); ++i) {
const GURL& url = urls[i];
base::hash_map<std::string, Hits>::const_iterator
badurls_it = badurls_.find(url.spec());
if (badurls_it == badurls_.end())
continue;
if (badurls_it->second.list_name == list_name0 ||
badurls_it->second.list_name == list_name1) {
prefix_hits->insert(prefix_hits->end(),
badurls_it->second.prefix_hits.begin(),
badurls_it->second.prefix_hits.end());
full_hits->insert(full_hits->end(),
badurls_it->second.full_hits.begin(),
badurls_it->second.full_hits.end());
hit = true;
}
}
return hit;
}
base::hash_map<std::string, Hits> badurls_;
base::hash_set<SBPrefix> download_digest_prefix_;
};
// Factory that creates TestSafeBrowsingDatabase instances.
class TestSafeBrowsingDatabaseFactory : public SafeBrowsingDatabaseFactory {
public:
TestSafeBrowsingDatabaseFactory() : db_(NULL) {}
virtual ~TestSafeBrowsingDatabaseFactory() {}
virtual SafeBrowsingDatabase* CreateSafeBrowsingDatabase(
bool enable_download_protection,
bool enable_client_side_whitelist) {
db_ = new TestSafeBrowsingDatabase();
return db_;
}
TestSafeBrowsingDatabase* GetDb() {
return db_;
}
private:
// Owned by the SafebrowsingService.
TestSafeBrowsingDatabase* db_;
};
// A TestProtocolManager that could return fixed responses from
// safebrowsing server for testing purpose.
class TestProtocolManager : public SafeBrowsingProtocolManager {
public:
TestProtocolManager(SafeBrowsingService* sb_service,
const std::string& client_name,
const std::string& client_key,
const std::string& wrapped_key,
net::URLRequestContextGetter* request_context_getter,
const std::string& info_url_prefix,
const std::string& mackey_url_prefix,
bool disable_auto_update)
: SafeBrowsingProtocolManager(sb_service, client_name, client_key,
wrapped_key, request_context_getter,
info_url_prefix, mackey_url_prefix,
disable_auto_update),
sb_service_(sb_service),
delay_ms_(0) {
}
// This function is called when there is a prefix hit in local safebrowsing
// database and safebrowsing service issues a get hash request to backends.
// We return a result from the prefilled full_hashes_ hash_map to simulate
// server's response. At the same time, latency is added to simulate real
// life network issues.
virtual void GetFullHash(SafeBrowsingService::SafeBrowsingCheck* check,
const std::vector<SBPrefix>& prefixes) {
// When we get a valid response, always cache the result.
bool cancache = true;
BrowserThread::PostDelayedTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(
sb_service_, &SafeBrowsingService::HandleGetHashResults,
check, full_hashes_, cancache),
delay_ms_);
}
// Prepare the GetFullHash results for the next request.
void SetGetFullHashResponse(const SBFullHashResult& full_hash_result) {
full_hashes_.clear();
full_hashes_.push_back(full_hash_result);
}
void IntroduceDelay(int64 ms) {
delay_ms_ = ms;
}
private:
std::vector<SBFullHashResult> full_hashes_;
SafeBrowsingService* sb_service_;
int64 delay_ms_;
};
// Factory that creates TestProtocolManager instances.
class TestSBProtocolManagerFactory : public SBProtocolManagerFactory {
public:
TestSBProtocolManagerFactory() : pm_(NULL) {}
virtual ~TestSBProtocolManagerFactory() {}
virtual SafeBrowsingProtocolManager* CreateProtocolManager(
SafeBrowsingService* sb_service,
const std::string& client_name,
const std::string& client_key,
const std::string& wrapped_key,
net::URLRequestContextGetter* request_context_getter,
const std::string& info_url_prefix,
const std::string& mackey_url_prefix,
bool disable_auto_update) {
pm_ = new TestProtocolManager(
sb_service, client_name, client_key, wrapped_key,
request_context_getter, info_url_prefix, mackey_url_prefix,
disable_auto_update);
return pm_;
}
TestProtocolManager* GetProtocolManager() {
return pm_;
}
private:
// Owned by the SafebrowsingService.
TestProtocolManager* pm_;
};
// Tests the safe browsing blocking page in a browser.
class SafeBrowsingServiceTest : public InProcessBrowserTest {
public:
SafeBrowsingServiceTest() {
}
static void GenUrlFullhashResult(const GURL& url,
const std::string& list_name,
int add_chunk_id,
SBFullHashResult* full_hash) {
std::string host;
std::string path;
safe_browsing_util::CanonicalizeUrl(url, &host, &path, NULL);
crypto::SHA256HashString(host + path, &full_hash->hash,
sizeof(SBFullHash));
full_hash->list_name = list_name;
full_hash->add_chunk_id = add_chunk_id;
}
static void GenDigestFullhashResult(const std::string& full_digest,
const std::string& list_name,
int add_chunk_id,
SBFullHashResult* full_hash) {
safe_browsing_util::StringToSBFullHash(full_digest, &full_hash->hash);
full_hash->list_name = list_name;
full_hash->add_chunk_id = add_chunk_id;
}
virtual void SetUp() {
// InProcessBrowserTest::SetUp() intantiates SafebrowsingService and
// RegisterFactory has to be called before SafeBrowsingService is created.
SafeBrowsingDatabase::RegisterFactory(&db_factory_);
SafeBrowsingProtocolManager::RegisterFactory(&pm_factory_);
InProcessBrowserTest::SetUp();
}
virtual void TearDown() {
InProcessBrowserTest::TearDown();
// Unregister test factories after InProcessBrowserTest::TearDown
// (which destructs SafeBrowsingService).
SafeBrowsingDatabase::RegisterFactory(NULL);
SafeBrowsingProtocolManager::RegisterFactory(NULL);
}
virtual void SetUpCommandLine(CommandLine* command_line) {
// Makes sure the auto update is not triggered during the test.
// This test will fill up the database using testing prefixes
// and urls.
command_line->AppendSwitch(switches::kSbDisableAutoUpdate);
}
virtual void SetUpInProcessBrowserTestFixture() {
ASSERT_TRUE(test_server()->Start());
}
// This will setup the "url" prefix in database and prepare protocol manager
// to response with |full_hash| for get full hash request.
void SetupResponseForUrl(const GURL& url, const SBFullHashResult& full_hash) {
std::vector<SBPrefix> prefix_hits;
prefix_hits.push_back(full_hash.hash.prefix);
// Make sure the full hits is empty unless we need to test the
// full hash is hit in database's local cache.
std::vector<SBFullHashResult> empty_full_hits;
TestSafeBrowsingDatabase* db = db_factory_.GetDb();
db->AddUrl(url, full_hash.list_name, prefix_hits, empty_full_hits);
TestProtocolManager* pm = pm_factory_.GetProtocolManager();
pm->SetGetFullHashResponse(full_hash);
}
// This will setup the binary digest prefix in database and prepare protocol
// manager to response with |full_hash| for get full hash request.
void SetupResponseForDigest(const std::string& digest,
const SBFullHashResult& hash_result) {
TestSafeBrowsingDatabase* db = db_factory_.GetDb();
SBFullHash full_hash;
safe_browsing_util::StringToSBFullHash(digest, &full_hash);
db->AddDownloadPrefix(full_hash.prefix);
TestProtocolManager* pm = pm_factory_.GetProtocolManager();
pm->SetGetFullHashResponse(hash_result);
}
bool ShowingInterstitialPage() {
TabContents* contents = browser()->GetSelectedTabContents();
InterstitialPage* interstitial_page = contents->interstitial_page();
return interstitial_page != NULL;
}
void IntroduceGetHashDelay(int64 ms) {
pm_factory_.GetProtocolManager()->IntroduceDelay(ms);
}
int64 DownloadUrlCheckTimeout(SafeBrowsingService* sb_service) {
return sb_service->download_urlcheck_timeout_ms_;
}
int64 DownloadHashCheckTimeout(SafeBrowsingService* sb_service) {
return sb_service->download_hashcheck_timeout_ms_;
}
void SetDownloadUrlCheckTimeout(SafeBrowsingService* sb_service, int64 ms) {
sb_service->download_urlcheck_timeout_ms_ = ms;
}
void SetDownloadHashCheckTimeout(SafeBrowsingService* sb_service, int64 ms) {
sb_service->download_hashcheck_timeout_ms_ = ms;
}
private:
TestSafeBrowsingDatabaseFactory db_factory_;
TestSBProtocolManagerFactory pm_factory_;
DISALLOW_COPY_AND_ASSIGN(SafeBrowsingServiceTest);
};
namespace {
const char kEmptyPage[] = "files/empty.html";
const char kMalwareFile[] = "files/downloads/dangerous/dangerous.exe";
const char kMalwareIframe[] = "files/safe_browsing/malware_iframe.html";
const char kMalwarePage[] = "files/safe_browsing/malware.html";
// This test goes through DownloadResourceHandler.
IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, Malware) {
GURL url = test_server()->GetURL(kEmptyPage);
// After adding the url to safebrowsing database and getfullhash result,
// we should see the interstitial page.
SBFullHashResult malware_full_hash;
int chunk_id = 0;
GenUrlFullhashResult(url, safe_browsing_util::kMalwareList, chunk_id,
&malware_full_hash);
SetupResponseForUrl(url, malware_full_hash);
ui_test_utils::NavigateToURL(browser(), url);
EXPECT_TRUE(ShowingInterstitialPage());
}
const char kPrefetchMalwarePage[] = "files/safe_browsing/prefetch_malware.html";
// This test confirms that prefetches don't themselves get the
// interstitial treatment.
IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, Prefetch) {
GURL url = test_server()->GetURL(kPrefetchMalwarePage);
GURL malware_url = test_server()->GetURL(kMalwarePage);
class SetPrefetchForTest {
public:
explicit SetPrefetchForTest(bool prefetch)
: old_prefetch_state_(ResourceDispatcherHost::is_prefetch_enabled()) {
ResourceDispatcherHost::set_is_prefetch_enabled(prefetch);
}
~SetPrefetchForTest() {
ResourceDispatcherHost::set_is_prefetch_enabled(old_prefetch_state_);
}
private:
bool old_prefetch_state_;
} set_prefetch_for_test(true);
// Even though we have added this uri to the safebrowsing database and
// getfullhash result, we should not see the interstitial page since the
// only malware was a prefetch target.
SBFullHashResult malware_full_hash;
int chunk_id = 0;
GenUrlFullhashResult(malware_url, safe_browsing_util::kMalwareList,
chunk_id, &malware_full_hash);
SetupResponseForUrl(malware_url, malware_full_hash);
ui_test_utils::NavigateToURL(browser(), url);
EXPECT_FALSE(ShowingInterstitialPage());
// However, when we navigate to the malware page, we should still get
// the interstitial.
ui_test_utils::NavigateToURL(browser(), malware_url);
EXPECT_TRUE(ShowingInterstitialPage());
}
} // namespace
class TestSBClient
: public base::RefCountedThreadSafe<TestSBClient>,
public SafeBrowsingService::Client {
public:
TestSBClient() : result_(SafeBrowsingService::SAFE),
safe_browsing_service_(g_browser_process->
resource_dispatcher_host()->
safe_browsing_service()) {
}
int GetResult() {
return result_;
}
void CheckDownloadUrl(const std::vector<GURL>& url_chain) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this,
&TestSBClient::CheckDownloadUrlOnIOThread,
url_chain));
ui_test_utils::RunMessageLoop(); // Will stop in OnDownloadUrlCheckResult.
}
void CheckDownloadHash(const std::string& full_hash) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this,
&TestSBClient::CheckDownloadHashOnIOThread,
full_hash));
ui_test_utils::RunMessageLoop(); // Will stop in OnDownloadHashCheckResult.
}
private:
void CheckDownloadUrlOnIOThread(const std::vector<GURL>& url_chain) {
safe_browsing_service_->CheckDownloadUrl(url_chain, this);
}
void CheckDownloadHashOnIOThread(const std::string& full_hash) {
safe_browsing_service_->CheckDownloadHash(full_hash, this);
}
// Called when the result of checking a download URL is known.
void OnDownloadUrlCheckResult(const std::vector<GURL>& url_chain,
SafeBrowsingService::UrlCheckResult result) {
result_ = result;
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
NewRunnableMethod(this, &TestSBClient::DownloadCheckDone));
}
// Called when the result of checking a download hash is known.
void OnDownloadHashCheckResult(const std::string& hash,
SafeBrowsingService::UrlCheckResult result) {
result_ = result;
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
NewRunnableMethod(this, &TestSBClient::DownloadCheckDone));
}
void DownloadCheckDone() {
MessageLoopForUI::current()->Quit();
}
SafeBrowsingService::UrlCheckResult result_;
SafeBrowsingService* safe_browsing_service_;
DISALLOW_COPY_AND_ASSIGN(TestSBClient);
};
// These tests use SafeBrowsingService::Client to directly interact with
// SafeBrowsingService.
namespace {
IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, CheckDownloadUrl) {
GURL badbin_url = test_server()->GetURL(kMalwareFile);
std::vector<GURL> badbin_urls(1, badbin_url);
scoped_refptr<TestSBClient> client(new TestSBClient);
client->CheckDownloadUrl(badbin_urls);
// Since badbin_url is not in database, it is considered to be safe.
EXPECT_EQ(SafeBrowsingService::SAFE, client->GetResult());
SBFullHashResult full_hash_result;
int chunk_id = 0;
GenUrlFullhashResult(badbin_url, safe_browsing_util::kBinUrlList,
chunk_id, &full_hash_result);
SetupResponseForUrl(badbin_url, full_hash_result);
client->CheckDownloadUrl(badbin_urls);
// Now, the badbin_url is not safe since it is added to download database.
EXPECT_EQ(SafeBrowsingService::BINARY_MALWARE_URL, client->GetResult());
}
IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, CheckDownloadUrlRedirects) {
GURL original_url = test_server()->GetURL(kEmptyPage);
GURL badbin_url = test_server()->GetURL(kMalwareFile);
GURL final_url = test_server()->GetURL(kEmptyPage);
std::vector<GURL> badbin_urls;
badbin_urls.push_back(original_url);
badbin_urls.push_back(badbin_url);
badbin_urls.push_back(final_url);
scoped_refptr<TestSBClient> client(new TestSBClient);
client->CheckDownloadUrl(badbin_urls);
// Since badbin_url is not in database, it is considered to be safe.
EXPECT_EQ(SafeBrowsingService::SAFE, client->GetResult());
SBFullHashResult full_hash_result;
int chunk_id = 0;
GenUrlFullhashResult(badbin_url, safe_browsing_util::kBinUrlList,
chunk_id, &full_hash_result);
SetupResponseForUrl(badbin_url, full_hash_result);
client->CheckDownloadUrl(badbin_urls);
// Now, the badbin_url is not safe since it is added to download database.
EXPECT_EQ(SafeBrowsingService::BINARY_MALWARE_URL, client->GetResult());
}
IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, CheckDownloadHash) {
const std::string full_hash = "12345678902234567890323456789012";
scoped_refptr<TestSBClient> client(new TestSBClient);
client->CheckDownloadHash(full_hash);
// Since badbin_url is not in database, it is considered to be safe.
EXPECT_EQ(SafeBrowsingService::SAFE, client->GetResult());
SBFullHashResult full_hash_result;
int chunk_id = 0;
GenDigestFullhashResult(full_hash, safe_browsing_util::kBinHashList,
chunk_id, &full_hash_result);
SetupResponseForDigest(full_hash, full_hash_result);
client->CheckDownloadHash(full_hash);
// Now, the badbin_url is not safe since it is added to download database.
EXPECT_EQ(SafeBrowsingService::BINARY_MALWARE_HASH, client->GetResult());
}
IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, CheckDownloadUrlTimedOut) {
GURL badbin_url = test_server()->GetURL(kMalwareFile);
std::vector<GURL> badbin_urls(1, badbin_url);
scoped_refptr<TestSBClient> client(new TestSBClient);
SBFullHashResult full_hash_result;
int chunk_id = 0;
GenUrlFullhashResult(badbin_url, safe_browsing_util::kBinUrlList,
chunk_id, &full_hash_result);
SetupResponseForUrl(badbin_url, full_hash_result);
client->CheckDownloadUrl(badbin_urls);
// badbin_url is not safe since it is added to download database.
EXPECT_EQ(SafeBrowsingService::BINARY_MALWARE_URL, client->GetResult());
//
// Now introducing delays and we should hit timeout.
//
SafeBrowsingService* sb_service =
g_browser_process->resource_dispatcher_host()->safe_browsing_service();
const int64 kOneSec = 1000;
const int64 kOneMs = 1;
int64 default_urlcheck_timeout = DownloadUrlCheckTimeout(sb_service);
IntroduceGetHashDelay(kOneSec);
SetDownloadUrlCheckTimeout(sb_service, kOneMs);
client->CheckDownloadUrl(badbin_urls);
// There should be a timeout and the hash would be considered as safe.
EXPECT_EQ(SafeBrowsingService::SAFE, client->GetResult());
// Need to set the timeout back to the default value.
SetDownloadHashCheckTimeout(sb_service, default_urlcheck_timeout);
}
IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, CheckDownloadHashTimedOut) {
const std::string full_hash = "12345678902234567890323456789012";
scoped_refptr<TestSBClient> client(new TestSBClient);
SBFullHashResult full_hash_result;
int chunk_id = 0;
GenDigestFullhashResult(full_hash, safe_browsing_util::kBinHashList,
chunk_id, &full_hash_result);
SetupResponseForDigest(full_hash, full_hash_result);
client->CheckDownloadHash(full_hash);
// The badbin_url is not safe since it is added to download database.
EXPECT_EQ(SafeBrowsingService::BINARY_MALWARE_HASH, client->GetResult());
//
// Now introducing delays and we should hit timeout.
//
SafeBrowsingService* sb_service =
g_browser_process->resource_dispatcher_host()->safe_browsing_service();
const int64 kOneSec = 1000;
const int64 kOneMs = 1;
int64 default_hashcheck_timeout = DownloadHashCheckTimeout(sb_service);
IntroduceGetHashDelay(kOneSec);
SetDownloadHashCheckTimeout(sb_service, kOneMs);
client->CheckDownloadHash(full_hash);
// There should be a timeout and the hash would be considered as safe.
EXPECT_EQ(SafeBrowsingService::SAFE, client->GetResult());
// Need to set the timeout back to the default value.
SetDownloadHashCheckTimeout(sb_service, default_hashcheck_timeout);
}
} // namespace