| // 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 <stddef.h> |
| |
| #include <utility> |
| |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/sha1.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/test/test_simple_task_runner.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 "crypto/sha2.h" |
| #include "net/base/ip_address.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/platform_test.h" |
| #include "url/gurl.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| |
| namespace safe_browsing { |
| |
| 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::IPAddress ip_address; |
| EXPECT_TRUE(ip_address.AssignFromIPLiteral(ip_prefix)); |
| EXPECT_TRUE(ip_address.IsIPv6()); |
| const std::string hashed_ip_prefix = |
| base::SHA1HashString(net::IPAddressToPackedString(ip_address)); |
| std::string hash(crypto::kSHA256Length, '\0'); |
| hash.replace(0, hashed_ip_prefix.size(), hashed_ip_prefix); |
| hash[base::kSHA1Length] = static_cast<char>(prefix_size); |
| return hash; |
| } |
| |
| // Helper to build a chunk. Caller takes ownership. |
| std::unique_ptr<SBChunkData> BuildChunk( |
| int chunk_number, |
| ChunkData::ChunkType chunk_type, |
| ChunkData::PrefixType prefix_type, |
| const void* data, |
| size_t data_size, |
| const std::vector<int>& add_chunk_numbers) { |
| std::unique_ptr<ChunkData> raw_data(new 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 std::make_unique<SBChunkData>(std::move(raw_data)); |
| } |
| |
| // Create add chunk with a single prefix. |
| std::unique_ptr<SBChunkData> AddChunkPrefix(int chunk_number, SBPrefix prefix) { |
| return BuildChunk(chunk_number, ChunkData::ADD, ChunkData::PREFIX_4B, &prefix, |
| sizeof(prefix), std::vector<int>()); |
| } |
| |
| // Create add chunk with a single prefix generated from |value|. |
| std::unique_ptr<SBChunkData> AddChunkPrefixValue(int chunk_number, |
| const std::string& value) { |
| return AddChunkPrefix(chunk_number, SBPrefixForString(value)); |
| } |
| |
| // Generate an add chunk with two prefixes. |
| std::unique_ptr<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, ChunkData::ADD, ChunkData::PREFIX_4B, |
| &prefixes[0], sizeof(prefixes), std::vector<int>()); |
| } |
| |
| // Generate an add chunk with four prefixes. |
| std::unique_ptr<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, ChunkData::ADD, ChunkData::PREFIX_4B, |
| &prefixes[0], sizeof(prefixes), std::vector<int>()); |
| } |
| |
| // Generate an add chunk with a full hash. |
| std::unique_ptr<SBChunkData> AddChunkFullHash(int chunk_number, |
| SBFullHash full_hash) { |
| return BuildChunk(chunk_number, ChunkData::ADD, ChunkData::FULL_32B, |
| &full_hash, sizeof(full_hash), std::vector<int>()); |
| } |
| |
| // Generate an add chunk with a full hash generated from |value|. |
| std::unique_ptr<SBChunkData> AddChunkFullHashValue(int chunk_number, |
| const std::string& value) { |
| return AddChunkFullHash(chunk_number, |
| SBFullHashForString(value)); |
| } |
| |
| // Generate an add chunk with two full hashes. |
| std::unique_ptr<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, ChunkData::ADD, ChunkData::FULL_32B, |
| &full_hashes[0], sizeof(full_hashes), std::vector<int>()); |
| } |
| |
| // Generate a sub chunk with a prefix generated from |value|. |
| std::unique_ptr<SBChunkData> SubChunkPrefixValue(int chunk_number, |
| const std::string& value, |
| int add_chunk_number) { |
| const SBPrefix prefix = SBPrefixForString(value); |
| return BuildChunk(chunk_number, ChunkData::SUB, ChunkData::PREFIX_4B, &prefix, |
| sizeof(prefix), std::vector<int>(1, add_chunk_number)); |
| } |
| |
| // Generate a sub chunk with two prefixes. |
| std::unique_ptr<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<int> add_chunk_numbers; |
| add_chunk_numbers.push_back(add_chunk_number1); |
| add_chunk_numbers.push_back(add_chunk_number2); |
| return BuildChunk(chunk_number, ChunkData::SUB, ChunkData::PREFIX_4B, |
| &prefixes[0], sizeof(prefixes), add_chunk_numbers); |
| } |
| |
| // Generate a sub chunk with a full hash. |
| std::unique_ptr<SBChunkData> SubChunkFullHash(int chunk_number, |
| SBFullHash full_hash, |
| int add_chunk_number) { |
| return BuildChunk(chunk_number, ChunkData::SUB, ChunkData::FULL_32B, |
| &full_hash, sizeof(full_hash), |
| std::vector<int>(1, add_chunk_number)); |
| } |
| |
| // Generate a sub chunk with a full hash generated from |value|. |
| std::unique_ptr<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. |
| std::unique_ptr<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, ChunkData::ADD, ChunkData::FULL_32B, |
| &full_hash, sizeof(full_hash), std::vector<int>()); |
| } |
| |
| // 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: |
| SafeBrowsingDatabaseTest() : task_runner_(new base::TestSimpleTaskRunner) {} |
| |
| void SetUp() override { |
| PlatformTest::SetUp(); |
| |
| // Setup a database in a temporary directory. |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| database_filename_ = |
| temp_dir_.GetPath().AppendASCII("SafeBrowsingTestDatabase"); |
| |
| ResetAndReloadFullDatabase(); |
| } |
| |
| void TearDown() override { |
| database_.reset(); |
| |
| PlatformTest::TearDown(); |
| } |
| |
| // Reloads the |database_| in a new SafeBrowsingDatabaseNew object with all |
| // stores enabled. |
| void ResetAndReloadFullDatabase() { |
| SafeBrowsingStoreFile* browse_store = |
| new SafeBrowsingStoreFile(task_runner_); |
| SafeBrowsingStoreFile* download_store = |
| new SafeBrowsingStoreFile(task_runner_); |
| SafeBrowsingStoreFile* csd_whitelist_store = |
| new SafeBrowsingStoreFile(task_runner_); |
| SafeBrowsingStoreFile* download_whitelist_store = |
| new SafeBrowsingStoreFile(task_runner_); |
| SafeBrowsingStoreFile* extension_blacklist_store = |
| new SafeBrowsingStoreFile(task_runner_); |
| SafeBrowsingStoreFile* ip_blacklist_store = |
| new SafeBrowsingStoreFile(task_runner_); |
| SafeBrowsingStoreFile* unwanted_software_store = |
| new SafeBrowsingStoreFile(task_runner_); |
| SafeBrowsingStoreFile* resource_blacklist_store = |
| new SafeBrowsingStoreFile(task_runner_); |
| database_.reset(new SafeBrowsingDatabaseNew( |
| task_runner_, |
| browse_store, |
| download_store, |
| csd_whitelist_store, |
| download_whitelist_store, |
| extension_blacklist_store, |
| ip_blacklist_store, |
| unwanted_software_store, |
| resource_blacklist_store)); |
| database_->Init(database_filename_); |
| } |
| |
| bool ContainsDownloadUrl(const std::vector<GURL>& urls, |
| std::vector<SBPrefix>* prefix_hits) { |
| std::vector<SBPrefix> prefixes; |
| SafeBrowsingDatabase::GetDownloadUrlPrefixes(urls, &prefixes); |
| return database_->ContainsDownloadUrlPrefixes(prefixes, prefix_hits); |
| } |
| |
| bool ContainsResourceUrl(const GURL& url, |
| std::vector<SBPrefix>* prefix_hits) { |
| std::vector<SBFullHash> full_hashes; |
| UrlToFullHashes(url, false, &full_hashes); |
| std::vector<SBPrefix> prefixes(full_hashes.size()); |
| for (size_t i = 0; i < full_hashes.size(); ++i) |
| prefixes[i] = full_hashes[i].prefix; |
| return database_->ContainsResourceUrlPrefixes(prefixes, prefix_hits); |
| } |
| |
| void GetListsInfo(std::vector<SBListChunkRanges>* 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<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_refptr<base::TestSimpleTaskRunner> task_runner_; |
| std::unique_ptr<SafeBrowsingDatabaseNew> database_; |
| base::FilePath database_filename_; |
| base::ScopedTempDir temp_dir_; |
| }; |
| |
| // Tests retrieving list name information. |
| TEST_F(SafeBrowsingDatabaseTest, BrowseListsInfo) { |
| std::vector<SBListChunkRanges> lists; |
| std::vector<std::unique_ptr<SBChunkData>> 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(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(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(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(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(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(kPhishingList, chunks); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(2U, lists.size()); |
| EXPECT_EQ(kMalwareList, lists[0].name); |
| EXPECT_EQ("1-3", lists[0].adds); |
| EXPECT_EQ("7", lists[0].subs); |
| EXPECT_EQ(kPhishingList, lists[1].name); |
| EXPECT_EQ("47", lists[1].adds); |
| EXPECT_EQ("200-201", lists[1].subs); |
| } |
| |
| TEST_F(SafeBrowsingDatabaseTest, ListNames) { |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| |
| std::vector<SBListChunkRanges> 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(kMalwareList, chunks); |
| |
| chunks.clear(); |
| chunks.push_back(AddChunkPrefixValue(2, "www.foo.com/malware.html")); |
| database_->InsertChunks(kPhishingList, chunks); |
| |
| chunks.clear(); |
| chunks.push_back(AddChunkPrefixValue(3, "www.whatever.com/download.html")); |
| database_->InsertChunks(kBinUrlList, chunks); |
| |
| chunks.clear(); |
| chunks.push_back(AddChunkFullHashValue(4, "www.forwhitelist.com/a.html")); |
| database_->InsertChunks(kCsdWhiteList, chunks); |
| |
| chunks.clear(); |
| chunks.push_back(AddChunkFullHashValue(5, "www.download.com/")); |
| database_->InsertChunks(kDownloadWhiteList, chunks); |
| |
| chunks.clear(); |
| chunks.push_back(AddChunkFullHashValue(6, |
| "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
| "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); |
| database_->InsertChunks(kExtensionBlacklist, |
| chunks); |
| |
| chunks.clear(); |
| chunks.push_back(AddChunkHashedIpValue(7, "::ffff:192.168.1.0", 120)); |
| database_->InsertChunks(kIPBlacklist, chunks); |
| |
| chunks.clear(); |
| chunks.push_back(AddChunkPrefixValue(8, "www.unwanted.com/software.html")); |
| database_->InsertChunks(kUnwantedUrlList, chunks); |
| |
| chunks.clear(); |
| chunks.push_back(AddChunkPrefixValue(9, "foo.com/script.js")); |
| database_->InsertChunks(kResourceBlacklist, chunks); |
| |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_EQ(9U, lists.size()); |
| EXPECT_EQ(kMalwareList, lists[0].name); |
| EXPECT_EQ("1", lists[0].adds); |
| EXPECT_TRUE(lists[0].subs.empty()); |
| EXPECT_EQ(kPhishingList, lists[1].name); |
| EXPECT_EQ("2", lists[1].adds); |
| EXPECT_TRUE(lists[1].subs.empty()); |
| EXPECT_EQ(kBinUrlList, lists[2].name); |
| EXPECT_EQ("3", lists[2].adds); |
| EXPECT_TRUE(lists[2].subs.empty()); |
| EXPECT_EQ(kCsdWhiteList, lists[3].name); |
| EXPECT_EQ("4", lists[3].adds); |
| EXPECT_TRUE(lists[3].subs.empty()); |
| EXPECT_EQ(kDownloadWhiteList, lists[4].name); |
| EXPECT_EQ("5", lists[4].adds); |
| EXPECT_TRUE(lists[4].subs.empty()); |
| EXPECT_EQ(kExtensionBlacklist, lists[5].name); |
| EXPECT_EQ("6", lists[5].adds); |
| EXPECT_TRUE(lists[5].subs.empty()); |
| EXPECT_EQ(kIPBlacklist, lists[6].name); |
| EXPECT_EQ("7", lists[6].adds); |
| EXPECT_TRUE(lists[6].subs.empty()); |
| EXPECT_EQ(kUnwantedUrlList, lists[7].name); |
| EXPECT_EQ("8", lists[7].adds); |
| EXPECT_TRUE(lists[7].subs.empty()); |
| EXPECT_EQ(kResourceBlacklist, lists[8].name); |
| EXPECT_EQ("9", lists[8].adds); |
| EXPECT_TRUE(lists[8].subs.empty()); |
| |
| database_.reset(); |
| } |
| |
| // Checks database reading and writing for browse and unwanted PrefixSets. |
| TEST_F(SafeBrowsingDatabaseTest, BrowseAndUnwantedDatabasesAndPrefixSets) { |
| |
| struct TestCase { |
| using TestListContainsBadUrl = bool (SafeBrowsingDatabase::*)( |
| const GURL& url, |
| std::vector<SBPrefix>* prefix_hits, |
| std::vector<SBFullHashResult>* cache_hits); |
| using TestListContainsBadHashes = bool (SafeBrowsingDatabase::*)( |
| const std::vector<SBFullHash>& full_hashes, |
| std::vector<SBPrefix>* prefix_hits, |
| std::vector<SBFullHashResult>* cache_hits); |
| |
| const char* test_list_name; |
| size_t expected_list_index; |
| TestListContainsBadUrl test_list_contains_bad_url; |
| TestListContainsBadHashes test_list_contains_bad_hashes; |
| |
| void TestContainsFunctions(SafeBrowsingDatabaseNew& database, |
| bool expected_outcome, |
| const GURL& url, |
| std::vector<SBPrefix>* prefix_hits, |
| std::vector<SBFullHashResult>* cache_hits) const { |
| EXPECT_EQ(expected_outcome, |
| (database.*test_list_contains_bad_url)(url, prefix_hits, cache_hits)) |
| << test_list_name << url; |
| |
| // Contains*Hashes should always return the same result as Contains*Url. |
| std::vector<SBFullHash> full_hashes; |
| UrlToFullHashes(url, false, &full_hashes); |
| ASSERT_FALSE(full_hashes.empty()) << test_list_name << url; |
| |
| std::vector<SBPrefix> hash_prefix_hits; |
| std::vector<SBFullHashResult> hash_cache_hits; |
| EXPECT_EQ(expected_outcome, (database.*test_list_contains_bad_hashes)( |
| full_hashes, &hash_prefix_hits, &hash_cache_hits)) << test_list_name |
| << url; |
| |
| EXPECT_EQ(prefix_hits->size(), hash_prefix_hits.size()) << test_list_name |
| << url; |
| EXPECT_EQ(cache_hits->size(), hash_cache_hits.size()) << test_list_name |
| << url; |
| } |
| }; |
| |
| TestCase const kTestCases[] { |
| { |
| kMalwareList, |
| 0U, |
| &SafeBrowsingDatabase::ContainsBrowseUrl, |
| &SafeBrowsingDatabase::ContainsBrowseHashes |
| }, |
| { |
| kPhishingList, |
| 1U, |
| &SafeBrowsingDatabase::ContainsBrowseUrl, |
| &SafeBrowsingDatabase::ContainsBrowseHashes |
| }, |
| { |
| kUnwantedUrlList, |
| 7U, |
| &SafeBrowsingDatabase::ContainsUnwantedSoftwareUrl, |
| &SafeBrowsingDatabase::ContainsUnwantedSoftwareHashes |
| }, |
| }; |
| |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(std::string("Tested list at fault => ") + |
| test_case.test_list_name); |
| |
| std::vector<SBListChunkRanges> lists; |
| std::vector<std::unique_ptr<SBChunkData>> 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(test_case.test_list_name, chunks); |
| database_->UpdateFinished(true); |
| |
| // Make sure they were added correctly. |
| GetListsInfo(&lists); |
| |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(test_case.test_list_name, |
| lists[test_case.expected_list_index].name); |
| EXPECT_EQ("1-3,7", lists[test_case.expected_list_index].adds); |
| EXPECT_TRUE(lists[test_case.expected_list_index].subs.empty()); |
| |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| test_case.TestContainsFunctions(*database_, true, |
| 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()); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://www.evil.com/notevil1.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://192.168.0.1/malware.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.evil.com/"), &prefix_hits, &cache_hits); |
| EXPECT_TRUE(prefix_hits.empty()); |
| EXPECT_TRUE(cache_hits.empty()); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.evil.com/robots.txt"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| 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(test_case.test_list_name, chunks); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(test_case.test_list_name, |
| lists[test_case.expected_list_index].name); |
| EXPECT_EQ("1-3,7", lists[test_case.expected_list_index].adds); |
| EXPECT_TRUE(lists[test_case.expected_list_index].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(test_case.test_list_name, chunks); |
| database_->UpdateFinished(true); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| 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()); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.evil.com/notevil1.html"), &prefix_hits, &cache_hits); |
| EXPECT_TRUE(prefix_hits.empty()); |
| EXPECT_TRUE(cache_hits.empty()); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, true, |
| GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(test_case.test_list_name, |
| lists[test_case.expected_list_index].name); |
| EXPECT_EQ("1-3,7", lists[test_case.expected_list_index].adds); |
| EXPECT_EQ("4", lists[test_case.expected_list_index].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(test_case.test_list_name, chunks); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(test_case.test_list_name, |
| lists[test_case.expected_list_index].name); |
| EXPECT_EQ("1-3,7", lists[test_case.expected_list_index].adds); |
| EXPECT_EQ("4", lists[test_case.expected_list_index].subs); |
| |
| // Test removing all the prefixes from an add chunk. |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| AddDelChunk(test_case.test_list_name, 2); |
| database_->UpdateFinished(true); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(test_case.test_list_name, |
| lists[test_case.expected_list_index].name); |
| EXPECT_EQ("1,3,7", lists[test_case.expected_list_index].adds); |
| EXPECT_EQ("4", lists[test_case.expected_list_index].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(test_case.test_list_name, chunks); |
| |
| // Now remove the dummy entry. If there are any problems with the |
| // transactions, asserts will fire. |
| AddDelChunk(test_case.test_list_name, 44); |
| |
| // Test the subdel command. |
| SubDelChunk(test_case.test_list_name, 4); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(test_case.test_list_name, |
| lists[test_case.expected_list_index].name); |
| EXPECT_EQ("1,3,7", lists[test_case.expected_list_index].adds); |
| EXPECT_TRUE(lists[test_case.expected_list_index].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(test_case.test_list_name, chunks); |
| database_->UpdateFinished(true); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| 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(test_case.test_list_name, chunks); |
| database_->UpdateFinished(true); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.notevilanymore.com/index.html"), |
| &prefix_hits, |
| &cache_hits); |
| |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.notevilanymore.com/good.html"), |
| &prefix_hits, |
| &cache_hits); |
| |
| // Reset and reload the database. The database will rely on the prefix set. |
| ResetAndReloadFullDatabase(); |
| |
| // Check that a prefix still hits. |
| test_case.TestContainsFunctions(*database_, true, |
| 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. |
| test_case.TestContainsFunctions(*database_, false, |
| GURL("http://www.evil.com/"), &prefix_hits, &cache_hits); |
| |
| // Check that the full hash is still present. |
| test_case.TestContainsFunctions(*database_, true, |
| 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<SBListChunkRanges> lists; |
| std::vector<std::unique_ptr<SBChunkData>> 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(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| // Add an empty ADD and SUB chunk. |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(kMalwareList, lists[0].name); |
| EXPECT_EQ("1,10", lists[0].adds); |
| EXPECT_TRUE(lists[0].subs.empty()); |
| |
| chunks.clear(); |
| chunks.push_back(BuildChunk(19, ChunkData::ADD, ChunkData::PREFIX_4B, NULL, 0, |
| std::vector<int>())); |
| chunks.push_back(BuildChunk(7, ChunkData::SUB, ChunkData::PREFIX_4B, NULL, 0, |
| std::vector<int>())); |
| |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(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, ChunkData::ADD, ChunkData::PREFIX_4B, NULL, 0, |
| std::vector<int>())); |
| chunks.push_back(AddChunkPrefixValue(22, "www.notempty.com/full2.html")); |
| |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> 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(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(kMalwareList, 21); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(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(kMalwareList, 7); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(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. |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkPrefix2Value(1, |
| "www.evil.com/phishing.html", |
| "www.evil.com/malware.html")); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| // Cache should be cleared after updating. |
| EXPECT_TRUE( |
| database_->GetUnsynchronizedPrefixGetHashCacheForTesting()->empty()); |
| |
| SBFullHashResult full_hash; |
| full_hash.list_id = MALWARE; |
| |
| std::vector<SBFullHashResult> results; |
| std::vector<SBPrefix> 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_->GetUnsynchronizedPrefixGetHashCacheForTesting()->size()); |
| |
| // Test the cache lookup for the first prefix. |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> 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. |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(SubChunkPrefixValue(2, "www.evil.com/phishing.html", 1)); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| 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(kMalwareList, 1); |
| database_->UpdateFinished(true); |
| EXPECT_FALSE(database_->ContainsBrowseUrl( |
| GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits)); |
| EXPECT_TRUE( |
| database_->GetUnsynchronizedPrefixGetHashCacheForTesting()->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(); |
| |
| SafeBrowsingDatabaseNew::PrefixGetHashCache* hash_cache = |
| database_->GetUnsynchronizedPrefixGetHashCacheForTesting(); |
| 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"); |
| SafeBrowsingDatabaseNew::PrefixGetHashCache::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(kMalwareList, 1); |
| database_->UpdateFinished(true); |
| |
| // Cache should be cleared after updating. |
| EXPECT_TRUE(hash_cache->empty()); |
| |
| std::vector<SBPrefix> prefix_misses; |
| std::vector<SBFullHashResult> 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(kMalwareList, chunks); |
| 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(kMalwareList, chunks); |
| 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(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)); |
| { |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkPrefixValue(21, kExampleCollision)); |
| database_->InsertChunks(kMalwareList, chunks); |
| } |
| 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 = MALWARE; |
| database_->CacheHashResults(std::vector<SBPrefix>(1, result.hash.prefix), |
| std::vector<SBFullHashResult>(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(task_runner_); |
| database_.reset(new SafeBrowsingDatabaseNew(task_runner_, store, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL)); |
| database_->Init(database_filename_); |
| |
| // This will cause an empty database to be created. |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->UpdateFinished(true); |
| |
| // Create a sub chunk to insert. |
| std::vector<std::unique_ptr<SBChunkData>> 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(kMalwareList, chunks); |
| 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. |
| DVLOG(1) << "Expect failed check on: SafeBrowsing database reset"; |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Database file should not exist. |
| EXPECT_FALSE(base::PathExists(database_filename_)); |
| |
| // Run the update again successfully. |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| EXPECT_TRUE(base::PathExists(database_filename_)); |
| |
| database_.reset(); |
| } |
| |
| // Checks database reading and writing. |
| TEST_F(SafeBrowsingDatabaseTest, ContainsDownloadUrlPrefixes) { |
| 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. |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkPrefix2Value(1, kEvil1Url1, kEvil1Url2)); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kBinUrlList, chunks); |
| database_->UpdateFinished(true); |
| |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<GURL> urls(1); |
| |
| urls[0] = GURL(std::string("http://") + kEvil1Url1); |
| EXPECT_TRUE(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(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(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(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(ContainsDownloadUrl(urls, &prefix_hits)); |
| |
| // Should match with query args stripped. |
| urls[0] = GURL(std::string("http://") + kEvil1Url2 + "?blah"); |
| EXPECT_TRUE(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(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(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(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(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(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(); |
| } |
| |
| TEST_F(SafeBrowsingDatabaseTest, ContainsResourceUrlPrefixes) { |
| const char* kBadUrl1 = "bad1.com/"; |
| const char* kBadUrl2 = "bad2.com/script.js"; |
| const SBPrefix kBadPrefix1 = SBPrefixForString(kBadUrl1); |
| const SBPrefix kBadPrefix2 = SBPrefixForString(kBadUrl2); |
| |
| // Populate database |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkPrefix2Value(1, kBadUrl1, kBadUrl2)); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kResourceBlacklist, chunks); |
| database_->UpdateFinished(true); |
| |
| struct { |
| std::string url; |
| bool found_in_db; |
| std::vector<SBPrefix> prefix_hits; |
| } test_cases[] = { |
| {std::string("http://") + kBadUrl1, true, {kBadPrefix1}}, |
| {std::string("https://") + kBadUrl2, true, {kBadPrefix2}}, |
| {std::string("ftp://") + kBadUrl1, true, {kBadPrefix1}}, |
| {std::string("http://") + kBadUrl1 + "a/b/?arg=value", true, {kBadPrefix1}}, |
| {std::string("http://") + kBadUrl1 + "script.js", true, {kBadPrefix1}}, |
| {std::string("http://www.domain.") + kBadUrl2, true, {kBadPrefix2}}, |
| {"http://www.good.org/script.js", false, std::vector<SBPrefix>()}, |
| }; |
| |
| std::vector<SBPrefix> prefix_hits; |
| for (const auto& test_case : test_cases) { |
| EXPECT_EQ(test_case.found_in_db, |
| ContainsResourceUrl(GURL(test_case.url), &prefix_hits)); |
| EXPECT_THAT(prefix_hits, testing::ElementsAreArray(test_case.prefix_hits)); |
| } |
| |
| database_.reset(); |
| } |
| |
| // Checks that the whitelists are handled properly. |
| TEST_F(SafeBrowsingDatabaseTest, Whitelists) { |
| struct TestCase { |
| using TestListContainsWhitelistedUrl = |
| bool (SafeBrowsingDatabase::*)(const GURL& url); |
| using TestListContainsWhitelistedString = |
| bool (SafeBrowsingDatabase::*)(const std::string& str); |
| |
| // Returns true if strings should be tested in this test case (i.e. |
| // |test_list_contains_whitelisted_string| is not null). |
| bool TestStrings() const { |
| return test_list_contains_whitelisted_string != nullptr; |
| } |
| |
| const char* test_list_name; |
| TestListContainsWhitelistedUrl test_list_contains_whitelisted_url; |
| // Optional test case field, if set the tested whitelist will also be tested |
| // for strings. |
| TestListContainsWhitelistedString test_list_contains_whitelisted_string; |
| } const kTestCases[]{ |
| {kCsdWhiteList, &SafeBrowsingDatabase::ContainsCsdWhitelistedUrl, |
| nullptr}, |
| {kDownloadWhiteList, |
| &SafeBrowsingDatabase::ContainsDownloadWhitelistedUrl, |
| &SafeBrowsingDatabase::ContainsDownloadWhitelistedString}, |
| }; |
| |
| // If the whitelist is disabled everything should match the whitelist. |
| database_.reset(new SafeBrowsingDatabaseNew( |
| task_runner_, new SafeBrowsingStoreFile(task_runner_), NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL)); |
| database_->Init(database_filename_); |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(std::string("Tested list at fault => ") + |
| test_case.test_list_name); |
| |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://www.phishing.com/")))); |
| if (test_case.TestStrings()) { |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_string)( |
| "asdf")); |
| } |
| } |
| ResetAndReloadFullDatabase(); |
| |
| // Now test every whitelist one-by-one; intentionally not resetting the |
| // database in-between to further stress potential inter-dependencies. |
| for (const auto& test_case : kTestCases) { |
| SCOPED_TRACE(std::string("Tested list at fault => ") + |
| test_case.test_list_name); |
| |
| 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"; |
| |
| // Nothing should be whitelisted before the database receives the chunks. |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood1Host))); |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood1Url1))); |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood1Url2))); |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood2Url1))); |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood3Url1))); |
| if (test_case.TestStrings()) { |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_string)( |
| kGoodString)); |
| } |
| |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| |
| // Add a few test chunks to the whitelist under test. |
| chunks.push_back(AddChunkFullHash2Value(1, kGood1Url1, kGood1Url2)); |
| chunks.push_back(AddChunkFullHashValue(2, kGood2Url1)); |
| if (test_case.TestStrings()) |
| chunks.push_back(AddChunkFullHashValue(3, kGoodString)); |
| chunks.push_back(AddChunkFullHashValue(4, kGood3Url1)); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(test_case.test_list_name, chunks); |
| database_->UpdateFinished(true); |
| |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood1Host))); |
| |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood1Url1))); |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood1Url1 + "?a=b"))); |
| |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood1Url2))); |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood1Url2 + "/c.html"))); |
| |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("https://") + kGood1Url2 + "/c.html"))); |
| |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood2Url1 + "/c"))); |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood2Url1 + "/c?bla"))); |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood2Url1 + "/c/bla"))); |
| |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://www.google.com/")))); |
| |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://") + kGood3Url1 + "a/b/c/d/e/f/g/"))); |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://a.b.") + kGood3Url1))); |
| |
| if (test_case.TestStrings()) { |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_string)( |
| "asdf")); |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_string)( |
| kGoodString)); |
| } |
| |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("https://") + kGood1Url2 + "/c.html"))); |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("https://") + kGood2Url1 + "/c/bla"))); |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("https://") + kGood3Url1))); |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://www.google.com/")))); |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_url)( |
| GURL(std::string("http://www.phishing_url.com/")))); |
| if (test_case.TestStrings()) { |
| EXPECT_TRUE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_string)( |
| kGoodString)); |
| EXPECT_FALSE( |
| (database_.get()->*test_case.test_list_contains_whitelisted_string)( |
| "asdf")); |
| } |
| } |
| } |
| |
| // Test to make sure we could insert chunk list that |
| // contains entries for the same host. |
| TEST_F(SafeBrowsingDatabaseTest, SameHostEntriesOkay) { |
| std::vector<std::unique_ptr<SBChunkData>> 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<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(1U, lists.size()); |
| EXPECT_EQ(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(kPhishingList, chunks); |
| database_->UpdateFinished(true); |
| |
| GetListsInfo(&lists); |
| ASSERT_LE(2U, lists.size()); |
| EXPECT_EQ(kMalwareList, lists[0].name); |
| EXPECT_EQ("1", lists[0].adds); |
| EXPECT_TRUE(lists[0].subs.empty()); |
| EXPECT_EQ(kPhishingList, lists[1].name); |
| EXPECT_EQ("47", lists[1].adds); |
| EXPECT_TRUE(lists[1].subs.empty()); |
| |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> 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(kMalwareList, chunks); |
| 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(kPhishingList, chunks); |
| 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) { |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| |
| base::FilePath filename = database_->BrowseDBFilename(database_filename_); |
| |
| // Prime the database. |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html")); |
| database_->InsertChunks(kMalwareList, chunks); |
| 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(kMalwareList, chunks); |
| 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(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<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html")); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| } |
| |
| // Find the malware url in the database, don't find a good url. |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> 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)); |
| ResetAndReloadFullDatabase(); |
| 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)); |
| ResetAndReloadFullDatabase(); |
| 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. |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkPrefix(1, kPrefix1)); |
| chunks.push_back(AddChunkPrefix(2, kPrefix2)); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| { |
| // Cache a full miss result for kPrefix1. |
| std::vector<SBPrefix> prefixes(1, kPrefix1); |
| std::vector<SBFullHashResult> 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<SBFullHash> full_hashes(1, kFullHash1_1); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_FALSE(database_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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. |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkPrefix(1, kPrefix1)); |
| chunks.push_back(AddChunkPrefix(2, kPrefix2)); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| { |
| // kFullHash1_1 has a prefix hit of kPrefix1. |
| std::vector<SBFullHash> full_hashes; |
| full_hashes.push_back(kFullHash1_1); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_TRUE(database_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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<SBPrefix> prefixes(1, kPrefix1); |
| std::vector<SBFullHashResult> cache_results; |
| |
| SBFullHashResult full_hash_result; |
| full_hash_result.list_id = 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<SBFullHash> full_hashes(1, kFullHash1_1); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_TRUE(database_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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<SBFullHash> full_hashes(1, kFullHash1_3); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_TRUE(database_->ContainsBrowseHashes( |
| 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<SBFullHash> full_hashes(1, kFullHash1_2); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_FALSE(database_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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. |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkFullHash(1, kFullHash1_1)); |
| chunks.push_back(AddChunkFullHash(2, kFullHash1_2)); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| { |
| // Check a full hash which isn't present. |
| std::vector<SBFullHash> full_hashes(1, kFullHash1_3); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_FALSE(database_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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 = MALWARE; |
| full_hash_result.hash = kFullHash1_2; |
| |
| std::vector<SBPrefix> prefixes(1, kPrefix1); |
| std::vector<SBFullHashResult> 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<SBFullHash> full_hashes(1, kFullHash1_3); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_FALSE(database_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| // Cache should be cleared after updating. |
| EXPECT_TRUE( |
| database_->GetUnsynchronizedPrefixGetHashCacheForTesting()->empty()); |
| |
| { |
| // Now the database doesn't contain kFullHash1_1. |
| std::vector<SBFullHash> full_hashes(1, kFullHash1_1); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_FALSE(database_->ContainsBrowseHashes( |
| full_hashes, &prefix_hits, &cache_hits)); |
| |
| // Nor kFullHash1_3. |
| full_hashes.push_back(kFullHash1_3); |
| prefix_hits.clear(); |
| cache_hits.clear(); |
| EXPECT_FALSE(database_->ContainsBrowseHashes( |
| 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_->ContainsBrowseHashes( |
| 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(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| // Cache should be cleared after updating. |
| EXPECT_TRUE( |
| database_->GetUnsynchronizedPrefixGetHashCacheForTesting()->empty()); |
| |
| { |
| // None are present. |
| std::vector<SBFullHash> full_hashes; |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| full_hashes.push_back(kFullHash1_1); |
| full_hashes.push_back(kFullHash1_2); |
| full_hashes.push_back(kFullHash1_3); |
| EXPECT_FALSE(database_->ContainsBrowseHashes( |
| 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"); |
| |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkFullHash(1, kFullHash1_1)); |
| |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| database_->InsertChunks(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| { |
| // kFullHash1_2 does not match kFullHash1_1. |
| std::vector<SBFullHash> full_hashes(1, kFullHash1_2); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_FALSE(database_->ContainsBrowseHashes( |
| 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(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| { |
| // kFullHash1_2 does match kPrefix1. |
| std::vector<SBFullHash> full_hashes(1, kFullHash1_2); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_TRUE(database_->ContainsBrowseHashes( |
| 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(kMalwareList, chunks); |
| database_->UpdateFinished(true); |
| |
| { |
| // kFullHash1_2 still returns true due to the prefix hit. |
| std::vector<SBFullHash> full_hashes(1, kFullHash1_2); |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> cache_hits; |
| EXPECT_TRUE(database_->ContainsBrowseHashes( |
| 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) { |
| std::vector<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| |
| std::vector<std::unique_ptr<SBChunkData>> 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(kIPBlacklist, chunks); |
| 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<SBListChunkRanges> lists; |
| ASSERT_TRUE(database_->UpdateStarted(&lists)); |
| |
| // Add a host-level hit. |
| { |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/")); |
| database_->InsertChunks(kMalwareList, chunks); |
| } |
| |
| // Add a specific fullhash. |
| static const char kWhateverMalware[] = "www.whatever.com/malware.html"; |
| { |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkFullHashValue(2, kWhateverMalware)); |
| database_->InsertChunks(kMalwareList, chunks); |
| } |
| |
| // 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)); |
| { |
| std::vector<std::unique_ptr<SBChunkData>> chunks; |
| chunks.push_back(AddChunkFullHashValue(3, kExampleCollision)); |
| database_->InsertChunks(kMalwareList, chunks); |
| } |
| |
| database_->UpdateFinished(true); |
| |
| std::vector<SBPrefix> prefix_hits; |
| std::vector<SBFullHashResult> 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)); |
| } |
| |
| } // namespace safe_browsing |