// 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