blob: 1d6ca88d1cad11e89eb37050a896ead6bbe29e75 [file] [log] [blame]
// 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