// 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. // // Unit tests for the SafeBrowsing storage system. #include "app/sql/connection.h" #include "app/sql/statement.h" #include "base/file_util.h" #include "base/logging.h" #include "base/memory/scoped_temp_dir.h" #include "base/message_loop.h" #include "base/time.h" #include "crypto/sha2.h" #include "chrome/browser/safe_browsing/safe_browsing_database.h" #include "chrome/browser/safe_browsing/safe_browsing_store_file.h" #include "chrome/browser/safe_browsing/safe_browsing_store_unittest_helper.h" #include "content/browser/browser_thread.h" #include "googleurl/src/gurl.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" using base::Time; namespace { SBPrefix Sha256Prefix(const std::string& str) { SBPrefix prefix; crypto::SHA256HashString(str, &prefix, sizeof(prefix)); return prefix; } SBFullHash Sha256Hash(const std::string& str) { SBFullHash hash; crypto::SHA256HashString(str, &hash, sizeof(hash)); return hash; } // Same as InsertAddChunkHostPrefixUrl, but with pre-computed // prefix values. void InsertAddChunkHostPrefixValue(SBChunk* chunk, int chunk_number, const SBPrefix& host_prefix, const SBPrefix& url_prefix) { chunk->chunk_number = chunk_number; chunk->is_add = true; SBChunkHost host; host.host = host_prefix; host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 1); host.entry->set_chunk_id(chunk->chunk_number); host.entry->SetPrefixAt(0, url_prefix); chunk->hosts.push_back(host); } // A helper function that appends one AddChunkHost to chunk with // one url for prefix. void InsertAddChunkHostPrefixUrl(SBChunk* chunk, int chunk_number, const std::string& host_name, const std::string& url) { InsertAddChunkHostPrefixValue(chunk, chunk_number, Sha256Prefix(host_name), Sha256Prefix(url)); } // Same as InsertAddChunkHostPrefixUrl, but with full hashes. void InsertAddChunkHostFullHashes(SBChunk* chunk, int chunk_number, const std::string& host_name, const std::string& url) { chunk->chunk_number = chunk_number; chunk->is_add = true; SBChunkHost host; host.host = Sha256Prefix(host_name); host.entry = SBEntry::Create(SBEntry::ADD_FULL_HASH, 1); host.entry->set_chunk_id(chunk->chunk_number); host.entry->SetFullHashAt(0, Sha256Hash(url)); chunk->hosts.push_back(host); } // Same as InsertAddChunkHostPrefixUrl, but with two urls for prefixes. void InsertAddChunkHost2PrefixUrls(SBChunk* chunk, int chunk_number, const std::string& host_name, const std::string& url1, const std::string& url2) { chunk->chunk_number = chunk_number; chunk->is_add = true; SBChunkHost host; host.host = Sha256Prefix(host_name); host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 2); host.entry->set_chunk_id(chunk->chunk_number); host.entry->SetPrefixAt(0, Sha256Prefix(url1)); host.entry->SetPrefixAt(1, Sha256Prefix(url2)); chunk->hosts.push_back(host); } // Same as InsertAddChunkHost2PrefixUrls, but with full hashes. void InsertAddChunkHost2FullHashes(SBChunk* chunk, int chunk_number, const std::string& host_name, const std::string& url1, const std::string& url2) { chunk->chunk_number = chunk_number; chunk->is_add = true; SBChunkHost host; host.host = Sha256Prefix(host_name); host.entry = SBEntry::Create(SBEntry::ADD_FULL_HASH, 2); host.entry->set_chunk_id(chunk->chunk_number); host.entry->SetFullHashAt(0, Sha256Hash(url1)); host.entry->SetFullHashAt(1, Sha256Hash(url2)); chunk->hosts.push_back(host); } // Same as InsertSubChunkHostPrefixUrl, but with pre-computed // prefix values. void InsertSubChunkHostPrefixValue(SBChunk* chunk, int chunk_number, int chunk_id_to_sub, const SBPrefix& host_prefix, const SBPrefix& url_prefix) { chunk->chunk_number = chunk_number; chunk->is_add = false; SBChunkHost host; host.host = host_prefix; host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 1); host.entry->set_chunk_id(chunk->chunk_number); host.entry->SetChunkIdAtPrefix(0, chunk_id_to_sub); host.entry->SetPrefixAt(0, url_prefix); chunk->hosts.push_back(host); } // A helper function that adds one SubChunkHost to chunk with // one url for prefix. void InsertSubChunkHostPrefixUrl(SBChunk* chunk, int chunk_number, int chunk_id_to_sub, const std::string& host_name, const std::string& url) { InsertSubChunkHostPrefixValue(chunk, chunk_number, chunk_id_to_sub, Sha256Prefix(host_name), Sha256Prefix(url)); } // Same as InsertSubChunkHostPrefixUrl, but with two urls for prefixes. void InsertSubChunkHost2PrefixUrls(SBChunk* chunk, int chunk_number, int chunk_id_to_sub, const std::string& host_name, const std::string& url1, const std::string& url2) { chunk->chunk_number = chunk_number; chunk->is_add = false; SBChunkHost host; host.host = Sha256Prefix(host_name); host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 2); host.entry->set_chunk_id(chunk->chunk_number); host.entry->SetPrefixAt(0, Sha256Prefix(url1)); host.entry->SetChunkIdAtPrefix(0, chunk_id_to_sub); host.entry->SetPrefixAt(1, Sha256Prefix(url2)); host.entry->SetChunkIdAtPrefix(1, chunk_id_to_sub); chunk->hosts.push_back(host); } // Same as InsertSubChunkHost2PrefixUrls, but with full hashes. void InsertSubChunkHost2FullHashes(SBChunk* chunk, int chunk_number, int chunk_id_to_sub, const std::string& host_name, const std::string& url1, const std::string& url2) { chunk->chunk_number = chunk_number; chunk->is_add = false; SBChunkHost host; host.host = Sha256Prefix(host_name); host.entry = SBEntry::Create(SBEntry::SUB_FULL_HASH, 2); host.entry->set_chunk_id(chunk->chunk_number); host.entry->SetFullHashAt(0, Sha256Hash(url1)); host.entry->SetChunkIdAtPrefix(0, chunk_id_to_sub); host.entry->SetFullHashAt(1, Sha256Hash(url2)); host.entry->SetChunkIdAtPrefix(1, chunk_id_to_sub); chunk->hosts.push_back(host); } // Same as InsertSubChunkHost2PrefixUrls, but with full hashes. void InsertSubChunkHostFullHash(SBChunk* chunk, int chunk_number, int chunk_id_to_sub, const std::string& host_name, const std::string& url) { chunk->chunk_number = chunk_number; chunk->is_add = false; SBChunkHost host; host.host = Sha256Prefix(host_name); host.entry = SBEntry::Create(SBEntry::SUB_FULL_HASH, 2); host.entry->set_chunk_id(chunk->chunk_number); host.entry->SetFullHashAt(0, Sha256Hash(url)); host.entry->SetChunkIdAtPrefix(0, chunk_id_to_sub); chunk->hosts.push_back(host); } // Prevent DCHECK from killing tests. // TODO(shess): Pawel disputes the use of this, so the test which uses // it is DISABLED. http://crbug.com/56448 class ScopedLogMessageIgnorer { public: ScopedLogMessageIgnorer() { logging::SetLogMessageHandler(&LogMessageIgnorer); } ~ScopedLogMessageIgnorer() { // TODO(shess): Would be better to verify whether anyone else // changed it, and then restore it to the previous value. logging::SetLogMessageHandler(NULL); } private: static bool LogMessageIgnorer(int severity, const char* file, int line, size_t message_start, const std::string& str) { // Intercept FATAL, strip the stack backtrace, and log it without // the crash part. if (severity == logging::LOG_FATAL) { size_t newline = str.find('\n'); if (newline != std::string::npos) { const std::string msg = str.substr(0, newline + 1); fprintf(stderr, "%s", msg.c_str()); fflush(stderr); } return true; } return false; } }; } // namespace class SafeBrowsingDatabaseTest : public PlatformTest { public: virtual void SetUp() { PlatformTest::SetUp(); // Setup a database in a temporary directory. ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); database_.reset(new SafeBrowsingDatabaseNew); database_filename_ = temp_dir_.path().AppendASCII("SafeBrowsingTestDatabase"); database_->Init(database_filename_); } virtual void TearDown() { database_.reset(); PlatformTest::TearDown(); } void GetListsInfo(std::vector<SBListChunkRanges>* lists) { lists->clear(); EXPECT_TRUE(database_->UpdateStarted(lists)); database_->UpdateFinished(true); } // Helper function to do an AddDel or SubDel command. void DelChunk(const std::string& list, int chunk_id, bool is_sub_del) { std::vector<SBChunkDelete> deletes; SBChunkDelete chunk_delete; chunk_delete.list_name = list; chunk_delete.is_sub_del = is_sub_del; chunk_delete.chunk_del.push_back(ChunkRange(chunk_id)); deletes.push_back(chunk_delete); database_->DeleteChunks(deletes); } void AddDelChunk(const std::string& list, int chunk_id) { DelChunk(list, chunk_id, false); } void SubDelChunk(const std::string& list, int chunk_id) { DelChunk(list, chunk_id, true); } // Utility function for setting up the database for the caching test. void PopulateDatabaseForCacheTest(); scoped_ptr<SafeBrowsingDatabaseNew> database_; FilePath database_filename_; ScopedTempDir temp_dir_; }; // Tests retrieving list name information. TEST_F(SafeBrowsingDatabaseTest, ListNameForBrowse) { SBChunkList chunks; SBChunk chunk; InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/", "www.evil.com/malware.html"); chunks.clear(); chunks.push_back(chunk); std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 2, "www.foo.com/", "www.foo.com/malware.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 3, "www.whatever.com/", "www.whatever.com/malware.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].adds, "1-3"); EXPECT_TRUE(lists[0].subs.empty()); // Insert a malware sub chunk. chunk.hosts.clear(); InsertSubChunkHostPrefixUrl(&chunk, 7, 19, "www.subbed.com/", "www.subbed.com/noteveil1.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].adds, "1-3"); EXPECT_EQ(lists[0].subs, "7"); if (lists.size() == 2) { // Old style database won't have the second entry since it creates the lists // when it receives an update containing that list. The new bloom filter // based database has these values hard coded. EXPECT_TRUE(lists[1].name == safe_browsing_util::kPhishingList); EXPECT_TRUE(lists[1].adds.empty()); EXPECT_TRUE(lists[1].subs.empty()); } // Add a phishing add chunk. chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 47, "www.evil.com/", "www.evil.com/phishing.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks); // Insert some phishing sub chunks. chunk.hosts.clear(); InsertSubChunkHostPrefixUrl(&chunk, 200, 1999, "www.phishy.com/", "www.phishy.com/notevil1.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks); chunk.hosts.clear(); InsertSubChunkHostPrefixUrl(&chunk, 201, 1999, "www.phishy2.com/", "www.phishy2.com/notevil1.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].adds, "1-3"); EXPECT_EQ(lists[0].subs, "7"); EXPECT_TRUE(lists[1].name == safe_browsing_util::kPhishingList); EXPECT_EQ(lists[1].adds, "47"); EXPECT_EQ(lists[1].subs, "200-201"); } TEST_F(SafeBrowsingDatabaseTest, ListNameForBrowseAndDownload) { database_.reset(); MessageLoop loop(MessageLoop::TYPE_DEFAULT); SafeBrowsingStoreFile* browse_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* download_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* csd_whitelist_store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(browse_store, download_store, csd_whitelist_store)); database_->Init(database_filename_); SBChunkList chunks; SBChunk chunk; // Insert malware, phish, binurl and bindownload add chunks. InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/", "www.evil.com/malware.html"); chunks.push_back(chunk); std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 2, "www.foo.com/", "www.foo.com/malware.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks); chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 3, "www.whatever.com/", "www.whatever.com/download.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kBinUrlList, chunks); chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 4, "www.forhash.com/", "www.forhash.com/download.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kBinHashList, chunks); chunk.hosts.clear(); InsertAddChunkHostFullHashes(&chunk, 5, "www.forwhitelist.com/", "www.forwhitelist.com/a.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_EQ(5U, lists.size()); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].adds, "1"); EXPECT_TRUE(lists[0].subs.empty()); EXPECT_TRUE(lists[1].name == safe_browsing_util::kPhishingList); EXPECT_EQ(lists[1].adds, "2"); EXPECT_TRUE(lists[1].subs.empty()); EXPECT_TRUE(lists[2].name == safe_browsing_util::kBinUrlList); EXPECT_EQ(lists[2].adds, "3"); EXPECT_TRUE(lists[2].subs.empty()); EXPECT_TRUE(lists[3].name == safe_browsing_util::kBinHashList); EXPECT_EQ(lists[3].adds, "4"); EXPECT_TRUE(lists[3].subs.empty()); EXPECT_TRUE(lists[4].name == safe_browsing_util::kCsdWhiteList); EXPECT_EQ(lists[4].adds, "5"); EXPECT_TRUE(lists[4].subs.empty()); database_.reset(); } // Checks database reading and writing for browse. TEST_F(SafeBrowsingDatabaseTest, BrowseDatabase) { SBChunkList chunks; SBChunk chunk; // Add a simple chunk with one hostkey. InsertAddChunkHost2PrefixUrls(&chunk, 1, "www.evil.com/", "www.evil.com/phishing.html", "www.evil.com/malware.html"); chunks.push_back(chunk); std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); chunk.hosts.clear(); InsertAddChunkHost2PrefixUrls(&chunk, 2, "www.evil.com/", "www.evil.com/notevil1.html", "www.evil.com/notevil2.html"); InsertAddChunkHost2PrefixUrls(&chunk, 2, "www.good.com/", "www.good.com/good1.html", "www.good.com/good2.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); // and a chunk with an IP-based host chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 3, "192.168.0.1/", "192.168.0.1/malware.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); // Make sure they were added correctly. GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].adds, "1-3"); EXPECT_TRUE(lists[0].subs.empty()); const Time now = Time::Now(); std::vector<SBFullHashResult> full_hashes; std::vector<SBPrefix> prefix_hits; std::string matching_list; EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_EQ(prefix_hits[0], Sha256Prefix("www.evil.com/phishing.html")); EXPECT_EQ(prefix_hits.size(), 1U); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil1.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil2.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good1.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good2.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://192.168.0.1/malware.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(prefix_hits.empty()); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/robots.txt"), &matching_list, &prefix_hits, &full_hashes, now)); // Attempt to re-add the first chunk (should be a no-op). // see bug: http://code.google.com/p/chromium/issues/detail?id=4522 chunk.hosts.clear(); InsertAddChunkHost2PrefixUrls(&chunk, 1, "www.evil.com/", "www.evil.com/phishing.html", "www.evil.com/malware.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].adds, "1-3"); EXPECT_TRUE(lists[0].subs.empty()); // Test removing a single prefix from the add chunk. chunk.hosts.clear(); InsertSubChunkHostPrefixUrl(&chunk, 4, 2, "www.evil.com/", "www.evil.com/notevil1.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_EQ(prefix_hits[0], Sha256Prefix("www.evil.com/phishing.html")); EXPECT_EQ(prefix_hits.size(), 1U); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil1.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(prefix_hits.empty()); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil2.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good1.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good2.html"), &matching_list, &prefix_hits, &full_hashes, now)); GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].subs, "4"); // Test the same sub chunk again. This should be a no-op. // see bug: http://code.google.com/p/chromium/issues/detail?id=4522 chunk.hosts.clear(); InsertSubChunkHostPrefixUrl(&chunk, 4, 2, "www.evil.com/", "www.evil.com/notevil1.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].subs, "4"); // Test removing all the prefixes from an add chunk. EXPECT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 2); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil2.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good1.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good2.html"), &matching_list, &prefix_hits, &full_hashes, now)); GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].adds, "1,3"); EXPECT_EQ(lists[0].subs, "4"); // The adddel command exposed a bug in the transaction code where any // transaction after it would fail. Add a dummy entry and remove it to // make sure the transcation works fine. chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 44, "www.redherring.com/", "www.redherring.com/index.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); // Now remove the dummy entry. If there are any problems with the // transactions, asserts will fire. AddDelChunk(safe_browsing_util::kMalwareList, 44); // Test the subdel command. SubDelChunk(safe_browsing_util::kMalwareList, 4); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList); EXPECT_EQ(lists[0].adds, "1,3"); EXPECT_EQ(lists[0].subs, ""); // Test a sub command coming in before the add. chunk.hosts.clear(); InsertSubChunkHost2PrefixUrls(&chunk, 5, 10, "www.notevilanymore.com/", "www.notevilanymore.com/index.html", "www.notevilanymore.com/good.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.notevilanymore.com/index.html"), &matching_list, &prefix_hits, &full_hashes, now)); // Now insert the tardy add chunk and we don't expect them to appear // in database because of the previous sub chunk. chunk.hosts.clear(); InsertAddChunkHost2PrefixUrls(&chunk, 10, "www.notevilanymore.com/", "www.notevilanymore.com/index.html", "www.notevilanymore.com/good.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.notevilanymore.com/index.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.notevilanymore.com/good.html"), &matching_list, &prefix_hits, &full_hashes, now)); } // Test adding zero length chunks to the database. TEST_F(SafeBrowsingDatabaseTest, ZeroSizeChunk) { SBChunkList chunks; SBChunk chunk; // Populate with a couple of normal chunks. InsertAddChunkHost2PrefixUrls(&chunk, 1, "www.test.com/", "www.test.com/test1.html", "www.test.com/test2.html"); chunks.clear(); chunks.push_back(chunk); chunk.hosts.clear(); InsertAddChunkHost2PrefixUrls(&chunk, 10, "www.random.com/", "www.random.com/random1.html", "www.random.com/random2.html"); chunks.push_back(chunk); std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); // Add an empty ADD and SUB chunk. GetListsInfo(&lists); EXPECT_EQ(lists[0].adds, "1,10"); SBChunk empty_chunk; empty_chunk.chunk_number = 19; empty_chunk.is_add = true; chunks.clear(); chunks.push_back(empty_chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); chunks.clear(); empty_chunk.chunk_number = 7; empty_chunk.is_add = false; chunks.push_back(empty_chunk); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_EQ(lists[0].adds, "1,10,19"); EXPECT_EQ(lists[0].subs, "7"); // Add an empty chunk along with a couple that contain data. This should // result in the chunk range being reduced in size. empty_chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&empty_chunk, 20, "www.notempy.com/", "www.notempty.com/full1.html"); chunks.clear(); chunks.push_back(empty_chunk); empty_chunk.chunk_number = 21; empty_chunk.is_add = true; empty_chunk.hosts.clear(); chunks.push_back(empty_chunk); empty_chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&empty_chunk, 22, "www.notempy.com/", "www.notempty.com/full2.html"); chunks.push_back(empty_chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); const Time now = Time::Now(); std::vector<SBFullHashResult> full_hashes; std::vector<SBPrefix> prefix_hits; std::string matching_list; EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.notempty.com/full1.html"), &matching_list, &prefix_hits, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.notempty.com/full2.html"), &matching_list, &prefix_hits, &full_hashes, now)); GetListsInfo(&lists); EXPECT_EQ(lists[0].adds, "1,10,19-22"); EXPECT_EQ(lists[0].subs, "7"); // Handle AddDel and SubDel commands for empty chunks. EXPECT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 21); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_EQ(lists[0].adds, "1,10,19-20,22"); EXPECT_EQ(lists[0].subs, "7"); EXPECT_TRUE(database_->UpdateStarted(&lists)); SubDelChunk(safe_browsing_util::kMalwareList, 7); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_EQ(lists[0].adds, "1,10,19-20,22"); EXPECT_EQ(lists[0].subs, ""); } // Utility function for setting up the database for the caching test. void SafeBrowsingDatabaseTest::PopulateDatabaseForCacheTest() { SBChunkList chunks; SBChunk chunk; // Add a simple chunk with one hostkey and cache it. InsertAddChunkHost2PrefixUrls(&chunk, 1, "www.evil.com/", "www.evil.com/phishing.html", "www.evil.com/malware.html"); chunks.push_back(chunk); std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); // Add the GetHash results to the cache. SBFullHashResult full_hash; full_hash.hash = Sha256Hash("www.evil.com/phishing.html"); full_hash.list_name = safe_browsing_util::kMalwareList; full_hash.add_chunk_id = 1; std::vector<SBFullHashResult> results; results.push_back(full_hash); full_hash.hash = Sha256Hash("www.evil.com/malware.html"); results.push_back(full_hash); std::vector<SBPrefix> prefixes; database_->CacheHashResults(prefixes, results); } TEST_F(SafeBrowsingDatabaseTest, HashCaching) { PopulateDatabaseForCacheTest(); // We should have both full hashes in the cache. EXPECT_EQ(database_->pending_browse_hashes_.size(), 2U); // Test the cache lookup for the first prefix. std::string listname; std::vector<SBPrefix> prefixes; std::vector<SBFullHashResult> full_hashes; database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &listname, &prefixes, &full_hashes, Time::Now()); EXPECT_EQ(full_hashes.size(), 1U); EXPECT_TRUE(SBFullHashEq(full_hashes[0].hash, Sha256Hash("www.evil.com/phishing.html"))); prefixes.clear(); full_hashes.clear(); // Test the cache lookup for the second prefix. database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &listname, &prefixes, &full_hashes, Time::Now()); EXPECT_EQ(full_hashes.size(), 1U); EXPECT_TRUE(SBFullHashEq(full_hashes[0].hash, Sha256Hash("www.evil.com/malware.html"))); prefixes.clear(); full_hashes.clear(); // Test removing a prefix via a sub chunk. SBChunk chunk; SBChunkList chunks; InsertSubChunkHostPrefixUrl(&chunk, 2, 1, "www.evil.com/", "www.evil.com/phishing.html"); chunks.push_back(chunk); std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); // This prefix should still be there. database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &listname, &prefixes, &full_hashes, Time::Now()); EXPECT_EQ(full_hashes.size(), 1U); EXPECT_TRUE(SBFullHashEq(full_hashes[0].hash, Sha256Hash("www.evil.com/malware.html"))); prefixes.clear(); full_hashes.clear(); // This prefix should be gone. database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &listname, &prefixes, &full_hashes, Time::Now()); EXPECT_TRUE(full_hashes.empty()); prefixes.clear(); full_hashes.clear(); // Test that an AddDel for the original chunk removes the last cached entry. EXPECT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 1); database_->UpdateFinished(true); database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &listname, &prefixes, &full_hashes, Time::Now()); EXPECT_TRUE(full_hashes.empty()); EXPECT_TRUE(database_->full_browse_hashes_.empty()); EXPECT_TRUE(database_->pending_browse_hashes_.empty()); prefixes.clear(); full_hashes.clear(); // Test that the cache won't return expired values. First we have to adjust // the cached entries' received time to make them older, since the database // cache insert uses Time::Now(). First, store some entries. PopulateDatabaseForCacheTest(); std::vector<SBAddFullHash>* hash_cache = &database_->pending_browse_hashes_; EXPECT_EQ(hash_cache->size(), 2U); // Now adjust one of the entries times to be in the past. base::Time expired = base::Time::Now() - base::TimeDelta::FromMinutes(60); const SBPrefix key = Sha256Prefix("www.evil.com/malware.html"); std::vector<SBAddFullHash>::iterator iter; for (iter = hash_cache->begin(); iter != hash_cache->end(); ++iter) { if (iter->full_hash.prefix == key) { iter->received = static_cast<int32>(expired.ToTimeT()); break; } } EXPECT_TRUE(iter != hash_cache->end()); database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &listname, &prefixes, &full_hashes, expired); EXPECT_TRUE(full_hashes.empty()); // This entry should still exist. database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &listname, &prefixes, &full_hashes, expired); EXPECT_EQ(full_hashes.size(), 1U); // Testing prefix miss caching. First, we clear out the existing database, // Since PopulateDatabaseForCacheTest() doesn't handle adding duplicate // chunks. EXPECT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 1); database_->UpdateFinished(true); std::vector<SBPrefix> prefix_misses; std::vector<SBFullHashResult> empty_full_hash; prefix_misses.push_back(Sha256Prefix("http://www.bad.com/malware.html")); prefix_misses.push_back(Sha256Prefix("http://www.bad.com/phishing.html")); database_->CacheHashResults(prefix_misses, empty_full_hash); // Prefixes with no full results are misses. EXPECT_EQ(database_->prefix_miss_cache_.size(), 2U); // Update the database. PopulateDatabaseForCacheTest(); // Prefix miss cache should be cleared. EXPECT_TRUE(database_->prefix_miss_cache_.empty()); // Cache a GetHash miss for a particular prefix, and even though the prefix is // in the database, it is flagged as a miss so looking up the associated URL // will not succeed. prefixes.clear(); full_hashes.clear(); prefix_misses.clear(); empty_full_hash.clear(); prefix_misses.push_back(Sha256Prefix("www.evil.com/phishing.html")); database_->CacheHashResults(prefix_misses, empty_full_hash); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &listname, &prefixes, &full_hashes, Time::Now())); prefixes.clear(); full_hashes.clear(); // Test receiving a full add chunk. chunk.hosts.clear(); InsertAddChunkHost2FullHashes(&chunk, 20, "www.fullevil.com/", "www.fullevil.com/bad1.html", "www.fullevil.com/bad2.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad1.html"), &listname, &prefixes, &full_hashes, Time::Now())); EXPECT_EQ(full_hashes.size(), 1U); EXPECT_TRUE(SBFullHashEq(full_hashes[0].hash, Sha256Hash("www.fullevil.com/bad1.html"))); prefixes.clear(); full_hashes.clear(); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad2.html"), &listname, &prefixes, &full_hashes, Time::Now())); EXPECT_EQ(full_hashes.size(), 1U); EXPECT_TRUE(SBFullHashEq(full_hashes[0].hash, Sha256Hash("www.fullevil.com/bad2.html"))); prefixes.clear(); full_hashes.clear(); // Test receiving a full sub chunk, which will remove one of the full adds. chunk.hosts.clear(); InsertSubChunkHostFullHash(&chunk, 200, 20, "www.fullevil.com/", "www.fullevil.com/bad1.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad1.html"), &listname, &prefixes, &full_hashes, Time::Now())); EXPECT_TRUE(full_hashes.empty()); // There should be one remaining full add. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad2.html"), &listname, &prefixes, &full_hashes, Time::Now())); EXPECT_EQ(full_hashes.size(), 1U); EXPECT_TRUE(SBFullHashEq(full_hashes[0].hash, Sha256Hash("www.fullevil.com/bad2.html"))); prefixes.clear(); full_hashes.clear(); // Now test an AddDel for the remaining full add. EXPECT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 20); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad1.html"), &listname, &prefixes, &full_hashes, Time::Now())); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad2.html"), &listname, &prefixes, &full_hashes, Time::Now())); } // Test that corrupt databases are appropriately handled, even if the // corruption is detected in the midst of the update. // TODO(shess): Disabled until ScopedLogMessageIgnorer resolved. // http://crbug.com/56448 TEST_F(SafeBrowsingDatabaseTest, DISABLED_FileCorruptionHandling) { // Re-create the database in a captive message loop so that we can // influence task-posting. Database specifically needs to the // file-backed. database_.reset(); MessageLoop loop(MessageLoop::TYPE_DEFAULT); SafeBrowsingStoreFile* store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(store, NULL, NULL)); database_->Init(database_filename_); // This will cause an empty database to be created. std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->UpdateFinished(true); // Create a sub chunk to insert. SBChunkList chunks; SBChunk chunk; SBChunkHost host; host.host = Sha256Prefix("www.subbed.com/"); host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 1); host.entry->set_chunk_id(7); host.entry->SetChunkIdAtPrefix(0, 19); host.entry->SetPrefixAt(0, Sha256Prefix("www.subbed.com/notevil1.html")); chunk.chunk_number = 7; chunk.is_add = false; chunk.hosts.clear(); chunk.hosts.push_back(host); chunks.clear(); chunks.push_back(chunk); // Corrupt the file by corrupting the checksum, which is not checked // until the entire table is read in |UpdateFinished()|. FILE* fp = file_util::OpenFile(database_filename_, "r+"); ASSERT_TRUE(fp); ASSERT_NE(-1, fseek(fp, -8, SEEK_END)); for (size_t i = 0; i < 8; ++i) { fputc('!', fp); } fclose(fp); { // The following code will cause DCHECKs, so suppress the crashes. ScopedLogMessageIgnorer ignorer; // Start an update. The insert will fail due to corruption. EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); // Database file still exists until the corruption handler has run. EXPECT_TRUE(file_util::PathExists(database_filename_)); // Flush through the corruption-handler task. VLOG(1) << "Expect failed check on: SafeBrowsing database reset"; MessageLoop::current()->RunAllPending(); } // Database file should not exist. EXPECT_FALSE(file_util::PathExists(database_filename_)); // Run the update again successfully. EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); EXPECT_TRUE(file_util::PathExists(database_filename_)); database_.reset(); } // Checks database reading and writing. TEST_F(SafeBrowsingDatabaseTest, ContainsDownloadUrl) { database_.reset(); MessageLoop loop(MessageLoop::TYPE_DEFAULT); SafeBrowsingStoreFile* browse_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* download_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* csd_whitelist_store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(browse_store, download_store, csd_whitelist_store)); database_->Init(database_filename_); const char kEvil1Host[] = "www.evil1.com/"; const char kEvil1Url1[] = "www.evil1.com/download1/"; const char kEvil1Url2[] = "www.evil1.com/download2.html"; SBChunkList chunks; SBChunk chunk; // Add a simple chunk with one hostkey for download url list. InsertAddChunkHost2PrefixUrls(&chunk, 1, kEvil1Host, kEvil1Url1, kEvil1Url2); chunks.push_back(chunk); std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kBinUrlList, chunks); database_->UpdateFinished(true); std::vector<SBPrefix> prefix_hits; std::vector<GURL> urls(1); urls[0] = GURL(std::string("http://") + kEvil1Url1); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url1)); urls[0] = GURL(std::string("http://") + kEvil1Url2); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url2)); urls[0] = GURL(std::string("https://") + kEvil1Url2); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url2)); urls[0] = GURL(std::string("ftp://") + kEvil1Url2); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url2)); urls[0] = GURL("http://www.randomevil.com"); EXPECT_FALSE(database_->ContainsDownloadUrl(urls, &prefix_hits)); // Should match with query args stripped. urls[0] = GURL(std::string("http://") + kEvil1Url2 + "?blah"); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url2)); // Should match with extra path stuff and query args stripped. urls[0] = GURL(std::string("http://") + kEvil1Url1 + "foo/bar?blah"); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url1)); // First hit in redirect chain is malware. urls.clear(); urls.push_back(GURL(std::string("http://") + kEvil1Url1)); urls.push_back(GURL("http://www.randomevil.com")); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url1)); // Middle hit in redirect chain is malware. urls.clear(); urls.push_back(GURL("http://www.randomevil.com")); urls.push_back(GURL(std::string("http://") + kEvil1Url1)); urls.push_back(GURL("http://www.randomevil2.com")); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url1)); // Final hit in redirect chain is malware. urls.clear(); urls.push_back(GURL("http://www.randomevil.com")); urls.push_back(GURL(std::string("http://") + kEvil1Url1)); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 1U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url1)); // Multiple hits in redirect chain are in malware list. urls.clear(); urls.push_back(GURL(std::string("http://") + kEvil1Url1)); urls.push_back(GURL(std::string("https://") + kEvil1Url2)); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(prefix_hits.size(), 2U); EXPECT_EQ(prefix_hits[0], Sha256Prefix(kEvil1Url1)); EXPECT_EQ(prefix_hits[1], Sha256Prefix(kEvil1Url2)); database_.reset(); } // Checks that the csd-whitelist is handled properly. TEST_F(SafeBrowsingDatabaseTest, CsdWhitelist) { database_.reset(); MessageLoop loop(MessageLoop::TYPE_DEFAULT); // We expect all calls to ContainsCsdWhitelistedUrl to be made from the IO // thread. BrowserThread io_thread(BrowserThread::IO, &loop); // If the whitelist is disabled everything should match the whitelist. database_.reset(new SafeBrowsingDatabaseNew(new SafeBrowsingStoreFile(), NULL, NULL)); database_->Init(database_filename_); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://www.phishig.com/")))); SafeBrowsingStoreFile* browse_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* csd_whitelist_store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(browse_store, NULL, csd_whitelist_store)); database_->Init(database_filename_); const char kGood1Host[] = "www.good1.com/"; const char kGood1Url1[] = "www.good1.com/a/b.html"; const char kGood1Url2[] = "www.good1.com/b/"; const char kGood2Host[] = "www.good2.com/"; const char kGood2Url1[] = "www.good2.com/c"; // Should match '/c/bla'. SBChunkList chunks; SBChunk chunk; // Add two simple chunks to the csd whitelist. InsertAddChunkHost2FullHashes(&chunk, 1, kGood1Host, kGood1Url1, kGood1Url2); chunks.push_back(chunk); chunk.hosts.clear(); InsertAddChunkHostFullHashes(&chunk, 2, kGood2Host, kGood2Url1); chunks.push_back(chunk); std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, chunks); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://") + kGood1Host))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://") + kGood1Url1))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://") + kGood1Url1 + "?a=b"))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://") + kGood1Url2))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://") + kGood1Url2 + "/c.html"))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("https://") + kGood1Url2 + "/c.html"))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://") + kGood2Url1 + "/c"))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://") + kGood2Url1 + "/c?bla"))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://") + kGood2Url1 + "/c/bla"))); EXPECT_FALSE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://www.google.com/")))); // Test that the kill-switch works as intended. chunks.clear(); lists.clear(); SBChunk chunk2; InsertAddChunkHostFullHashes(&chunk2, 3, "sb-ssl.google.com/", "sb-ssl.google.com/safebrowsing/csd/killswitch"); chunks.push_back(chunk2); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, chunks); database_->UpdateFinished(true); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("https://") + kGood1Url2 + "/c.html"))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://www.google.com/")))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://www.phishing_url.com/")))); // Remove the kill-switch and verify that we can recover. chunks.clear(); lists.clear(); SBChunk sub_chunk; InsertSubChunkHostFullHash(&sub_chunk, 1, 3, "sb-ssl.google.com/", "sb-ssl.google.com/safebrowsing/csd/killswitch"); chunks.push_back(sub_chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, chunks); database_->UpdateFinished(true); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("https://") + kGood1Url2 + "/c.html"))); EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("https://") + kGood2Url1 + "/c/bla"))); EXPECT_FALSE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://www.google.com/")))); EXPECT_FALSE(database_->ContainsCsdWhitelistedUrl( GURL(std::string("http://www.phishing_url.com/")))); database_.reset(); } // Test to make sure we could insert chunk list that // contains entries for the same host. TEST_F(SafeBrowsingDatabaseTest, SameHostEntriesOkay) { SBChunk chunk; // Add a malware add chunk with two entries of the same host. InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/", "www.evil.com/malware1.html"); InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/", "www.evil.com/malware2.html"); SBChunkList chunks; chunks.push_back(chunk); // Insert the testing chunks into database. std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_EQ(std::string(safe_browsing_util::kMalwareList), lists[0].name); EXPECT_EQ("1", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); // Add a phishing add chunk with two entries of the same host. chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 47, "www.evil.com/", "www.evil.com/phishing1.html"); InsertAddChunkHostPrefixUrl(&chunk, 47, "www.evil.com/", "www.evil.com/phishing2.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); EXPECT_EQ(std::string(safe_browsing_util::kMalwareList), lists[0].name); EXPECT_EQ("1", lists[0].adds); EXPECT_EQ(std::string(safe_browsing_util::kPhishingList), lists[1].name); EXPECT_EQ("47", lists[1].adds); const Time now = Time::Now(); std::vector<SBPrefix> prefixes; std::vector<SBFullHashResult> full_hashes; std::vector<SBPrefix> prefix_hits; std::string matching_list; std::string listname; EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware1.html"), &listname, &prefixes, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware2.html"), &listname, &prefixes, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing1.html"), &listname, &prefixes, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing2.html"), &listname, &prefixes, &full_hashes, now)); // Test removing a single prefix from the add chunk. // Remove the prefix that added first. chunk.hosts.clear(); InsertSubChunkHostPrefixUrl(&chunk, 4, 1, "www.evil.com/", "www.evil.com/malware1.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); // Remove the prefix that added last. chunk.hosts.clear(); InsertSubChunkHostPrefixUrl(&chunk, 5, 47, "www.evil.com/", "www.evil.com/phishing2.html"); chunks.clear(); chunks.push_back(chunk); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks); database_->UpdateFinished(true); // Verify that the database contains urls expected. EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware1.html"), &listname, &prefixes, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware2.html"), &listname, &prefixes, &full_hashes, now)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing1.html"), &listname, &prefixes, &full_hashes, now)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing2.html"), &listname, &prefixes, &full_hashes, now)); } TEST_F(SafeBrowsingDatabaseTest, BinHashInsertLookup) { const SBPrefix kPrefix1 = 0x31313131; const SBPrefix kPrefix2 = 0x32323232; const SBPrefix kPrefix3 = 0x33333333; database_.reset(); MessageLoop loop(MessageLoop::TYPE_DEFAULT); SafeBrowsingStoreFile* browse_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* download_store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(browse_store, download_store, NULL)); database_->Init(database_filename_); SBChunkList chunks; SBChunk chunk; // Insert one host. InsertAddChunkHostPrefixValue(&chunk, 1, 0, kPrefix1); // Insert a second host, which has the same host prefix as the first one. InsertAddChunkHostPrefixValue(&chunk, 1, 0, kPrefix2); chunks.push_back(chunk); // Insert the testing chunks into database. std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kBinHashList, chunks); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_EQ(4U, lists.size()); EXPECT_EQ(std::string(safe_browsing_util::kBinHashList), lists[3].name); EXPECT_EQ("1", lists[3].adds); EXPECT_TRUE(lists[3].subs.empty()); EXPECT_TRUE(database_->ContainsDownloadHashPrefix(kPrefix1)); EXPECT_TRUE(database_->ContainsDownloadHashPrefix(kPrefix2)); EXPECT_FALSE(database_->ContainsDownloadHashPrefix(kPrefix3)); database_.reset(); } // Test that an empty update doesn't actually update the database. // This isn't a functionality requirement, but it is a useful // optimization. TEST_F(SafeBrowsingDatabaseTest, EmptyUpdate) { SBChunkList chunks; SBChunk chunk; FilePath filename = database_->BrowseDBFilename(database_filename_); // Prime the database. std::vector<SBListChunkRanges> lists; EXPECT_TRUE(database_->UpdateStarted(&lists)); InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/", "www.evil.com/malware.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); // Get an older time to reset the lastmod time for detecting whether // the file has been updated. base::PlatformFileInfo before_info, after_info; ASSERT_TRUE(file_util::GetFileInfo(filename, &before_info)); const base::Time old_last_modified = before_info.last_modified - base::TimeDelta::FromSeconds(10); // Inserting another chunk updates the database file. The sleep is // needed because otherwise the entire test can finish w/in the // resolution of the lastmod time. ASSERT_TRUE(file_util::SetLastModifiedTime(filename, old_last_modified)); ASSERT_TRUE(file_util::GetFileInfo(filename, &before_info)); EXPECT_TRUE(database_->UpdateStarted(&lists)); chunk.hosts.clear(); InsertAddChunkHostPrefixUrl(&chunk, 2, "www.foo.com/", "www.foo.com/malware.html"); chunks.clear(); chunks.push_back(chunk); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks); database_->UpdateFinished(true); ASSERT_TRUE(file_util::GetFileInfo(filename, &after_info)); EXPECT_LT(before_info.last_modified, after_info.last_modified); // Deleting a chunk updates the database file. ASSERT_TRUE(file_util::SetLastModifiedTime(filename, old_last_modified)); ASSERT_TRUE(file_util::GetFileInfo(filename, &before_info)); EXPECT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, chunk.chunk_number); database_->UpdateFinished(true); ASSERT_TRUE(file_util::GetFileInfo(filename, &after_info)); EXPECT_LT(before_info.last_modified, after_info.last_modified); // Simply calling |UpdateStarted()| then |UpdateFinished()| does not // update the database file. ASSERT_TRUE(file_util::SetLastModifiedTime(filename, old_last_modified)); ASSERT_TRUE(file_util::GetFileInfo(filename, &before_info)); EXPECT_TRUE(database_->UpdateStarted(&lists)); database_->UpdateFinished(true); ASSERT_TRUE(file_util::GetFileInfo(filename, &after_info)); EXPECT_EQ(before_info.last_modified, after_info.last_modified); }