// Copyright (c) 2012 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 "chrome/browser/safe_browsing/safe_browsing_database.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/memory/scoped_vector.h" #include "base/message_loop/message_loop.h" #include "base/sha1.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/time/time.h" #include "chrome/browser/safe_browsing/chunk.pb.h" #include "chrome/browser/safe_browsing/safe_browsing_store_file.h" #include "content/public/test/test_browser_thread_bundle.h" #include "crypto/sha2.h" #include "net/base/net_util.h" #include "sql/connection.h" #include "sql/statement.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" #include "url/gurl.h" using base::Time; using base::TimeDelta; namespace { const TimeDelta kCacheLifetime = TimeDelta::FromMinutes(45); SBPrefix SBPrefixForString(const std::string& str) { return SBFullHashForString(str).prefix; } // Construct a full hash which has the given prefix, with the given // suffix data coming after the prefix. SBFullHash SBFullHashForPrefixAndSuffix(SBPrefix prefix, const base::StringPiece& suffix) { SBFullHash full_hash; memset(&full_hash, 0, sizeof(SBFullHash)); full_hash.prefix = prefix; CHECK_LE(suffix.size() + sizeof(SBPrefix), sizeof(SBFullHash)); memcpy(full_hash.full_hash + sizeof(SBPrefix), suffix.data(), suffix.size()); return full_hash; } std::string HashedIpPrefix(const std::string& ip_prefix, size_t prefix_size) { net::IPAddressNumber ip_number; EXPECT_TRUE(net::ParseIPLiteralToNumber(ip_prefix, &ip_number)); EXPECT_EQ(net::kIPv6AddressSize, ip_number.size()); const std::string hashed_ip_prefix = base::SHA1HashString( net::IPAddressToPackedString(ip_number)); std::string hash(crypto::kSHA256Length, '\0'); hash.replace(0, hashed_ip_prefix.size(), hashed_ip_prefix); hash[base::kSHA1Length] = static_cast(prefix_size); return hash; } // Helper to build a chunk. Caller takes ownership. SBChunkData* BuildChunk(int chunk_number, safe_browsing::ChunkData::ChunkType chunk_type, safe_browsing::ChunkData::PrefixType prefix_type, const void* data, size_t data_size, const std::vector& add_chunk_numbers) { scoped_ptr raw_data(new safe_browsing::ChunkData); raw_data->set_chunk_number(chunk_number); raw_data->set_chunk_type(chunk_type); raw_data->set_prefix_type(prefix_type); raw_data->set_hashes(data, data_size); raw_data->clear_add_numbers(); for (size_t i = 0; i < add_chunk_numbers.size(); ++i) { raw_data->add_add_numbers(add_chunk_numbers[i]); } return new SBChunkData(raw_data.release()); } // Create add chunk with a single prefix. SBChunkData* AddChunkPrefix(int chunk_number, SBPrefix prefix) { return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD, safe_browsing::ChunkData::PREFIX_4B, &prefix, sizeof(prefix), std::vector()); } // Create add chunk with a single prefix generated from |value|. SBChunkData* AddChunkPrefixValue(int chunk_number, const std::string& value) { return AddChunkPrefix(chunk_number, SBPrefixForString(value)); } // Generate an add chunk with two prefixes. SBChunkData* AddChunkPrefix2Value(int chunk_number, const std::string& value1, const std::string& value2) { const SBPrefix prefixes[2] = { SBPrefixForString(value1), SBPrefixForString(value2), }; return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD, safe_browsing::ChunkData::PREFIX_4B, &prefixes[0], sizeof(prefixes), std::vector()); } // Generate an add chunk with four prefixes. SBChunkData* AddChunkPrefix4Value(int chunk_number, const std::string& value1, const std::string& value2, const std::string& value3, const std::string& value4) { const SBPrefix prefixes[4] = { SBPrefixForString(value1), SBPrefixForString(value2), SBPrefixForString(value3), SBPrefixForString(value4), }; return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD, safe_browsing::ChunkData::PREFIX_4B, &prefixes[0], sizeof(prefixes), std::vector()); } // Generate an add chunk with a full hash. SBChunkData* AddChunkFullHash(int chunk_number, SBFullHash full_hash) { return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD, safe_browsing::ChunkData::FULL_32B, &full_hash, sizeof(full_hash), std::vector()); } // Generate an add chunk with a full hash generated from |value|. SBChunkData* AddChunkFullHashValue(int chunk_number, const std::string& value) { return AddChunkFullHash(chunk_number, SBFullHashForString(value)); } // Generate an add chunk with two full hashes. SBChunkData* AddChunkFullHash2Value(int chunk_number, const std::string& value1, const std::string& value2) { const SBFullHash full_hashes[2] = { SBFullHashForString(value1), SBFullHashForString(value2), }; return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD, safe_browsing::ChunkData::FULL_32B, &full_hashes[0], sizeof(full_hashes), std::vector()); } // Generate a sub chunk with a prefix generated from |value|. SBChunkData* SubChunkPrefixValue(int chunk_number, const std::string& value, int add_chunk_number) { const SBPrefix prefix = SBPrefixForString(value); return BuildChunk(chunk_number, safe_browsing::ChunkData::SUB, safe_browsing::ChunkData::PREFIX_4B, &prefix, sizeof(prefix), std::vector(1, add_chunk_number)); } // Generate a sub chunk with two prefixes. SBChunkData* SubChunkPrefix2Value(int chunk_number, const std::string& value1, int add_chunk_number1, const std::string& value2, int add_chunk_number2) { const SBPrefix prefixes[2] = { SBPrefixForString(value1), SBPrefixForString(value2), }; std::vector add_chunk_numbers; add_chunk_numbers.push_back(add_chunk_number1); add_chunk_numbers.push_back(add_chunk_number2); return BuildChunk(chunk_number, safe_browsing::ChunkData::SUB, safe_browsing::ChunkData::PREFIX_4B, &prefixes[0], sizeof(prefixes), add_chunk_numbers); } // Generate a sub chunk with a full hash. SBChunkData* SubChunkFullHash(int chunk_number, SBFullHash full_hash, int add_chunk_number) { return BuildChunk(chunk_number, safe_browsing::ChunkData::SUB, safe_browsing::ChunkData::FULL_32B, &full_hash, sizeof(full_hash), std::vector(1, add_chunk_number)); } // Generate a sub chunk with a full hash generated from |value|. SBChunkData* SubChunkFullHashValue(int chunk_number, const std::string& value, int add_chunk_number) { return SubChunkFullHash(chunk_number, SBFullHashForString(value), add_chunk_number); } // Generate an add chunk with a single full hash for the ip blacklist. SBChunkData* AddChunkHashedIpValue(int chunk_number, const std::string& ip_str, size_t prefix_size) { const std::string full_hash_str = HashedIpPrefix(ip_str, prefix_size); EXPECT_EQ(sizeof(SBFullHash), full_hash_str.size()); SBFullHash full_hash; std::memcpy(&(full_hash.full_hash), full_hash_str.data(), sizeof(SBFullHash)); return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD, safe_browsing::ChunkData::FULL_32B, &full_hash, sizeof(full_hash), std::vector()); } // 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* lists) { lists->clear(); ASSERT_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 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 database_; base::FilePath database_filename_; base::ScopedTempDir temp_dir_; }; // Tests retrieving list name information. TEST_F(SafeBrowsingDatabaseTest, ListNameForBrowse) { std::vector lists; ScopedVector chunks; chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html")); chunks.push_back(AddChunkPrefixValue(2, "www.foo.com/malware.html")); chunks.push_back(AddChunkPrefixValue(3, "www.whatever.com/malware.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1-3", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); // Insert a malware sub chunk. chunks.clear(); chunks.push_back(SubChunkPrefixValue(7, "www.subbed.com/noteveil1.html", 19)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1-3", lists[0].adds); EXPECT_EQ("7", lists[0].subs); 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 filter-based // database has these values hard coded. EXPECT_EQ(safe_browsing_util::kPhishingList, lists[1].name); EXPECT_TRUE(lists[1].adds.empty()); EXPECT_TRUE(lists[1].subs.empty()); } // Add phishing chunks. chunks.clear(); chunks.push_back(AddChunkPrefixValue(47, "www.evil.com/phishing.html")); chunks.push_back( SubChunkPrefixValue(200, "www.phishy.com/notevil1.html", 1999)); chunks.push_back( SubChunkPrefixValue(201, "www.phishy2.com/notevil1.html", 1999)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_EQ(2U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1-3", lists[0].adds); EXPECT_EQ("7", lists[0].subs); EXPECT_EQ(safe_browsing_util::kPhishingList, lists[1].name); EXPECT_EQ("47", lists[1].adds); EXPECT_EQ("200-201", lists[1].subs); } TEST_F(SafeBrowsingDatabaseTest, ListNameForBrowseAndDownload) { database_.reset(); base::MessageLoop loop; SafeBrowsingStoreFile* browse_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* download_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* csd_whitelist_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* download_whitelist_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* extension_blacklist_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* ip_blacklist_store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(browse_store, download_store, csd_whitelist_store, download_whitelist_store, extension_blacklist_store, NULL, ip_blacklist_store)); database_->Init(database_filename_); ScopedVector chunks; std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); // Insert malware, phish, binurl and bindownload add chunks. chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html")); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); chunks.clear(); chunks.push_back(AddChunkPrefixValue(2, "www.foo.com/malware.html")); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks.get()); chunks.clear(); chunks.push_back(AddChunkPrefixValue(3, "www.whatever.com/download.html")); database_->InsertChunks(safe_browsing_util::kBinUrlList, chunks.get()); chunks.clear(); chunks.push_back(AddChunkFullHashValue(5, "www.forwhitelist.com/a.html")); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, chunks.get()); chunks.clear(); chunks.push_back(AddChunkFullHashValue(6, "www.download.com/")); database_->InsertChunks(safe_browsing_util::kDownloadWhiteList, chunks.get()); chunks.clear(); chunks.push_back(AddChunkFullHashValue(8, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); database_->InsertChunks(safe_browsing_util::kExtensionBlacklist, chunks.get()); chunks.clear(); chunks.push_back(AddChunkHashedIpValue(9, "::ffff:192.168.1.0", 120)); database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_EQ(7U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); EXPECT_EQ(safe_browsing_util::kPhishingList, lists[1].name); EXPECT_EQ("2", lists[1].adds); EXPECT_TRUE(lists[1].subs.empty()); EXPECT_EQ(safe_browsing_util::kBinUrlList, lists[2].name); EXPECT_EQ("3", lists[2].adds); EXPECT_TRUE(lists[2].subs.empty()); EXPECT_EQ(safe_browsing_util::kCsdWhiteList, lists[3].name); EXPECT_EQ("5", lists[3].adds); EXPECT_TRUE(lists[3].subs.empty()); EXPECT_EQ(safe_browsing_util::kDownloadWhiteList, lists[4].name); EXPECT_EQ("6", lists[4].adds); EXPECT_TRUE(lists[4].subs.empty()); EXPECT_EQ(safe_browsing_util::kExtensionBlacklist, lists[5].name); EXPECT_EQ("8", lists[5].adds); EXPECT_TRUE(lists[5].subs.empty()); EXPECT_EQ(safe_browsing_util::kIPBlacklist, lists[6].name); EXPECT_EQ("9", lists[6].adds); EXPECT_TRUE(lists[6].subs.empty()); database_.reset(); } // Checks database reading and writing for browse. TEST_F(SafeBrowsingDatabaseTest, BrowseDatabase) { std::vector lists; ScopedVector chunks; chunks.push_back(AddChunkPrefix2Value(1, "www.evil.com/phishing.html", "www.evil.com/malware.html")); chunks.push_back(AddChunkPrefix4Value(2, "www.evil.com/notevil1.html", "www.evil.com/notevil2.html", "www.good.com/good1.html", "www.good.com/good2.html")); chunks.push_back(AddChunkPrefixValue(3, "192.168.0.1/malware.html")); chunks.push_back(AddChunkFullHashValue(7, "www.evil.com/evil.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // Make sure they were added correctly. GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1-3,7", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.evil.com/phishing.html"), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil1.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://192.168.0.1/malware.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/"), &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); EXPECT_TRUE(cache_hits.empty()); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/robots.txt"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/evil.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.evil.com/evil.html"), prefix_hits[0]); // Attempt to re-add the first chunk (should be a no-op). // see bug: http://code.google.com/p/chromium/issues/detail?id=4522 chunks.clear(); chunks.push_back(AddChunkPrefix2Value(1, "www.evil.com/phishing.html", "www.evil.com/malware.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1-3,7", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); // Test removing a single prefix from the add chunk. chunks.clear(); chunks.push_back(SubChunkPrefixValue(4, "www.evil.com/notevil1.html", 2)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.evil.com/phishing.html"), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil1.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); EXPECT_TRUE(cache_hits.empty()); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits)); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1-3,7", lists[0].adds); EXPECT_EQ("4", lists[0].subs); // Test the same sub chunk again. This should be a no-op. // see bug: http://code.google.com/p/chromium/issues/detail?id=4522 chunks.clear(); chunks.push_back(SubChunkPrefixValue(4, "www.evil.com/notevil1.html", 2)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1-3,7", lists[0].adds); EXPECT_EQ("4", lists[0].subs); // Test removing all the prefixes from an add chunk. ASSERT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 2); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits)); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1,3,7", lists[0].adds); EXPECT_EQ("4", lists[0].subs); // 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. chunks.clear(); chunks.push_back(AddChunkPrefixValue(44, "www.redherring.com/index.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); // 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); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1,3,7", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); // Test a sub command coming in before the add. chunks.clear(); chunks.push_back(SubChunkPrefix2Value(5, "www.notevilanymore.com/index.html", 10, "www.notevilanymore.com/good.html", 10)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.notevilanymore.com/index.html"), &prefix_hits, &cache_hits)); // Now insert the tardy add chunk and we don't expect them to appear // in database because of the previous sub chunk. chunks.clear(); chunks.push_back(AddChunkPrefix2Value(10, "www.notevilanymore.com/index.html", "www.notevilanymore.com/good.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.notevilanymore.com/index.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.notevilanymore.com/good.html"), &prefix_hits, &cache_hits)); // Reset and reload the database. The database will rely on the prefix set. database_.reset(new SafeBrowsingDatabaseNew); database_->Init(database_filename_); // Check that a prefix still hits. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.evil.com/phishing.html"), prefix_hits[0]); // Also check that it's not just always returning true in this case. EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/"), &prefix_hits, &cache_hits)); // Check that the full hash is still present. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/evil.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.evil.com/evil.html"), prefix_hits[0]); } // Test adding zero length chunks to the database. TEST_F(SafeBrowsingDatabaseTest, ZeroSizeChunk) { std::vector lists; ScopedVector chunks; // Populate with a couple of normal chunks. chunks.push_back(AddChunkPrefix2Value(1, "www.test.com/test1.html", "www.test.com/test2.html")); chunks.push_back(AddChunkPrefix2Value(10, "www.random.com/random1.html", "www.random.com/random2.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // Add an empty ADD and SUB chunk. GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1,10", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); chunks.clear(); chunks.push_back(BuildChunk(19, safe_browsing::ChunkData::ADD, safe_browsing::ChunkData::PREFIX_4B, NULL, 0, std::vector())); chunks.push_back(BuildChunk(7, safe_browsing::ChunkData::SUB, safe_browsing::ChunkData::PREFIX_4B, NULL, 0, std::vector())); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1,10,19", lists[0].adds); EXPECT_EQ("7", lists[0].subs); // Add an empty chunk along with a couple that contain data. This should // result in the chunk range being reduced in size. chunks.clear(); chunks.push_back(AddChunkPrefixValue(20, "www.notempty.com/full1.html")); chunks.push_back(BuildChunk(21, safe_browsing::ChunkData::ADD, safe_browsing::ChunkData::PREFIX_4B, NULL, 0, std::vector())); chunks.push_back(AddChunkPrefixValue(22, "www.notempty.com/full2.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.notempty.com/full1.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.notempty.com/full2.html"), &prefix_hits, &cache_hits)); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1,10,19-22", lists[0].adds); EXPECT_EQ("7", lists[0].subs); // Handle AddDel and SubDel commands for empty chunks. ASSERT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 21); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1,10,19-20,22", lists[0].adds); EXPECT_EQ("7", lists[0].subs); ASSERT_TRUE(database_->UpdateStarted(&lists)); SubDelChunk(safe_browsing_util::kMalwareList, 7); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1,10,19-20,22", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); } // Utility function for setting up the database for the caching test. void SafeBrowsingDatabaseTest::PopulateDatabaseForCacheTest() { // Add a couple prefixes. ScopedVector chunks; chunks.push_back(AddChunkPrefix2Value(1, "www.evil.com/phishing.html", "www.evil.com/malware.html")); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // Cache should be cleared after updating. EXPECT_TRUE(database_->browse_gethash_cache_.empty()); SBFullHashResult full_hash; full_hash.list_id = safe_browsing_util::MALWARE; std::vector results; std::vector prefixes; // Add a fullhash result for each prefix. full_hash.hash = SBFullHashForString("www.evil.com/phishing.html"); results.push_back(full_hash); prefixes.push_back(full_hash.hash.prefix); full_hash.hash = SBFullHashForString("www.evil.com/malware.html"); results.push_back(full_hash); prefixes.push_back(full_hash.hash.prefix); database_->CacheHashResults(prefixes, results, kCacheLifetime); } TEST_F(SafeBrowsingDatabaseTest, HashCaching) { PopulateDatabaseForCacheTest(); // We should have both full hashes in the cache. EXPECT_EQ(2U, database_->browse_gethash_cache_.size()); // Test the cache lookup for the first prefix. std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); ASSERT_EQ(1U, cache_hits.size()); EXPECT_TRUE(SBFullHashEqual( cache_hits[0].hash, SBFullHashForString("www.evil.com/phishing.html"))); prefix_hits.clear(); cache_hits.clear(); // Test the cache lookup for the second prefix. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); ASSERT_EQ(1U, cache_hits.size()); EXPECT_TRUE(SBFullHashEqual( cache_hits[0].hash, SBFullHashForString("www.evil.com/malware.html"))); prefix_hits.clear(); cache_hits.clear(); // Test removing a prefix via a sub chunk. ScopedVector chunks; chunks.push_back(SubChunkPrefixValue(2, "www.evil.com/phishing.html", 1)); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // This prefix should still be there, but cached fullhash should be gone. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.evil.com/malware.html"), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); prefix_hits.clear(); cache_hits.clear(); // This prefix should be gone. EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits)); prefix_hits.clear(); cache_hits.clear(); // Test that an AddDel for the original chunk removes the last cached entry. ASSERT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 1); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->browse_gethash_cache_.empty()); prefix_hits.clear(); cache_hits.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::map* hash_cache = &database_->browse_gethash_cache_; EXPECT_EQ(2U, hash_cache->size()); // Now adjust one of the entries times to be in the past. const SBPrefix key = SBPrefixForString("www.evil.com/malware.html"); std::map::iterator iter = hash_cache->find(key); ASSERT_TRUE(iter != hash_cache->end()); iter->second.expire_after = Time::Now() - TimeDelta::FromMinutes(1); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); EXPECT_EQ(1U, prefix_hits.size()); EXPECT_TRUE(cache_hits.empty()); // Expired entry should have been removed from cache. EXPECT_EQ(1U, hash_cache->size()); // This entry should still exist. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); EXPECT_EQ(1U, cache_hits.size()); // Testing prefix miss caching. First, we clear out the existing database, // Since PopulateDatabaseForCacheTest() doesn't handle adding duplicate // chunks. ASSERT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 1); database_->UpdateFinished(true); // Cache should be cleared after updating. EXPECT_TRUE(hash_cache->empty()); std::vector prefix_misses; std::vector empty_full_hash; prefix_misses.push_back(SBPrefixForString("http://www.bad.com/malware.html")); prefix_misses.push_back( SBPrefixForString("http://www.bad.com/phishing.html")); database_->CacheHashResults(prefix_misses, empty_full_hash, kCacheLifetime); // Prefixes with no full results are misses. EXPECT_EQ(hash_cache->size(), prefix_misses.size()); ASSERT_TRUE( hash_cache->count(SBPrefixForString("http://www.bad.com/malware.html"))); EXPECT_TRUE( hash_cache->find(SBPrefixForString("http://www.bad.com/malware.html")) ->second.full_hashes.empty()); ASSERT_TRUE( hash_cache->count(SBPrefixForString("http://www.bad.com/phishing.html"))); EXPECT_TRUE( hash_cache->find(SBPrefixForString("http://www.bad.com/phishing.html")) ->second.full_hashes.empty()); // Update the database. PopulateDatabaseForCacheTest(); // 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. prefix_hits.clear(); cache_hits.clear(); prefix_misses.clear(); empty_full_hash.clear(); prefix_misses.push_back(SBPrefixForString("www.evil.com/phishing.html")); database_->CacheHashResults(prefix_misses, empty_full_hash, kCacheLifetime); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits)); prefix_hits.clear(); cache_hits.clear(); // Test receiving a full add chunk. chunks.clear(); chunks.push_back(AddChunkFullHash2Value(20, "www.fullevil.com/bad1.html", "www.fullevil.com/bad2.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad1.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.fullevil.com/bad1.html"), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad2.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.fullevil.com/bad2.html"), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); prefix_hits.clear(); cache_hits.clear(); // Test receiving a full sub chunk, which will remove one of the full adds. chunks.clear(); chunks.push_back(SubChunkFullHashValue(200, "www.fullevil.com/bad1.html", 20)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad1.html"), &prefix_hits, &cache_hits)); // There should be one remaining full add. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad2.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.fullevil.com/bad2.html"), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); prefix_hits.clear(); cache_hits.clear(); // Now test an AddDel for the remaining full add. ASSERT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 20); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad1.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.fullevil.com/bad2.html"), &prefix_hits, &cache_hits)); // Add a fullhash which has a prefix collision for a known url. static const char kExampleFine[] = "www.example.com/fine.html"; static const char kExampleCollision[] = "www.example.com/3123364814/malware.htm"; ASSERT_EQ(SBPrefixForString(kExampleFine), SBPrefixForString(kExampleCollision)); ASSERT_TRUE(database_->UpdateStarted(&lists)); { ScopedVector chunks; chunks.push_back(AddChunkPrefixValue(21, kExampleCollision)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); } database_->UpdateFinished(true); // Expect a prefix hit due to the collision between |kExampleFine| and // |kExampleCollision|. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL(std::string("http://") + kExampleFine), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kExampleFine), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); // Cache gethash response for |kExampleCollision|. { SBFullHashResult result; result.hash = SBFullHashForString(kExampleCollision); result.list_id = safe_browsing_util::MALWARE; database_->CacheHashResults(std::vector(1, result.hash.prefix), std::vector(1, result), kCacheLifetime); } // The cached response means the collision no longer causes a hit. EXPECT_FALSE(database_->ContainsBrowseUrl( GURL(std::string("http://") + kExampleFine), &prefix_hits, &cache_hits)); } // 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(); base::MessageLoop loop; SafeBrowsingStoreFile* store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(store, NULL, NULL, NULL, NULL, NULL, NULL)); database_->Init(database_filename_); // This will cause an empty database to be created. std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->UpdateFinished(true); // Create a sub chunk to insert. ScopedVector chunks; chunks.push_back(SubChunkPrefixValue(7, "www.subbed.com/notevil1.html", 19)); // Corrupt the file by corrupting the checksum, which is not checked // until the entire table is read in |UpdateFinished()|. FILE* fp = base::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. ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // Database file still exists until the corruption handler has run. EXPECT_TRUE(base::PathExists(database_filename_)); // Flush through the corruption-handler task. VLOG(1) << "Expect failed check on: SafeBrowsing database reset"; base::MessageLoop::current()->RunUntilIdle(); } // Database file should not exist. EXPECT_FALSE(base::PathExists(database_filename_)); // Run the update again successfully. ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); EXPECT_TRUE(base::PathExists(database_filename_)); database_.reset(); } // Checks database reading and writing. TEST_F(SafeBrowsingDatabaseTest, ContainsDownloadUrl) { database_.reset(); base::MessageLoop loop; 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, NULL, NULL, NULL, NULL)); database_->Init(database_filename_); const char kEvil1Url1[] = "www.evil1.com/download1/"; const char kEvil1Url2[] = "www.evil1.com/download2.html"; // Add a simple chunk with one hostkey for download url list. ScopedVector chunks; chunks.push_back(AddChunkPrefix2Value(1, kEvil1Url1, kEvil1Url2)); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kBinUrlList, chunks.get()); database_->UpdateFinished(true); std::vector prefix_hits; std::vector urls(1); urls[0] = GURL(std::string("http://") + kEvil1Url1); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]); urls[0] = GURL(std::string("http://") + kEvil1Url2); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[0]); urls[0] = GURL(std::string("https://") + kEvil1Url2); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[0]); urls[0] = GURL(std::string("ftp://") + kEvil1Url2); EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[0]); 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(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[0]); // 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(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]); // 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(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]); // 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(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]); // 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(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]); // 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(2U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]); EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[1]); database_.reset(); } // Checks that the whitelists are handled properly. TEST_F(SafeBrowsingDatabaseTest, Whitelists) { database_.reset(); // We expect all calls to ContainsCsdWhitelistedUrl in particular to be made // from the IO thread. In general the whitelist lookups are thread-safe. content::TestBrowserThreadBundle thread_bundle_; // If the whitelist is disabled everything should match the whitelist. database_.reset(new SafeBrowsingDatabaseNew(new SafeBrowsingStoreFile(), NULL, NULL, NULL, NULL, NULL, NULL)); database_->Init(database_filename_); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://www.phishing.com/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://www.phishing.com/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedString("asdf")); SafeBrowsingStoreFile* browse_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* csd_whitelist_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* download_whitelist_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* extension_blacklist_store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(browse_store, NULL, csd_whitelist_store, download_whitelist_store, extension_blacklist_store, NULL, NULL)); 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 kGood2Url1[] = "www.good2.com/c"; // Should match '/c/bla'. // good3.com/a/b/c/d/e/f/g/ should match because it's a whitelist. const char kGood3Url1[] = "good3.com/"; const char kGoodString[] = "good_string"; ScopedVector csd_chunks; ScopedVector download_chunks; // Add two simple chunks to the csd whitelist. csd_chunks.push_back(AddChunkFullHash2Value(1, kGood1Url1, kGood1Url2)); csd_chunks.push_back(AddChunkFullHashValue(2, kGood2Url1)); download_chunks.push_back(AddChunkFullHashValue(2, kGood2Url1)); download_chunks.push_back(AddChunkFullHashValue(3, kGoodString)); download_chunks.push_back(AddChunkFullHashValue(4, kGood3Url1)); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks.get()); database_->InsertChunks(safe_browsing_util::kDownloadWhiteList, download_chunks.get()); 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/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://") + kGood2Url1 + "/c"))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://") + kGood2Url1 + "/c?bla"))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://") + kGood2Url1 + "/c/bla"))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://good3.com/a/b/c/d/e/f/g/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://a.b.good3.com/")))); EXPECT_FALSE(database_->ContainsDownloadWhitelistedString("asdf")); EXPECT_TRUE(database_->ContainsDownloadWhitelistedString(kGoodString)); EXPECT_FALSE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://www.google.com/")))); // The CSD whitelist killswitch is not present. EXPECT_FALSE(database_->IsCsdWhitelistKillSwitchOn()); // Test only add the malware IP killswitch csd_chunks.clear(); csd_chunks.push_back(AddChunkFullHashValue( 15, "sb-ssl.google.com/safebrowsing/csd/killswitch_malware")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks.get()); database_->UpdateFinished(true); EXPECT_TRUE(database_->IsMalwareIPMatchKillSwitchOn()); // The CSD whitelist killswitch is not present. EXPECT_FALSE(database_->IsCsdWhitelistKillSwitchOn()); // Test that the kill-switch works as intended. csd_chunks.clear(); download_chunks.clear(); lists.clear(); csd_chunks.push_back(AddChunkFullHashValue( 5, "sb-ssl.google.com/safebrowsing/csd/killswitch")); download_chunks.push_back(AddChunkFullHashValue( 5, "sb-ssl.google.com/safebrowsing/csd/killswitch")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks.get()); database_->InsertChunks(safe_browsing_util::kDownloadWhiteList, download_chunks.get()); database_->UpdateFinished(true); // The CSD whitelist killswitch is present. EXPECT_TRUE(database_->IsCsdWhitelistKillSwitchOn()); EXPECT_TRUE(database_->IsMalwareIPMatchKillSwitchOn()); 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/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("https://") + kGood1Url2 + "/c.html"))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://www.google.com/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://www.phishing_url.com/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedString("asdf")); EXPECT_TRUE(database_->ContainsDownloadWhitelistedString(kGoodString)); // Remove the kill-switch and verify that we can recover. csd_chunks.clear(); download_chunks.clear(); lists.clear(); csd_chunks.push_back(SubChunkFullHashValue( 1, "sb-ssl.google.com/safebrowsing/csd/killswitch", 5)); csd_chunks.push_back(SubChunkFullHashValue( 10, "sb-ssl.google.com/safebrowsing/csd/killswitch_malware", 15)); download_chunks.push_back(SubChunkFullHashValue( 1, "sb-ssl.google.com/safebrowsing/csd/killswitch", 5)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks.get()); database_->InsertChunks(safe_browsing_util::kDownloadWhiteList, download_chunks.get()); database_->UpdateFinished(true); EXPECT_FALSE(database_->IsMalwareIPMatchKillSwitchOn()); EXPECT_FALSE(database_->IsCsdWhitelistKillSwitchOn()); 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/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("https://") + kGood2Url1 + "/c/bla"))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("https://good3.com/")))); EXPECT_TRUE(database_->ContainsDownloadWhitelistedString(kGoodString)); EXPECT_FALSE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://www.google.com/")))); EXPECT_FALSE(database_->ContainsDownloadWhitelistedUrl( GURL(std::string("http://www.phishing_url.com/")))); EXPECT_FALSE(database_->ContainsDownloadWhitelistedString("asdf")); database_.reset(); } // Test to make sure we could insert chunk list that // contains entries for the same host. TEST_F(SafeBrowsingDatabaseTest, SameHostEntriesOkay) { ScopedVector chunks; // Add a malware add chunk with two entries of the same host. chunks.push_back(AddChunkPrefix2Value(1, "www.evil.com/malware1.html", "www.evil.com/malware2.html")); // Insert the testing chunks into database. std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_LE(1U, lists.size()); EXPECT_EQ(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. chunks.clear(); chunks.push_back(AddChunkPrefix2Value(47, "www.evil.com/phishing1.html", "www.evil.com/phishing2.html")); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks.get()); database_->UpdateFinished(true); GetListsInfo(&lists); ASSERT_EQ(2U, lists.size()); EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name); EXPECT_EQ("1", lists[0].adds); EXPECT_TRUE(lists[0].subs.empty()); EXPECT_EQ(safe_browsing_util::kPhishingList, lists[1].name); EXPECT_EQ("47", lists[1].adds); EXPECT_TRUE(lists[1].subs.empty()); std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware1.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware2.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing1.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing2.html"), &prefix_hits, &cache_hits)); // Test removing a single prefix from the add chunk. // Remove the prefix that added first. chunks.clear(); chunks.push_back(SubChunkPrefixValue(4, "www.evil.com/malware1.html", 1)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // Remove the prefix that added last. chunks.clear(); chunks.push_back(SubChunkPrefixValue(5, "www.evil.com/phishing2.html", 47)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kPhishingList, chunks.get()); database_->UpdateFinished(true); // Verify that the database contains urls expected. EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware1.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware2.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing1.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/phishing2.html"), &prefix_hits, &cache_hits)); } // 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) { ScopedVector chunks; base::FilePath filename = database_->BrowseDBFilename(database_filename_); // Prime the database. std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html")); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // Get an older time to reset the lastmod time for detecting whether // the file has been updated. base::File::Info before_info, after_info; ASSERT_TRUE(base::GetFileInfo(filename, &before_info)); const Time old_last_modified = before_info.last_modified - 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(base::TouchFile(filename, old_last_modified, old_last_modified)); ASSERT_TRUE(base::GetFileInfo(filename, &before_info)); ASSERT_TRUE(database_->UpdateStarted(&lists)); chunks.push_back(AddChunkPrefixValue(2, "www.foo.com/malware.html")); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); ASSERT_TRUE(base::GetFileInfo(filename, &after_info)); EXPECT_LT(before_info.last_modified, after_info.last_modified); // Deleting a chunk updates the database file. ASSERT_TRUE(base::TouchFile(filename, old_last_modified, old_last_modified)); ASSERT_TRUE(base::GetFileInfo(filename, &before_info)); ASSERT_TRUE(database_->UpdateStarted(&lists)); AddDelChunk(safe_browsing_util::kMalwareList, 2); database_->UpdateFinished(true); ASSERT_TRUE(base::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(base::TouchFile(filename, old_last_modified, old_last_modified)); ASSERT_TRUE(base::GetFileInfo(filename, &before_info)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->UpdateFinished(true); ASSERT_TRUE(base::GetFileInfo(filename, &after_info)); EXPECT_EQ(before_info.last_modified, after_info.last_modified); } // Test that a filter file is written out during update and read back // in during setup. TEST_F(SafeBrowsingDatabaseTest, FilterFile) { // Create a database with trivial example data and write it out. { // Prime the database. std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); ScopedVector chunks; chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html")); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); } // Find the malware url in the database, don't find a good url. std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.good.com/goodware.html"), &prefix_hits, &cache_hits)); base::FilePath filter_file = database_->PrefixSetForFilename( database_->BrowseDBFilename(database_filename_)); // After re-creating the database, it should have a filter read from // a file, so it should find the same results. ASSERT_TRUE(base::PathExists(filter_file)); database_.reset(new SafeBrowsingDatabaseNew); database_->Init(database_filename_); EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.good.com/goodware.html"), &prefix_hits, &cache_hits)); // If there is no filter file, the database cannot find malware urls. base::DeleteFile(filter_file, false); ASSERT_FALSE(base::PathExists(filter_file)); database_.reset(new SafeBrowsingDatabaseNew); database_->Init(database_filename_); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.good.com/goodware.html"), &prefix_hits, &cache_hits)); } TEST_F(SafeBrowsingDatabaseTest, CachedFullMiss) { const SBPrefix kPrefix1 = 1001U; const SBFullHash kFullHash1_1 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x01"); const SBPrefix kPrefix2 = 1002U; const SBFullHash kFullHash2_1 = SBFullHashForPrefixAndSuffix(kPrefix2, "\x01"); // Insert prefix kPrefix1 and kPrefix2 into database. ScopedVector chunks; chunks.push_back(AddChunkPrefix(1, kPrefix1)); chunks.push_back(AddChunkPrefix(2, kPrefix2)); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); { // Cache a full miss result for kPrefix1. std::vector prefixes(1, kPrefix1); std::vector cache_results; database_->CacheHashResults(prefixes, cache_results, kCacheLifetime); } { // kFullHash1_1 gets no prefix hit because of the cached item, and also does // not have a cache hit. std::vector full_hashes(1, kFullHash1_1); std::vector prefix_hits; std::vector cache_hits; EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); // kFullHash2_1 gets a hit from the prefix in the database. full_hashes.push_back(kFullHash2_1); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix2, prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); } } TEST_F(SafeBrowsingDatabaseTest, CachedPrefixHitFullMiss) { const SBPrefix kPrefix1 = 1001U; const SBFullHash kFullHash1_1 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x01"); const SBFullHash kFullHash1_2 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x02"); const SBFullHash kFullHash1_3 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x03"); const SBPrefix kPrefix2 = 1002U; const SBFullHash kFullHash2_1 = SBFullHashForPrefixAndSuffix(kPrefix2, "\x01"); const SBPrefix kPrefix3 = 1003U; const SBFullHash kFullHash3_1 = SBFullHashForPrefixAndSuffix(kPrefix3, "\x01"); // Insert prefix kPrefix1 and kPrefix2 into database. ScopedVector chunks; chunks.push_back(AddChunkPrefix(1, kPrefix1)); chunks.push_back(AddChunkPrefix(2, kPrefix2)); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); { // kFullHash1_1 has a prefix hit of kPrefix1. std::vector full_hashes; full_hashes.push_back(kFullHash1_1); std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix1, prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); // kFullHash2_1 has a prefix hit of kPrefix2. full_hashes.push_back(kFullHash2_1); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(2U, prefix_hits.size()); EXPECT_EQ(kPrefix1, prefix_hits[0]); EXPECT_EQ(kPrefix2, prefix_hits[1]); EXPECT_TRUE(cache_hits.empty()); // kFullHash3_1 has no hits. full_hashes.push_back(kFullHash3_1); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(2U, prefix_hits.size()); EXPECT_EQ(kPrefix1, prefix_hits[0]); EXPECT_EQ(kPrefix2, prefix_hits[1]); EXPECT_TRUE(cache_hits.empty()); } { // Cache a fullhash result for two kPrefix1 full hashes. std::vector prefixes(1, kPrefix1); std::vector cache_results; SBFullHashResult full_hash_result; full_hash_result.list_id = safe_browsing_util::MALWARE; full_hash_result.hash = kFullHash1_1; cache_results.push_back(full_hash_result); full_hash_result.hash = kFullHash1_3; cache_results.push_back(full_hash_result); database_->CacheHashResults(prefixes, cache_results, kCacheLifetime); } { // kFullHash1_1 should now see a cache hit. std::vector full_hashes(1, kFullHash1_1); std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); ASSERT_EQ(1U, cache_hits.size()); EXPECT_TRUE(SBFullHashEqual(kFullHash1_1, cache_hits[0].hash)); // Adding kFullHash2_1 will see the existing cache hit plus the prefix hit // for kPrefix2. full_hashes.push_back(kFullHash2_1); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix2, prefix_hits[0]); ASSERT_EQ(1U, cache_hits.size()); EXPECT_TRUE(SBFullHashEqual(kFullHash1_1, cache_hits[0].hash)); // kFullHash1_3 also gets a cache hit. full_hashes.push_back(kFullHash1_3); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix2, prefix_hits[0]); ASSERT_EQ(2U, cache_hits.size()); EXPECT_TRUE(SBFullHashEqual(kFullHash1_1, cache_hits[0].hash)); EXPECT_TRUE(SBFullHashEqual(kFullHash1_3, cache_hits[1].hash)); } { // Check if DB contains only kFullHash1_3. Should return a cache hit. std::vector full_hashes(1, kFullHash1_3); std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); ASSERT_EQ(1U, cache_hits.size()); EXPECT_TRUE(SBFullHashEqual(kFullHash1_3, cache_hits[0].hash)); } { // kFullHash1_2 has no cache hit, and no prefix hit because of the cache for // kPrefix1. std::vector full_hashes(1, kFullHash1_2); std::vector prefix_hits; std::vector cache_hits; EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); // Other prefix hits possible when kFullHash1_2 hits nothing. full_hashes.push_back(kFullHash2_1); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix2, prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); } } TEST_F(SafeBrowsingDatabaseTest, BrowseFullHashMatching) { const SBPrefix kPrefix1 = 1001U; const SBFullHash kFullHash1_1 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x01"); const SBFullHash kFullHash1_2 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x02"); const SBFullHash kFullHash1_3 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x03"); // Insert two full hashes with a shared prefix. ScopedVector chunks; chunks.push_back(AddChunkFullHash(1, kFullHash1_1)); chunks.push_back(AddChunkFullHash(2, kFullHash1_2)); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); { // Check a full hash which isn't present. std::vector full_hashes(1, kFullHash1_3); std::vector prefix_hits; std::vector cache_hits; EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); // Also one which is present, should have a prefix hit. full_hashes.push_back(kFullHash1_1); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix1, prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); // Two full hash matches with the same prefix should return one prefix hit. full_hashes.push_back(kFullHash1_2); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix1, prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); } { // Cache a gethash result for kFullHash1_2. SBFullHashResult full_hash_result; full_hash_result.list_id = safe_browsing_util::MALWARE; full_hash_result.hash = kFullHash1_2; std::vector prefixes(1, kPrefix1); std::vector cache_results(1, full_hash_result); database_->CacheHashResults(prefixes, cache_results, kCacheLifetime); } { // kFullHash1_3 should still return false, because the cached // result for kPrefix1 doesn't contain kFullHash1_3. std::vector full_hashes(1, kFullHash1_3); std::vector prefix_hits; std::vector cache_hits; EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); // kFullHash1_1 is also not in the cached result, which takes // priority over the database. prefix_hits.clear(); full_hashes.push_back(kFullHash1_1); cache_hits.clear(); EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); // kFullHash1_2 is in the cached result. full_hashes.push_back(kFullHash1_2); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); ASSERT_EQ(1U, cache_hits.size()); EXPECT_TRUE(SBFullHashEqual(kFullHash1_2, cache_hits[0].hash)); } // Remove kFullHash1_1 from the database. chunks.clear(); chunks.push_back(SubChunkFullHash(11, kFullHash1_1, 1)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // Cache should be cleared after updating. EXPECT_TRUE(database_->browse_gethash_cache_.empty()); { // Now the database doesn't contain kFullHash1_1. std::vector full_hashes(1, kFullHash1_1); std::vector prefix_hits; std::vector cache_hits; EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); // Nor kFullHash1_3. full_hashes.push_back(kFullHash1_3); prefix_hits.clear(); cache_hits.clear(); EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); // Still has kFullHash1_2. full_hashes.push_back(kFullHash1_2); prefix_hits.clear(); cache_hits.clear(); EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix1, prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); } // Remove kFullHash1_2 from the database. chunks.clear(); chunks.push_back(SubChunkFullHash(12, kFullHash1_2, 2)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); // Cache should be cleared after updating. EXPECT_TRUE(database_->browse_gethash_cache_.empty()); { // None are present. std::vector full_hashes; std::vector prefix_hits; std::vector cache_hits; full_hashes.push_back(kFullHash1_1); full_hashes.push_back(kFullHash1_2); full_hashes.push_back(kFullHash1_3); EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); } } TEST_F(SafeBrowsingDatabaseTest, BrowseFullHashAndPrefixMatching) { const SBPrefix kPrefix1 = 1001U; const SBFullHash kFullHash1_1 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x01"); const SBFullHash kFullHash1_2 = SBFullHashForPrefixAndSuffix(kPrefix1, "\x02"); ScopedVector chunks; chunks.push_back(AddChunkFullHash(1, kFullHash1_1)); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); { // kFullHash1_2 does not match kFullHash1_1. std::vector full_hashes(1, kFullHash1_2); std::vector prefix_hits; std::vector cache_hits; EXPECT_FALSE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); } // Add a prefix match. chunks.clear(); chunks.push_back(AddChunkPrefix(2, kPrefix1)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); { // kFullHash1_2 does match kPrefix1. std::vector full_hashes(1, kFullHash1_2); std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix1, prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); } // Remove the full hash. chunks.clear(); chunks.push_back(SubChunkFullHash(11, kFullHash1_1, 1)); ASSERT_TRUE(database_->UpdateStarted(&lists)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); database_->UpdateFinished(true); { // kFullHash1_2 still returns true due to the prefix hit. std::vector full_hashes(1, kFullHash1_2); std::vector prefix_hits; std::vector cache_hits; EXPECT_TRUE(database_->ContainsBrowseUrlHashes( full_hashes, &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(kPrefix1, prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); } } TEST_F(SafeBrowsingDatabaseTest, MalwareIpBlacklist) { database_.reset(); SafeBrowsingStoreFile* browse_store = new SafeBrowsingStoreFile(); SafeBrowsingStoreFile* ip_blacklist_store = new SafeBrowsingStoreFile(); database_.reset(new SafeBrowsingDatabaseNew(browse_store, NULL, NULL, NULL, NULL, NULL, ip_blacklist_store)); database_->Init(database_filename_); std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); ScopedVector chunks; // IPv4 prefix match for ::ffff:192.168.1.0/120. chunks.push_back(AddChunkHashedIpValue(1, "::ffff:192.168.1.0", 120)); // IPv4 exact match for ::ffff:192.1.1.1. chunks.push_back(AddChunkHashedIpValue(2, "::ffff:192.1.1.1", 128)); // IPv6 exact match for: fe80::31a:a0ff:fe10:786e/128. chunks.push_back(AddChunkHashedIpValue(3, "fe80::31a:a0ff:fe10:786e", 128)); // IPv6 prefix match for: 2620:0:1000:3103::/64. chunks.push_back(AddChunkHashedIpValue(4, "2620:0:1000:3103::", 64)); // IPv4 prefix match for ::ffff:192.1.122.0/119. chunks.push_back(AddChunkHashedIpValue(5, "::ffff:192.1.122.0", 119)); // IPv4 prefix match for ::ffff:192.1.128.0/113. chunks.push_back(AddChunkHashedIpValue(6, "::ffff:192.1.128.0", 113)); database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks.get()); database_->UpdateFinished(true); EXPECT_FALSE(database_->ContainsMalwareIP("192.168.0.255")); EXPECT_TRUE(database_->ContainsMalwareIP("192.168.1.0")); EXPECT_TRUE(database_->ContainsMalwareIP("192.168.1.255")); EXPECT_TRUE(database_->ContainsMalwareIP("192.168.1.10")); EXPECT_TRUE(database_->ContainsMalwareIP("::ffff:192.168.1.2")); EXPECT_FALSE(database_->ContainsMalwareIP("192.168.2.0")); EXPECT_FALSE(database_->ContainsMalwareIP("192.1.1.0")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.1.1")); EXPECT_FALSE(database_->ContainsMalwareIP("192.1.1.2")); EXPECT_FALSE(database_->ContainsMalwareIP( "2620:0:1000:3102:ffff:ffff:ffff:ffff")); EXPECT_TRUE(database_->ContainsMalwareIP("2620:0:1000:3103::")); EXPECT_TRUE(database_->ContainsMalwareIP( "2620:0:1000:3103:ffff:ffff:ffff:ffff")); EXPECT_FALSE(database_->ContainsMalwareIP("2620:0:1000:3104::")); EXPECT_FALSE(database_->ContainsMalwareIP("fe80::21a:a0ff:fe10:786d")); EXPECT_TRUE(database_->ContainsMalwareIP("fe80::31a:a0ff:fe10:786e")); EXPECT_FALSE(database_->ContainsMalwareIP("fe80::21a:a0ff:fe10:786f")); EXPECT_FALSE(database_->ContainsMalwareIP("192.1.121.255")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.122.0")); EXPECT_TRUE(database_->ContainsMalwareIP("::ffff:192.1.122.1")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.122.255")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.123.0")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.123.255")); EXPECT_FALSE(database_->ContainsMalwareIP("192.1.124.0")); EXPECT_FALSE(database_->ContainsMalwareIP("192.1.127.255")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.128.0")); EXPECT_TRUE(database_->ContainsMalwareIP("::ffff:192.1.128.1")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.128.255")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.255.0")); EXPECT_TRUE(database_->ContainsMalwareIP("192.1.255.255")); EXPECT_FALSE(database_->ContainsMalwareIP("192.2.0.0")); } TEST_F(SafeBrowsingDatabaseTest, ContainsBrowseURL) { std::vector lists; ASSERT_TRUE(database_->UpdateStarted(&lists)); // Add a host-level hit. { ScopedVector chunks; chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/")); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); } // Add a specific fullhash. static const char kWhateverMalware[] = "www.whatever.com/malware.html"; { ScopedVector chunks; chunks.push_back(AddChunkFullHashValue(2, kWhateverMalware)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); } // Add a fullhash which has a prefix collision for a known url. static const char kExampleFine[] = "www.example.com/fine.html"; static const char kExampleCollision[] = "www.example.com/3123364814/malware.htm"; ASSERT_EQ(SBPrefixForString(kExampleFine), SBPrefixForString(kExampleCollision)); { ScopedVector chunks; chunks.push_back(AddChunkFullHashValue(3, kExampleCollision)); database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get()); } database_->UpdateFinished(true); std::vector prefix_hits; std::vector cache_hits; // Anything will hit the host prefix. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString("www.evil.com/"), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); // Hit the specific URL prefix. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL(std::string("http://") + kWhateverMalware), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kWhateverMalware), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); // Other URLs at that host are fine. EXPECT_FALSE(database_->ContainsBrowseUrl( GURL("http://www.whatever.com/fine.html"), &prefix_hits, &cache_hits)); EXPECT_TRUE(prefix_hits.empty()); EXPECT_TRUE(cache_hits.empty()); // Hit the specific URL full hash. EXPECT_TRUE(database_->ContainsBrowseUrl( GURL(std::string("http://") + kExampleCollision), &prefix_hits, &cache_hits)); ASSERT_EQ(1U, prefix_hits.size()); EXPECT_EQ(SBPrefixForString(kExampleCollision), prefix_hits[0]); EXPECT_TRUE(cache_hits.empty()); // This prefix collides, but no full hash match. EXPECT_FALSE(database_->ContainsBrowseUrl( GURL(std::string("http://") + kExampleFine), &prefix_hits, &cache_hits)); }