blob: ebf6fd6dca67e0de961a548e0656b7c547f1daf3 [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.
#include "chrome/browser/safe_browsing/local_database_manager.h"
#include <limits>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/debug/leak_tracker.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prerender/prerender_field_trial.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/safe_browsing/client_side_detection_service.h"
#include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
#include "chrome/browser/safe_browsing/protocol_manager.h"
#include "chrome/browser/safe_browsing/safe_browsing_database.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/safe_browsing/ui_manager.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/common/safe_browsing_prefs.h"
#include "components/safe_browsing/common/safebrowsing_switches.h"
#include "components/safe_browsing/db/util.h"
#include "content/public/browser/browser_thread.h"
#include "url/url_constants.h"
using content::BrowserThread;
namespace safe_browsing {
namespace {
// Timeout for match checks, e.g. download URLs, hashes.
const int kCheckTimeoutMs = 10000;
// Records disposition information about the check. |hit| should be
// |true| if there were any prefix hits in |full_hashes|.
void RecordGetHashCheckStatus(
bool hit,
ListType check_type,
const std::vector<SBFullHashResult>& full_hashes) {
SafeBrowsingProtocolManager::ResultType result;
if (full_hashes.empty()) {
result = SafeBrowsingProtocolManager::GET_HASH_FULL_HASH_EMPTY;
} else if (hit) {
result = SafeBrowsingProtocolManager::GET_HASH_FULL_HASH_HIT;
} else {
result = SafeBrowsingProtocolManager::GET_HASH_FULL_HASH_MISS;
}
bool is_download = check_type == BINURL;
SafeBrowsingProtocolManager::RecordGetHashResult(is_download, result);
}
bool IsExpectedThreat(const SBThreatType threat_type,
const SBThreatTypeSet& expected_threats) {
return base::ContainsKey(expected_threats, threat_type);
}
// Returns threat level of the list. Lists with lower threat levels are more
// severe than lists with higher threat levels. Zero is the severest threat
// level possible.
int GetThreatSeverity(ListType threat) {
switch (threat) {
case MALWARE: // Falls through.
case PHISH: // Falls through.
case BINURL: // Falls through.
case CSDWHITELIST: // Falls through.
case DOWNLOADWHITELIST: // Falls through.
case EXTENSIONBLACKLIST: // Falls through.
case IPBLACKLIST:
return 0;
case UNWANTEDURL:
// UNWANTEDURL is considered less severe than other threats.
return 1;
case RESOURCEBLACKLIST:
// RESOURCEBLACKLIST is even less severe than UNWANTEDURL.
return 2;
case INVALID:
return std::numeric_limits<int>::max();
}
NOTREACHED();
return -1;
}
// Return the severest list id from the results in |full_hashes| which matches
// |hash|, or INVALID if none match.
ListType GetHashSeverestThreatListType(
const SBFullHash& hash,
const std::vector<SBFullHashResult>& full_hashes,
size_t* index) {
ListType pending_threat = INVALID;
int pending_threat_severity = GetThreatSeverity(INVALID);
for (size_t i = 0; i < full_hashes.size(); ++i) {
if (SBFullHashEqual(hash, full_hashes[i].hash)) {
const ListType threat = static_cast<ListType>(full_hashes[i].list_id);
int threat_severity = GetThreatSeverity(threat);
if (threat_severity < pending_threat_severity) {
pending_threat = threat;
pending_threat_severity = threat_severity;
if (index)
*index = i;
}
if (pending_threat_severity == 0)
return pending_threat;
}
}
return pending_threat;
}
// Given a URL, compare all the possible host + path full hashes to the set of
// provided full hashes. Returns the list id of the severest matching result
// from |full_hashes|, or INVALID if none match.
ListType GetUrlSeverestThreatListType(
const GURL& url,
const std::vector<SBFullHashResult>& full_hashes,
size_t* index) {
if (full_hashes.empty())
return INVALID;
std::vector<std::string> patterns;
V4ProtocolManagerUtil::GeneratePatternsToCheck(url, &patterns);
ListType pending_threat = INVALID;
int pending_threat_severity = GetThreatSeverity(INVALID);
for (size_t i = 0; i < patterns.size(); ++i) {
ListType threat = GetHashSeverestThreatListType(
SBFullHashForString(patterns[i]), full_hashes, index);
int threat_severity = GetThreatSeverity(threat);
if (threat_severity < pending_threat_severity) {
pending_threat = threat;
pending_threat_severity = threat_severity;
}
if (pending_threat_severity == 0)
return pending_threat;
}
return pending_threat;
}
SBThreatType GetThreatTypeFromListType(ListType list_type) {
switch (list_type) {
case PHISH:
return SB_THREAT_TYPE_URL_PHISHING;
case MALWARE:
return SB_THREAT_TYPE_URL_MALWARE;
case UNWANTEDURL:
return SB_THREAT_TYPE_URL_UNWANTED;
case BINURL:
return SB_THREAT_TYPE_URL_BINARY_MALWARE;
case EXTENSIONBLACKLIST:
return SB_THREAT_TYPE_EXTENSION;
case RESOURCEBLACKLIST:
return SB_THREAT_TYPE_BLACKLISTED_RESOURCE;
default:
DVLOG(1) << "Unknown safe browsing list id " << list_type;
return SB_THREAT_TYPE_SAFE;
}
}
} // namespace
// static
SBThreatType LocalSafeBrowsingDatabaseManager::GetHashSeverestThreatType(
const SBFullHash& hash,
const std::vector<SBFullHashResult>& full_hashes) {
return GetThreatTypeFromListType(
GetHashSeverestThreatListType(hash, full_hashes, NULL));
}
// static
SBThreatType LocalSafeBrowsingDatabaseManager::GetUrlSeverestThreatType(
const GURL& url,
const std::vector<SBFullHashResult>& full_hashes,
size_t* index) {
return GetThreatTypeFromListType(
GetUrlSeverestThreatListType(url, full_hashes, index));
}
LocalSafeBrowsingDatabaseManager::SafeBrowsingCheck::SafeBrowsingCheck(
const std::vector<GURL>& urls,
const std::vector<SBFullHash>& full_hashes,
Client* client,
ListType check_type,
const SBThreatTypeSet& expected_threats)
: urls(urls),
url_results(urls.size(), SB_THREAT_TYPE_SAFE),
url_metadata(urls.size()),
url_hit_hash(urls.size()),
full_hashes(full_hashes),
full_hash_results(full_hashes.size(), SB_THREAT_TYPE_SAFE),
client(client),
need_get_hash(false),
check_type(check_type),
expected_threats(expected_threats) {
DCHECK_EQ(urls.empty(), !full_hashes.empty())
<< "Exactly one of urls and full_hashes must be set";
}
LocalSafeBrowsingDatabaseManager::SafeBrowsingCheck::~SafeBrowsingCheck() {}
void LocalSafeBrowsingDatabaseManager::SafeBrowsingCheck::
OnSafeBrowsingResult() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(client);
DCHECK_EQ(urls.size(), url_results.size());
DCHECK_EQ(full_hashes.size(), full_hash_results.size());
if (!urls.empty()) {
DCHECK(full_hashes.empty());
switch (check_type) {
case MALWARE:
case PHISH:
case UNWANTEDURL:
DCHECK_EQ(1u, urls.size());
client->OnCheckBrowseUrlResult(urls[0], url_results[0],
url_metadata[0]);
break;
case BINURL:
DCHECK_EQ(urls.size(), url_results.size());
client->OnCheckDownloadUrlResult(
urls, *std::max_element(url_results.begin(), url_results.end()));
break;
case RESOURCEBLACKLIST:
DCHECK_EQ(1u, urls.size());
client->OnCheckResourceUrlResult(urls[0], url_results[0],
url_hit_hash[0]);
break;
default:
NOTREACHED();
}
} else if (!full_hashes.empty()) {
switch (check_type) {
case EXTENSIONBLACKLIST: {
std::set<std::string> unsafe_extension_ids;
for (size_t i = 0; i < full_hashes.size(); ++i) {
std::string extension_id = SBFullHashToString(full_hashes[i]);
if (full_hash_results[i] == SB_THREAT_TYPE_EXTENSION)
unsafe_extension_ids.insert(extension_id);
}
client->OnCheckExtensionsResult(unsafe_extension_ids);
break;
}
default:
NOTREACHED();
}
} else {
NOTREACHED();
}
}
LocalSafeBrowsingDatabaseManager::LocalSafeBrowsingDatabaseManager(
const scoped_refptr<SafeBrowsingService>& service)
: sb_service_(service),
database_(NULL),
enable_download_protection_(false),
enable_csd_whitelist_(false),
enable_download_whitelist_(false),
enable_extension_blacklist_(false),
enable_ip_blacklist_(false),
enable_unwanted_software_blacklist_(true),
update_in_progress_(false),
database_update_in_progress_(false),
closing_database_(false),
opening_database_(false),
check_timeout_(base::TimeDelta::FromMilliseconds(kCheckTimeoutMs)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(sb_service_.get() != NULL);
base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
enable_download_protection_ = !cmdline->HasSwitch(
safe_browsing::switches::kSbDisableDownloadProtection);
// We only download the csd-whitelist if client-side phishing detection is
// enabled.
enable_csd_whitelist_ =
!cmdline->HasSwitch(::switches::kDisableClientSidePhishingDetection);
// We download the download-whitelist if download protection is enabled.
enable_download_whitelist_ = enable_download_protection_;
// TODO(kalman): there really shouldn't be a flag for this.
enable_extension_blacklist_ = !cmdline->HasSwitch(
safe_browsing::switches::kSbDisableExtensionBlacklist);
// The client-side IP blacklist feature is tightly integrated with client-side
// phishing protection for now.
enable_ip_blacklist_ = enable_csd_whitelist_;
}
LocalSafeBrowsingDatabaseManager::~LocalSafeBrowsingDatabaseManager() {
// The DCHECK is disabled due to crbug.com/438754.
// DCHECK_CURRENTLY_ON(BrowserThread::UI);
// We should have already been shut down. If we're still enabled, then the
// database isn't going to be closed properly, which could lead to corruption.
DCHECK(!enabled_);
}
bool LocalSafeBrowsingDatabaseManager::IsSupported() const {
return true;
}
safe_browsing::ThreatSource LocalSafeBrowsingDatabaseManager::GetThreatSource()
const {
return safe_browsing::ThreatSource::LOCAL_PVER3;
}
bool LocalSafeBrowsingDatabaseManager::ChecksAreAlwaysAsync() const {
return false;
}
bool LocalSafeBrowsingDatabaseManager::CanCheckResourceType(
content::ResourceType resource_type) const {
// We check all types since most checks are fast.
return true;
}
bool LocalSafeBrowsingDatabaseManager::CanCheckUrl(const GURL& url) const {
return url.SchemeIsHTTPOrHTTPS() || url.SchemeIs(url::kFtpScheme) ||
url.SchemeIsWSOrWSS();
}
bool LocalSafeBrowsingDatabaseManager::CheckDownloadUrl(
const std::vector<GURL>& url_chain,
Client* client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_ || !enable_download_protection_)
return true;
// We need to check the database for url prefix, and later may fetch the url
// from the safebrowsing backends. These need to be asynchronous.
std::unique_ptr<SafeBrowsingCheck> check =
std::make_unique<SafeBrowsingCheck>(
url_chain, std::vector<SBFullHash>(), client, BINURL,
CreateSBThreatTypeSet({SB_THREAT_TYPE_URL_BINARY_MALWARE}));
std::vector<SBPrefix> prefixes;
SafeBrowsingDatabase::GetDownloadUrlPrefixes(url_chain, &prefixes);
StartSafeBrowsingCheck(
std::move(check),
base::Bind(&LocalSafeBrowsingDatabaseManager::CheckDownloadUrlOnSBThread,
this, prefixes));
return false;
}
bool LocalSafeBrowsingDatabaseManager::CheckExtensionIDs(
const std::set<std::string>& extension_ids,
Client* client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_ || !enable_extension_blacklist_)
return true;
std::vector<SBFullHash> extension_id_hashes;
std::transform(extension_ids.begin(), extension_ids.end(),
std::back_inserter(extension_id_hashes), StringToSBFullHash);
std::vector<SBPrefix> prefixes;
for (const SBFullHash& hash : extension_id_hashes)
prefixes.push_back(hash.prefix);
std::unique_ptr<SafeBrowsingCheck> check =
std::make_unique<SafeBrowsingCheck>(
std::vector<GURL>(), extension_id_hashes, client, EXTENSIONBLACKLIST,
CreateSBThreatTypeSet({SB_THREAT_TYPE_EXTENSION}));
StartSafeBrowsingCheck(
std::move(check),
base::Bind(&LocalSafeBrowsingDatabaseManager::CheckExtensionIDsOnSBThread,
this, prefixes));
return false;
}
bool LocalSafeBrowsingDatabaseManager::CheckResourceUrl(const GURL& url,
Client* client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_ || !CanCheckUrl(url))
return true;
SBThreatTypeSet expected_threats =
CreateSBThreatTypeSet({SB_THREAT_TYPE_BLACKLISTED_RESOURCE});
if (!MakeDatabaseAvailable()) {
QueuedCheck queued_check(RESOURCEBLACKLIST, client, url, expected_threats,
base::TimeTicks::Now());
queued_checks_.push_back(queued_check);
return false;
}
std::unique_ptr<SafeBrowsingCheck> check = base::WrapUnique(
new SafeBrowsingCheck({url}, std::vector<SBFullHash>(), client,
RESOURCEBLACKLIST, expected_threats));
std::vector<SBPrefix> prefixes;
SafeBrowsingDatabase::GetDownloadUrlPrefixes(check->urls, &prefixes);
StartSafeBrowsingCheck(
std::move(check),
base::Bind(&LocalSafeBrowsingDatabaseManager::CheckResourceUrlOnSBThread,
this, prefixes));
return false;
}
bool LocalSafeBrowsingDatabaseManager::MatchMalwareIP(
const std::string& ip_address) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_ || !enable_ip_blacklist_ || !MakeDatabaseAvailable()) {
return false; // Fail open.
}
return database_->ContainsMalwareIP(ip_address);
}
AsyncMatch LocalSafeBrowsingDatabaseManager::CheckCsdWhitelistUrl(
const GURL& url,
Client* Client) {
// Pver3 DB does not support actual partial-hash whitelists, so we emulate
// it. All this code will go away soon (~M62).
return (MatchCsdWhitelistUrl(url) ? AsyncMatch::MATCH : AsyncMatch::NO_MATCH);
}
bool LocalSafeBrowsingDatabaseManager::MatchCsdWhitelistUrl(const GURL& url) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_ || !enable_csd_whitelist_ || !MakeDatabaseAvailable()) {
// There is something funky going on here -- for example, perhaps the user
// has not restarted since enabling metrics reporting, so we haven't
// enabled the csd whitelist yet. Just to be safe we return true in this
// case.
return true;
}
return database_->ContainsCsdWhitelistedUrl(url);
}
bool LocalSafeBrowsingDatabaseManager::MatchDownloadWhitelistUrl(
const GURL& url) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_ || !enable_download_whitelist_ || !MakeDatabaseAvailable()) {
return true;
}
return database_->ContainsDownloadWhitelistedUrl(url);
}
bool LocalSafeBrowsingDatabaseManager::MatchDownloadWhitelistString(
const std::string& str) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_ || !enable_download_whitelist_ || !MakeDatabaseAvailable()) {
return true;
}
return database_->ContainsDownloadWhitelistedString(str);
}
bool LocalSafeBrowsingDatabaseManager::CheckBrowseUrl(
const GURL& url,
const SBThreatTypeSet& expected_threats,
Client* client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!expected_threats.empty());
DCHECK(SBThreatTypeSetIsValidForCheckBrowseUrl(expected_threats));
if (!enabled_)
return true;
if (!CanCheckUrl(url))
return true;
const base::TimeTicks start = base::TimeTicks::Now();
if (!MakeDatabaseAvailable()) {
QueuedCheck queued_check(MALWARE, // or PHISH
client, url, expected_threats, start);
queued_checks_.push_back(queued_check);
return false;
}
std::vector<SBFullHash> full_hashes;
UrlToFullHashes(url, false, &full_hashes);
// Cache hits should, in general, be the same for both (ignoring potential
// cache evictions in the second call for entries that were just about to be
// evicted in the first call).
// TODO(gab): Refactor SafeBrowsingDatabase to avoid depending on this here.
std::vector<SBFullHashResult> cache_hits;
std::vector<SBPrefix> browse_prefix_hits;
database_->ContainsBrowseHashes(full_hashes, &browse_prefix_hits,
&cache_hits);
std::vector<SBPrefix> unwanted_prefix_hits;
std::vector<SBFullHashResult> unused_cache_hits;
database_->ContainsUnwantedSoftwareHashes(full_hashes, &unwanted_prefix_hits,
&unused_cache_hits);
// Merge the two pre-sorted prefix hits lists.
// TODO(gab): Refactor SafeBrowsingDatabase for it to return this merged list
// by default rather than building it here.
std::vector<SBPrefix> prefix_hits(browse_prefix_hits.size() +
unwanted_prefix_hits.size());
std::merge(browse_prefix_hits.begin(), browse_prefix_hits.end(),
unwanted_prefix_hits.begin(), unwanted_prefix_hits.end(),
prefix_hits.begin());
prefix_hits.erase(std::unique(prefix_hits.begin(), prefix_hits.end()),
prefix_hits.end());
if (prefix_hits.empty() && cache_hits.empty())
return true; // URL is okay.
// Needs to be asynchronous, since we could be in the constructor of a
// ResourceDispatcherHost event handler which can't pause there.
// This check will ping the Safe Browsing servers and get all lists which it
// matches. These lists will then be filtered against the |expected_threats|
// and the result callback for MALWARE (which is the same as for PHISH and
// UNWANTEDURL) will eventually be invoked with the final decision.
SafeBrowsingCheck* check = new SafeBrowsingCheck(
std::vector<GURL>(1, url), std::vector<SBFullHash>(), client, MALWARE,
expected_threats);
check->need_get_hash = cache_hits.empty();
check->prefix_hits.swap(prefix_hits);
check->cache_hits.swap(cache_hits);
checks_[check] = base::WrapUnique(check);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::OnCheckDone, this,
check));
return false;
}
bool LocalSafeBrowsingDatabaseManager::CheckUrlForSubresourceFilter(
const GURL& url,
Client* client) {
// The check for the Subresource Filter in only implemented for pver4.
return true;
}
void LocalSafeBrowsingDatabaseManager::CancelCheck(Client* client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (const auto& check : checks_) {
// We can't delete matching checks here because the db thread has a copy of
// the pointer. Instead, we simply NULL out the client, and when the db
// thread calls us back, we'll clean up the check.
if (check.first->client == client)
check.first->client = NULL;
}
// Scan the queued clients store. Clients may be here if they requested a URL
// check before the database has finished loading.
for (auto it = queued_checks_.begin(); it != queued_checks_.end();) {
// In this case it's safe to delete matches entirely since nothing has a
// pointer to them.
if (it->client == client)
it = queued_checks_.erase(it);
else
++it;
}
}
void LocalSafeBrowsingDatabaseManager::HandleGetHashResults(
SafeBrowsingCheck* check,
const std::vector<SBFullHashResult>& full_hashes,
const base::TimeDelta& cache_lifetime) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_)
return;
// If the service has been shut down, |check| should have been deleted.
DCHECK(checks_.find(check) != checks_.end());
// |start| is set before calling |GetFullHash()|, which should be
// the only path which gets to here.
DCHECK(!check->start.is_null());
UMA_HISTOGRAM_LONG_TIMES("SB2.Network",
base::TimeTicks::Now() - check->start);
std::vector<SBPrefix> prefixes = check->prefix_hits;
OnHandleGetHashResults(check, full_hashes); // 'check' is deleted here.
// Cache the GetHash results.
if (!cache_lifetime.is_zero() && MakeDatabaseAvailable())
database_->CacheHashResults(prefixes, full_hashes, cache_lifetime);
}
void LocalSafeBrowsingDatabaseManager::GetChunks(GetChunksCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(enabled_);
DCHECK(!callback.is_null());
safe_browsing_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&LocalSafeBrowsingDatabaseManager::GetAllChunksFromDatabase, this,
callback));
}
void LocalSafeBrowsingDatabaseManager::AddChunks(
const std::string& list,
std::unique_ptr<std::vector<std::unique_ptr<SBChunkData>>> chunks,
AddChunksCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(enabled_);
DCHECK(!callback.is_null());
safe_browsing_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::AddDatabaseChunks, this,
list, std::move(chunks), callback));
}
void LocalSafeBrowsingDatabaseManager::DeleteChunks(
std::unique_ptr<std::vector<SBChunkDelete>> chunk_deletes) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(enabled_);
safe_browsing_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::DeleteDatabaseChunks,
this, std::move(chunk_deletes)));
}
void LocalSafeBrowsingDatabaseManager::UpdateStarted() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(enabled_);
DCHECK(!update_in_progress_);
update_in_progress_ = true;
}
void LocalSafeBrowsingDatabaseManager::UpdateFinished(bool update_succeeded) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(enabled_);
if (update_in_progress_) {
update_in_progress_ = false;
safe_browsing_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&LocalSafeBrowsingDatabaseManager::DatabaseUpdateFinished, this,
update_succeeded));
}
}
void LocalSafeBrowsingDatabaseManager::ResetDatabase() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(enabled_);
safe_browsing_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::OnResetDatabase, this));
}
void LocalSafeBrowsingDatabaseManager::StartOnIOThread(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const V4ProtocolConfig& config) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SafeBrowsingDatabaseManager::StartOnIOThread(url_loader_factory, config);
if (enabled_)
return;
// Only get a new task runner if there isn't one already. If the service has
// previously been started and stopped, a task runner could already exist.
if (!safe_browsing_task_runner_) {
safe_browsing_task_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
}
enabled_ = true;
MakeDatabaseAvailable();
}
void LocalSafeBrowsingDatabaseManager::StopOnIOThread(bool shutdown) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DoStopOnIOThread();
if (shutdown) {
sb_service_ = NULL;
}
SafeBrowsingDatabaseManager::StopOnIOThread(shutdown);
}
void LocalSafeBrowsingDatabaseManager::NotifyDatabaseUpdateFinished(
bool update_succeeded) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
update_complete_callback_list_.Notify();
}
LocalSafeBrowsingDatabaseManager::QueuedCheck::QueuedCheck(
const ListType check_type,
Client* client,
const GURL& url,
const SBThreatTypeSet& expected_threats,
const base::TimeTicks& start)
: check_type(check_type),
client(client),
url(url),
expected_threats(expected_threats),
start(start) {}
LocalSafeBrowsingDatabaseManager::QueuedCheck::QueuedCheck(
const QueuedCheck& other) = default;
LocalSafeBrowsingDatabaseManager::QueuedCheck::~QueuedCheck() {}
void LocalSafeBrowsingDatabaseManager::DoStopOnIOThread() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_)
return;
enabled_ = false;
// Delete queued checks, calling back any clients with 'SB_THREAT_TYPE_SAFE'.
while (!queued_checks_.empty()) {
QueuedCheck queued = queued_checks_.front();
if (queued.client) {
SafeBrowsingCheck sb_check(std::vector<GURL>(1, queued.url),
std::vector<SBFullHash>(), queued.client,
queued.check_type, queued.expected_threats);
sb_check.OnSafeBrowsingResult();
}
queued_checks_.pop_front();
}
// Close the database. Cases to avoid:
// * If |closing_database_| is true, continuing will queue up a second
// request, |closing_database_| will be reset after handling the first
// request, and if any functions on the db thread recreate the database, we
// could start using it on the IO thread and then have the second request
// handler delete it out from under us.
// * If |database_| is NULL, then either no creation request is in flight, in
// which case we don't need to do anything, or one is in flight, in which
// case the database will be recreated before our deletion request is
// handled, and could be used on the IO thread in that time period, leading
// to the same problem as above.
//
// If the database is not currently available, but a GetDatabase() task is
// posted to |safe_browsing_task_runner_|, then it will be available in the
// future. In this case, post the OnCloseDatabase() task so that resources
// will not be leaked.
bool post_task = false;
{
base::AutoLock lock(database_lock_);
if (!closing_database_ && (database_ || opening_database_)) {
closing_database_ = true;
post_task = true;
}
}
if (post_task) {
safe_browsing_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::OnCloseDatabase,
this));
}
// Delete pending checks, calling back any clients with 'SB_THREAT_TYPE_SAFE'.
// We have to do this after the db thread returns because methods on it can
// have copies of these pointers, so deleting them might lead to accessing
// garbage.
for (const auto& check : checks_) {
if (check.first->client)
check.first->OnSafeBrowsingResult();
}
checks_.clear();
gethash_requests_.clear();
}
bool LocalSafeBrowsingDatabaseManager::DatabaseAvailable() const {
base::AutoLock lock(database_lock_);
return !closing_database_ && (database_ != NULL);
}
bool LocalSafeBrowsingDatabaseManager::MakeDatabaseAvailable() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(enabled_);
{
base::AutoLock lock(database_lock_);
if (!closing_database_ && database_)
return true;
if (opening_database_)
return false;
opening_database_ = true;
}
safe_browsing_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&LocalSafeBrowsingDatabaseManager::GetDatabase),
this, true));
return false;
}
SafeBrowsingDatabase* LocalSafeBrowsingDatabaseManager::GetDatabase(
bool reset_opening_database) {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
if (database_)
return database_;
const base::TimeTicks before = base::TimeTicks::Now();
std::unique_ptr<SafeBrowsingDatabase> database = SafeBrowsingDatabase::Create(
safe_browsing_task_runner_, enable_download_protection_,
enable_csd_whitelist_, enable_download_whitelist_,
enable_extension_blacklist_, enable_ip_blacklist_,
enable_unwanted_software_blacklist_);
database->Init(SafeBrowsingService::GetBaseFilename());
{
// Acquiring the lock here guarantees correct ordering between the writes to
// the new database object above, and the setting of |database_| below.
base::AutoLock lock(database_lock_);
database_ = database.release();
if (reset_opening_database)
opening_database_ = false;
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::DatabaseLoadComplete,
this));
UMA_HISTOGRAM_TIMES("SB2.DatabaseOpen", base::TimeTicks::Now() - before);
return database_;
}
void LocalSafeBrowsingDatabaseManager::OnCheckDone(SafeBrowsingCheck* check) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_)
return;
// If the service has been shut down, |check| should have been deleted.
DCHECK(checks_.find(check) != checks_.end());
if (check->client && check->need_get_hash) {
// We have a partial match so we need to query Google for the full hash.
// Clean up will happen in HandleGetHashResults.
// See if we have a GetHash request already in progress for this particular
// prefix. If so, we just append ourselves to the list of interested parties
// when the results arrive. We only do this for checks involving one prefix,
// since that is the common case (multiple prefixes will issue the request
// as normal).
if (check->prefix_hits.size() == 1) {
SBPrefix prefix = check->prefix_hits[0];
GetHashRequests::iterator it = gethash_requests_.find(prefix);
if (it != gethash_requests_.end()) {
// There's already a request in progress.
it->second.push_back(check);
return;
}
// No request in progress, so we're the first for this prefix.
GetHashRequestors requestors;
requestors.push_back(check);
gethash_requests_[prefix] = requestors;
}
// Reset the start time so that we can measure the network time without the
// database time.
check->start = base::TimeTicks::Now();
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::OnRequestFullHash,
this, check));
} else {
// We may have cached results for previous GetHash queries. Since
// this data comes from cache, don't histogram hits.
HandleOneCheck(check, check->cache_hits);
}
}
void LocalSafeBrowsingDatabaseManager::OnRequestFullHash(
SafeBrowsingCheck* check) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
check->extended_reporting_level = GetExtendedReporting();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::RequestFullHash, this,
check));
}
ExtendedReportingLevel
LocalSafeBrowsingDatabaseManager::GetExtendedReporting() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Determine if the last used profile is opted into extended reporting.
// Note: It is possible that the last used profile is not the one triggers
// the hash request, but not very likely.
ExtendedReportingLevel extended_reporting_level = SBER_LEVEL_OFF;
ProfileManager* profile_manager = g_browser_process->profile_manager();
if (profile_manager) {
Profile* profile = profile_manager->GetLastUsedProfile();
extended_reporting_level =
profile ? GetExtendedReportingLevel(*profile->GetPrefs())
: SBER_LEVEL_OFF;
}
return extended_reporting_level;
}
void LocalSafeBrowsingDatabaseManager::RequestFullHash(
SafeBrowsingCheck* check) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_)
return;
bool is_download = check->check_type == BINURL;
sb_service_->protocol_manager()->GetFullHash(
check->prefix_hits,
base::Bind(&LocalSafeBrowsingDatabaseManager::HandleGetHashResults,
base::Unretained(this), check),
is_download, check->extended_reporting_level);
}
void LocalSafeBrowsingDatabaseManager::GetAllChunksFromDatabase(
GetChunksCallback callback) {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
bool database_error = true;
std::vector<SBListChunkRanges> lists;
DCHECK(!database_update_in_progress_);
database_update_in_progress_ = true;
GetDatabase(); // This guarantees that |database_| is non-NULL.
if (database_->UpdateStarted(&lists)) {
database_error = false;
} else {
database_->UpdateFinished(false);
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(
&LocalSafeBrowsingDatabaseManager::BeforeGetAllChunksFromDatabase,
this, lists, database_error, callback));
}
void LocalSafeBrowsingDatabaseManager::BeforeGetAllChunksFromDatabase(
const std::vector<SBListChunkRanges>& lists,
bool database_error,
GetChunksCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ExtendedReportingLevel extended_reporting_level = GetExtendedReporting();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(
&LocalSafeBrowsingDatabaseManager::OnGetAllChunksFromDatabase, this,
lists, database_error, extended_reporting_level, callback));
}
void LocalSafeBrowsingDatabaseManager::OnGetAllChunksFromDatabase(
const std::vector<SBListChunkRanges>& lists,
bool database_error,
ExtendedReportingLevel reporting_level,
GetChunksCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (enabled_)
callback.Run(lists, database_error, reporting_level);
}
void LocalSafeBrowsingDatabaseManager::OnAddChunksComplete(
AddChunksCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (enabled_)
callback.Run();
}
void LocalSafeBrowsingDatabaseManager::DatabaseLoadComplete() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!enabled_)
return;
LOCAL_HISTOGRAM_COUNTS("SB.QueueDepth", queued_checks_.size());
if (queued_checks_.empty())
return;
// If the database isn't already available, calling CheckUrl() in the loop
// below will add the check back to the queue, and we'll infinite-loop.
DCHECK(DatabaseAvailable());
while (!queued_checks_.empty()) {
QueuedCheck check = queued_checks_.front();
DCHECK(!check.start.is_null());
LOCAL_HISTOGRAM_TIMES("SB.QueueDelay",
base::TimeTicks::Now() - check.start);
// If CheckUrl() determines the URL is safe immediately, it doesn't call the
// client's handler function (because normally it's being directly called by
// the client). Since we're not the client, we have to convey this result.
if (check.client &&
CheckBrowseUrl(check.url, check.expected_threats, check.client)) {
SafeBrowsingCheck sb_check(std::vector<GURL>(1, check.url),
std::vector<SBFullHash>(), check.client,
check.check_type, check.expected_threats);
sb_check.OnSafeBrowsingResult();
}
queued_checks_.pop_front();
}
}
void LocalSafeBrowsingDatabaseManager::AddDatabaseChunks(
const std::string& list_name,
std::unique_ptr<std::vector<std::unique_ptr<SBChunkData>>> chunks,
AddChunksCallback callback) {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
if (chunks)
GetDatabase()->InsertChunks(list_name, *chunks);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::OnAddChunksComplete,
this, callback));
}
void LocalSafeBrowsingDatabaseManager::DeleteDatabaseChunks(
std::unique_ptr<std::vector<SBChunkDelete>> chunk_deletes) {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
if (chunk_deletes)
GetDatabase()->DeleteChunks(*chunk_deletes);
}
void LocalSafeBrowsingDatabaseManager::DatabaseUpdateFinished(
bool update_succeeded) {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
GetDatabase()->UpdateFinished(update_succeeded);
DCHECK(database_update_in_progress_);
database_update_in_progress_ = false;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(
&LocalSafeBrowsingDatabaseManager::NotifyDatabaseUpdateFinished, this,
update_succeeded));
}
void LocalSafeBrowsingDatabaseManager::OnCloseDatabase() {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
SafeBrowsingDatabase* to_delete = database_;
{
base::AutoLock lock(database_lock_);
DCHECK(closing_database_);
database_ = nullptr;
closing_database_ = false;
}
delete to_delete;
}
void LocalSafeBrowsingDatabaseManager::OnResetDatabase() {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
GetDatabase()->ResetDatabase();
}
void LocalSafeBrowsingDatabaseManager::OnHandleGetHashResults(
SafeBrowsingCheck* check,
const std::vector<SBFullHashResult>& full_hashes) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
ListType check_type = check->check_type;
SBPrefix prefix = check->prefix_hits[0];
GetHashRequests::iterator it = gethash_requests_.find(prefix);
if (check->prefix_hits.size() > 1 || it == gethash_requests_.end()) {
const bool hit = HandleOneCheck(check, full_hashes);
RecordGetHashCheckStatus(hit, check_type, full_hashes);
return;
}
// Call back all interested parties, noting if any has a hit.
GetHashRequestors& requestors = it->second;
bool hit = false;
for (GetHashRequestors::iterator r = requestors.begin();
r != requestors.end(); ++r) {
if (HandleOneCheck(*r, full_hashes))
hit = true;
}
RecordGetHashCheckStatus(hit, check_type, full_hashes);
gethash_requests_.erase(it);
}
bool LocalSafeBrowsingDatabaseManager::HandleOneCheck(
SafeBrowsingCheck* check,
const std::vector<SBFullHashResult>& full_hashes) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(check);
bool is_threat = false;
// TODO(shess): GetHashSeverestThreadListType() contains a loop,
// GetUrlSeverestThreatListType() a loop around that loop. Having another
// loop out here concerns me. It is likely that SAFE is an expected outcome,
// which means all of those loops run to completion. Refactoring this to
// generate a set of sorted items to compare in sequence would probably
// improve things.
//
// Additionally, the set of patterns generated from the urls is very similar
// to the patterns generated in ContainsBrowseUrl() and other database checks,
// which are called from this code. Refactoring that across the checks could
// interact well with batching the checks here.
std::vector<SBFullHashResult> expected_full_hashes;
for (const auto& full_hash : full_hashes) {
ListType type = static_cast<ListType>(full_hash.list_id);
if (IsExpectedThreat(GetThreatTypeFromListType(type),
check->expected_threats)) {
expected_full_hashes.push_back(full_hash);
}
}
if (expected_full_hashes.empty()) {
SafeBrowsingCheckDone(check);
return false;
}
for (size_t i = 0; i < check->urls.size(); ++i) {
size_t threat_index;
SBThreatType threat = GetUrlSeverestThreatType(
check->urls[i], expected_full_hashes, &threat_index);
if (threat != SB_THREAT_TYPE_SAFE) {
check->url_results[i] = threat;
check->url_metadata[i] = expected_full_hashes[threat_index].metadata;
const SBFullHash& hash = expected_full_hashes[threat_index].hash;
check->url_hit_hash[i] =
std::string(hash.full_hash, arraysize(hash.full_hash));
is_threat = true;
}
}
for (size_t i = 0; i < check->full_hashes.size(); ++i) {
SBThreatType threat =
GetHashSeverestThreatType(check->full_hashes[i], expected_full_hashes);
if (threat != SB_THREAT_TYPE_SAFE) {
check->full_hash_results[i] = threat;
is_threat = true;
}
}
SafeBrowsingCheckDone(check);
return is_threat;
}
void LocalSafeBrowsingDatabaseManager::OnAsyncCheckDone(
SafeBrowsingCheck* check,
const std::vector<SBPrefix>& prefix_hits) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(enable_download_protection_);
check->prefix_hits = prefix_hits;
if (check->prefix_hits.empty()) {
SafeBrowsingCheckDone(check);
} else {
check->need_get_hash = true;
OnCheckDone(check);
}
}
std::vector<SBPrefix>
LocalSafeBrowsingDatabaseManager::CheckDownloadUrlOnSBThread(
const std::vector<SBPrefix>& prefixes) {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
DCHECK(enable_download_protection_);
std::vector<SBPrefix> prefix_hits;
const bool result =
database_->ContainsDownloadUrlPrefixes(prefixes, &prefix_hits);
DCHECK_EQ(result, !prefix_hits.empty());
return prefix_hits;
}
std::vector<SBPrefix>
LocalSafeBrowsingDatabaseManager::CheckExtensionIDsOnSBThread(
const std::vector<SBPrefix>& prefixes) {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
std::vector<SBPrefix> prefix_hits;
const bool result =
database_->ContainsExtensionPrefixes(prefixes, &prefix_hits);
DCHECK_EQ(result, !prefix_hits.empty());
return prefix_hits;
}
std::vector<SBPrefix>
LocalSafeBrowsingDatabaseManager::CheckResourceUrlOnSBThread(
const std::vector<SBPrefix>& prefixes) {
DCHECK(safe_browsing_task_runner_->RunsTasksInCurrentSequence());
std::vector<SBPrefix> prefix_hits;
const bool result =
database_->ContainsResourceUrlPrefixes(prefixes, &prefix_hits);
DCHECK_EQ(result, !prefix_hits.empty());
return prefix_hits;
}
void LocalSafeBrowsingDatabaseManager::TimeoutCallback(
SafeBrowsingCheck* check) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(check);
if (!enabled_)
return;
DCHECK(checks_.find(check) != checks_.end());
if (check->client) {
check->OnSafeBrowsingResult();
check->client = NULL;
}
}
void LocalSafeBrowsingDatabaseManager::SafeBrowsingCheckDone(
SafeBrowsingCheck* check) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(check);
if (!enabled_)
return;
DCHECK(checks_.find(check) != checks_.end());
if (check->client)
check->OnSafeBrowsingResult();
checks_.erase(check);
}
void LocalSafeBrowsingDatabaseManager::StartSafeBrowsingCheck(
std::unique_ptr<SafeBrowsingCheck> check,
const base::Callback<std::vector<SBPrefix>(void)>& task) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
check->weak_ptr_factory_.reset(
new base::WeakPtrFactory<LocalSafeBrowsingDatabaseManager>(this));
SafeBrowsingCheck* check_ptr = check.get();
checks_[check_ptr] = std::move(check);
base::PostTaskAndReplyWithResult(
safe_browsing_task_runner_.get(), FROM_HERE, task,
base::Bind(&LocalSafeBrowsingDatabaseManager::OnAsyncCheckDone,
check_ptr->weak_ptr_factory_->GetWeakPtr(), check_ptr));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&LocalSafeBrowsingDatabaseManager::TimeoutCallback,
check_ptr->weak_ptr_factory_->GetWeakPtr(), check_ptr),
check_timeout_);
}
bool LocalSafeBrowsingDatabaseManager::IsDownloadProtectionEnabled() const {
return enable_download_protection_;
}
} // namespace safe_browsing