| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/safe_browsing_db/database_manager.h" | 5 #include "components/safe_browsing_db/database_manager.h" |
| 6 | 6 |
| 7 #include "base/metrics/histogram_macros.h" | 7 #include "base/metrics/histogram_macros.h" |
| 8 #include "components/safe_browsing_db/v4_get_hash_protocol_manager.h" | 8 #include "components/safe_browsing_db/v4_get_hash_protocol_manager.h" |
| 9 #include "content/public/browser/browser_thread.h" | 9 #include "content/public/browser/browser_thread.h" |
| 10 #include "net/url_request/url_request_context_getter.h" | 10 #include "net/url_request/url_request_context_getter.h" |
| 11 #include "url/gurl.h" | 11 #include "url/gurl.h" |
| 12 | 12 |
| 13 using content::BrowserThread; | 13 using content::BrowserThread; |
| 14 | 14 |
| 15 namespace { | |
| 16 | |
| 17 // Enumerate full hash cache hits/misses for histogramming purposes. | |
| 18 // DO NOT CHANGE THE ORDERING OF THESE VALUES. | |
| 19 enum V4FullHashCacheResultType { | |
| 20 // Full hashes for which there is no cache hit. | |
| 21 FULL_HASH_CACHE_MISS = 0, | |
| 22 | |
| 23 // Full hashes with a cache hit. | |
| 24 FULL_HASH_CACHE_HIT = 1, | |
| 25 | |
| 26 // Full hashes with a negative cache hit. | |
| 27 FULL_HASH_NEGATIVE_CACHE_HIT = 2, | |
| 28 | |
| 29 // Memory space for histograms is determined by the max. ALWAYS | |
| 30 // ADD NEW VALUES BEFORE THIS ONE. | |
| 31 FULL_HASH_CACHE_RESULT_MAX | |
| 32 }; | |
| 33 | |
| 34 // Enumerate GetHash hits/misses for histogramming purposes. DO NOT CHANGE THE | |
| 35 // ORDERING OF THESE VALUES. | |
| 36 enum V4GetHashCheckResultType { | |
| 37 // Successful responses which returned no full hashes. | |
| 38 GET_HASH_CHECK_EMPTY = 0, | |
| 39 | |
| 40 // Successful responses for which one or more of the full hashes matched. | |
| 41 GET_HASH_CHECK_HIT = 1, | |
| 42 | |
| 43 // Successful responses which weren't empty but have no matches. | |
| 44 GET_HASH_CHECK_MISS = 2, | |
| 45 | |
| 46 // Memory space for histograms is determined by the max. ALWAYS | |
| 47 // ADD NEW VALUES BEFORE THIS ONE. | |
| 48 GET_HASH_CHECK_RESULT_MAX | |
| 49 }; | |
| 50 | |
| 51 // Record a full hash cache hit result. | |
| 52 void RecordV4FullHashCacheResult( | |
| 53 V4FullHashCacheResultType result_type) { | |
| 54 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4FullHashCacheResult", result_type, | |
| 55 FULL_HASH_CACHE_RESULT_MAX); | |
| 56 } | |
| 57 | |
| 58 // Record a GetHash hit result. | |
| 59 void RecordV4GetHashCheckResult( | |
| 60 V4GetHashCheckResultType result_type) { | |
| 61 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4GetHashCheckResult", result_type, | |
| 62 GET_HASH_CHECK_RESULT_MAX); | |
| 63 } | |
| 64 | |
| 65 } // namespace | |
| 66 | |
| 67 namespace safe_browsing { | 15 namespace safe_browsing { |
| 68 | 16 |
| 69 SafeBrowsingDatabaseManager::SafeBrowsingDatabaseManager() | 17 SafeBrowsingDatabaseManager::SafeBrowsingDatabaseManager() {} |
| 70 : v4_get_hash_protocol_manager_(NULL) { | |
| 71 } | |
| 72 | 18 |
| 73 SafeBrowsingDatabaseManager::~SafeBrowsingDatabaseManager() { | 19 SafeBrowsingDatabaseManager::~SafeBrowsingDatabaseManager() { |
| 74 DCHECK(v4_get_hash_protocol_manager_ == NULL); | 20 DCHECK(!v4_get_hash_protocol_manager_); |
| 75 } | 21 } |
| 76 | 22 |
| 77 void SafeBrowsingDatabaseManager::StartOnIOThread( | 23 void SafeBrowsingDatabaseManager::StartOnIOThread( |
| 78 net::URLRequestContextGetter* request_context_getter, | 24 net::URLRequestContextGetter* request_context_getter, |
| 79 const V4ProtocolConfig& config) { | 25 const V4ProtocolConfig& config) { |
| 80 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 26 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 81 | 27 |
| 28 base::hash_set<UpdateListIdentifier> stores_to_look({GetChromeUrlApiId()}); |
| 82 v4_get_hash_protocol_manager_ = V4GetHashProtocolManager::Create( | 29 v4_get_hash_protocol_manager_ = V4GetHashProtocolManager::Create( |
| 83 request_context_getter, config); | 30 request_context_getter, stores_to_look, config); |
| 84 } | 31 } |
| 85 | 32 |
| 86 // |shutdown| not used. Destroys the v4 protocol managers. This may be called | 33 // |shutdown| not used. Destroys the v4 protocol managers. This may be called |
| 87 // multiple times during the life of the DatabaseManager. | 34 // multiple times during the life of the DatabaseManager. |
| 88 // Must be called on IO thread. | 35 // Must be called on IO thread. |
| 89 void SafeBrowsingDatabaseManager::StopOnIOThread(bool shutdown) { | 36 void SafeBrowsingDatabaseManager::StopOnIOThread(bool shutdown) { |
| 90 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 37 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 91 // This cancels all in-flight GetHash requests. | |
| 92 if (v4_get_hash_protocol_manager_) { | |
| 93 delete v4_get_hash_protocol_manager_; | |
| 94 v4_get_hash_protocol_manager_ = NULL; | |
| 95 } | |
| 96 | 38 |
| 97 // Delete pending checks, calling back any clients with empty metadata. | 39 // Delete pending checks, calling back any clients with empty metadata. |
| 98 for (auto* check : api_checks_) { | 40 for (const SafeBrowsingApiCheck* check : api_checks_) { |
| 99 if (check->client()) { | 41 if (check->client()) { |
| 100 check->client()-> | 42 check->client()-> |
| 101 OnCheckApiBlacklistUrlResult(check->url(), ThreatMetadata()); | 43 OnCheckApiBlacklistUrlResult(check->url(), ThreatMetadata()); |
| 102 } | 44 } |
| 103 } | 45 } |
| 104 base::STLDeleteElements(&api_checks_); | 46 |
| 47 // This cancels all in-flight GetHash requests. |
| 48 v4_get_hash_protocol_manager_.reset(); |
| 105 } | 49 } |
| 106 | 50 |
| 107 SafeBrowsingDatabaseManager::ApiCheckSet::iterator | 51 SafeBrowsingDatabaseManager::ApiCheckSet::iterator |
| 108 SafeBrowsingDatabaseManager::FindClientApiCheck(Client* client) { | 52 SafeBrowsingDatabaseManager::FindClientApiCheck(Client* client) { |
| 109 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 53 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 110 for (ApiCheckSet::iterator it = api_checks_.begin(); | 54 for (ApiCheckSet::iterator it = api_checks_.begin(); |
| 111 it != api_checks_.end(); ++it) { | 55 it != api_checks_.end(); ++it) { |
| 112 if ((*it)->client() == client) { | 56 if ((*it)->client() == client) { |
| 113 return it; | 57 return it; |
| 114 } | 58 } |
| 115 } | 59 } |
| 116 return api_checks_.end(); | 60 return api_checks_.end(); |
| 117 } | 61 } |
| 118 | 62 |
| 119 bool SafeBrowsingDatabaseManager::CancelApiCheck(Client* client) { | 63 bool SafeBrowsingDatabaseManager::CancelApiCheck(Client* client) { |
| 120 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 64 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 121 ApiCheckSet::iterator it = FindClientApiCheck(client); | 65 ApiCheckSet::iterator it = FindClientApiCheck(client); |
| 122 if (it != api_checks_.end()) { | 66 if (it != api_checks_.end()) { |
| 123 delete *it; | |
| 124 api_checks_.erase(it); | 67 api_checks_.erase(it); |
| 125 return true; | 68 return true; |
| 126 } | 69 } |
| 127 NOTREACHED(); | 70 NOTREACHED(); |
| 128 return false; | 71 return false; |
| 129 } | 72 } |
| 130 | 73 |
| 131 bool SafeBrowsingDatabaseManager::CheckApiBlacklistUrl(const GURL& url, | 74 bool SafeBrowsingDatabaseManager::CheckApiBlacklistUrl(const GURL& url, |
| 132 Client* client) { | 75 Client* client) { |
| 133 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 76 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 134 DCHECK(v4_get_hash_protocol_manager_); | 77 DCHECK(v4_get_hash_protocol_manager_); |
| 135 | 78 |
| 136 // Make sure we can check this url. | 79 // Make sure we can check this url. |
| 137 if (!(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme))) { | 80 if (!(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme))) { |
| 138 return true; | 81 return true; |
| 139 } | 82 } |
| 140 | 83 |
| 141 // There can only be one in-progress check for the same client at a time. | 84 // There can only be one in-progress check for the same client at a time. |
| 142 DCHECK(FindClientApiCheck(client) == api_checks_.end()); | 85 DCHECK(FindClientApiCheck(client) == api_checks_.end()); |
| 143 | 86 |
| 144 // Compute a list of hashes for this url. | 87 std::unique_ptr<SafeBrowsingApiCheck> check( |
| 145 std::vector<SBFullHash> full_hashes; | 88 new SafeBrowsingApiCheck(url, client)); |
| 146 UrlToFullHashes(url, false, &full_hashes); | 89 api_checks_.insert(check.get()); |
| 147 if (full_hashes.empty()) | 90 v4_get_hash_protocol_manager_->GetFullHashesWithApis( |
| 148 return true; | 91 url, base::Bind(&SafeBrowsingDatabaseManager::OnThreatMetadataResponse, |
| 149 | 92 base::Unretained(this), base::Passed(std::move(check)))); |
| 150 // First check the cache. | |
| 151 | |
| 152 // Used to determine cache expiration. | |
| 153 base::Time now = base::Time::Now(); | |
| 154 | |
| 155 std::vector<SBFullHashResult> cached_results; | |
| 156 std::vector<SBPrefix> prefixes_needing_reqs; | |
| 157 GetFullHashCachedResults(SB_THREAT_TYPE_API_ABUSE, | |
| 158 full_hashes, now, &prefixes_needing_reqs, &cached_results); | |
| 159 | |
| 160 if (prefixes_needing_reqs.empty() && cached_results.empty()) | |
| 161 return true; | |
| 162 | |
| 163 SafeBrowsingApiCheck* check = | |
| 164 new SafeBrowsingApiCheck(url, prefixes_needing_reqs, full_hashes, | |
| 165 cached_results, client); | |
| 166 api_checks_.insert(check); | |
| 167 | |
| 168 if (prefixes_needing_reqs.empty()) { | |
| 169 check->set_start_time(base::TimeTicks::Now()); | |
| 170 // We can call the callback immediately if no prefixes require a request. | |
| 171 // The |full_hash_results| representing the results fromt eh SB server will | |
| 172 // be empty. | |
| 173 std::vector<SBFullHashResult> full_hash_results; | |
| 174 HandleGetHashesWithApisResults(check, full_hash_results, base::Time()); | |
| 175 return false; | |
| 176 } | |
| 177 | |
| 178 v4_get_hash_protocol_manager_->GetFullHashesWithApis(prefixes_needing_reqs, | |
| 179 base::Bind(&SafeBrowsingDatabaseManager::HandleGetHashesWithApisResults, | |
| 180 base::Unretained(this), check)); | |
| 181 | 93 |
| 182 return false; | 94 return false; |
| 183 } | 95 } |
| 184 | 96 |
| 185 void SafeBrowsingDatabaseManager::GetFullHashCachedResults( | 97 void SafeBrowsingDatabaseManager::OnThreatMetadataResponse( |
| 186 const SBThreatType& threat_type, | 98 std::unique_ptr<SafeBrowsingApiCheck> check, |
| 187 const std::vector<SBFullHash>& full_hashes, | 99 const ThreatMetadata& md) { |
| 188 base::Time now, | |
| 189 std::vector<SBPrefix>* prefixes_needing_reqs, | |
| 190 std::vector<SBFullHashResult>* cached_results) { | |
| 191 DCHECK(prefixes_needing_reqs); | |
| 192 prefixes_needing_reqs->clear(); | |
| 193 DCHECK(cached_results); | |
| 194 cached_results->clear(); | |
| 195 | |
| 196 // Caching behavior is documented here: | |
| 197 // https://developers.google.com/safe-browsing/v4/caching#about-caching | |
| 198 // | |
| 199 // The cache operates as follows: | |
| 200 // Lookup: | |
| 201 // Case 1: The prefix is in the cache. | |
| 202 // Case a: The full hash is in the cache. | |
| 203 // Case i : The positive full hash result has not expired. | |
| 204 // The result is unsafe and we do not need to send a new | |
| 205 // request. | |
| 206 // Case ii: The positive full hash result has expired. | |
| 207 // We need to send a request for full hashes. | |
| 208 // Case b: The full hash is not in the cache. | |
| 209 // Case i : The negative cache entry has not expired. | |
| 210 // The result is still safe and we do not need to send a | |
| 211 // new request. | |
| 212 // Case ii: The negative cache entry has expired. | |
| 213 // We need to send a request for full hashes. | |
| 214 // Case 2: The prefix is not in the cache. | |
| 215 // We need to send a request for full hashes. | |
| 216 // | |
| 217 // Eviction: | |
| 218 // SBCachedFullHashResult entries can be removed from the cache only when | |
| 219 // the negative cache expire time and the cache expire time of all full | |
| 220 // hash results for that prefix have expired. | |
| 221 // Individual full hash results can be removed from the prefix's | |
| 222 // cache entry if they expire AND their expire time is after the negative | |
| 223 // cache expire time. | |
| 224 for (const SBFullHash& full_hash : full_hashes) { | |
| 225 auto entry = v4_full_hash_cache_[threat_type].find(full_hash.prefix); | |
| 226 if (entry != v4_full_hash_cache_[threat_type].end()) { | |
| 227 // Case 1. | |
| 228 SBCachedFullHashResult& cache_result = entry->second; | |
| 229 | |
| 230 const SBFullHashResult* found_full_hash = nullptr; | |
| 231 size_t matched_idx = 0; | |
| 232 for (const SBFullHashResult& hash_result : cache_result.full_hashes) { | |
| 233 if (SBFullHashEqual(full_hash, hash_result.hash)) { | |
| 234 found_full_hash = &hash_result; | |
| 235 break; | |
| 236 } | |
| 237 ++matched_idx; | |
| 238 } | |
| 239 | |
| 240 if (found_full_hash) { | |
| 241 // Case a. | |
| 242 if (found_full_hash->cache_expire_after > now) { | |
| 243 // Case i. | |
| 244 cached_results->push_back(*found_full_hash); | |
| 245 RecordV4FullHashCacheResult(FULL_HASH_CACHE_HIT); | |
| 246 } else { | |
| 247 // Case ii. | |
| 248 prefixes_needing_reqs->push_back(full_hash.prefix); | |
| 249 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); | |
| 250 // If the negative cache expire time has passed, evict this full hash | |
| 251 // result from the cache. | |
| 252 if (cache_result.expire_after <= now) { | |
| 253 cache_result.full_hashes.erase( | |
| 254 cache_result.full_hashes.begin() + matched_idx); | |
| 255 // If there are no more full hashes, we can evict the entire entry. | |
| 256 if (cache_result.full_hashes.empty()) { | |
| 257 v4_full_hash_cache_[threat_type].erase(entry); | |
| 258 } | |
| 259 } | |
| 260 } | |
| 261 } else { | |
| 262 // Case b. | |
| 263 if (cache_result.expire_after > now) { | |
| 264 // Case i. | |
| 265 RecordV4FullHashCacheResult(FULL_HASH_NEGATIVE_CACHE_HIT); | |
| 266 } else { | |
| 267 // Case ii. | |
| 268 prefixes_needing_reqs->push_back(full_hash.prefix); | |
| 269 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); | |
| 270 } | |
| 271 } | |
| 272 } else { | |
| 273 // Case 2. | |
| 274 prefixes_needing_reqs->push_back(full_hash.prefix); | |
| 275 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); | |
| 276 } | |
| 277 } | |
| 278 | |
| 279 // Multiple full hashes could share a prefix, remove duplicates. | |
| 280 // TODO(kcarattini): Make |prefixes_needing_reqs| a set. | |
| 281 std::sort(prefixes_needing_reqs->begin(), prefixes_needing_reqs->end()); | |
| 282 prefixes_needing_reqs->erase(std::unique(prefixes_needing_reqs->begin(), | |
| 283 prefixes_needing_reqs->end()), prefixes_needing_reqs->end()); | |
| 284 } | |
| 285 | |
| 286 void SafeBrowsingDatabaseManager::HandleGetHashesWithApisResults( | |
| 287 SafeBrowsingApiCheck* check, | |
| 288 const std::vector<SBFullHashResult>& full_hash_results, | |
| 289 const base::Time& negative_cache_expire) { | |
| 290 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 100 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 291 DCHECK(check); | 101 DCHECK(check); |
| 292 | 102 |
| 293 // Record the network time. | |
| 294 if (!check->start_time().is_null()) { | |
| 295 UMA_HISTOGRAM_LONG_TIMES("SafeBrowsing.GetV4HashNetwork", | |
| 296 base::TimeTicks::Now() - check->start_time()); | |
| 297 } | |
| 298 | |
| 299 // If the time is uninitialized, don't cache the results. | |
| 300 if (!negative_cache_expire.is_null()) { | |
| 301 // Cache the results. | |
| 302 // Create or reset all cached results for this prefix. | |
| 303 for (const SBPrefix& prefix : check->prefixes()) { | |
| 304 v4_full_hash_cache_[SB_THREAT_TYPE_API_ABUSE][prefix] = | |
| 305 SBCachedFullHashResult(negative_cache_expire); | |
| 306 } | |
| 307 // Insert any full hash hits. Note that there may be one, multiple, or no | |
| 308 // full hashes for any given prefix. | |
| 309 for (const SBFullHashResult& result : full_hash_results) { | |
| 310 v4_full_hash_cache_[SB_THREAT_TYPE_API_ABUSE][result.hash.prefix]. | |
| 311 full_hashes.push_back(result); | |
| 312 } | |
| 313 } | |
| 314 | |
| 315 // If the check is not in |api_checks_| then the request was cancelled by the | 103 // If the check is not in |api_checks_| then the request was cancelled by the |
| 316 // client. | 104 // client. |
| 317 ApiCheckSet::iterator it = api_checks_.find(check); | 105 ApiCheckSet::iterator it = api_checks_.find(check.get()); |
| 318 if (it == api_checks_.end()) | 106 if (it == api_checks_.end()) |
| 319 return; | 107 return; |
| 320 | 108 |
| 321 ThreatMetadata md; | |
| 322 // Merge the metadata from all matching results. | |
| 323 // Note: A full hash may have a result in both the cached results (from | |
| 324 // its own cache lookup) and in the server results (if another full hash | |
| 325 // with the same prefix needed to request results from the server). In this | |
| 326 // unlikely case, the two results' metadata will be merged. | |
| 327 bool get_hash_hit = | |
| 328 PopulateApiMetadataResult(full_hash_results, check->full_hashes(), &md); | |
| 329 PopulateApiMetadataResult(check->cached_results(), check->full_hashes(), &md); | |
| 330 | |
| 331 if (get_hash_hit) { | |
| 332 RecordV4GetHashCheckResult(GET_HASH_CHECK_HIT); | |
| 333 } else if (full_hash_results.empty()) { | |
| 334 RecordV4GetHashCheckResult(GET_HASH_CHECK_EMPTY); | |
| 335 } else { | |
| 336 RecordV4GetHashCheckResult(GET_HASH_CHECK_MISS); | |
| 337 } | |
| 338 | |
| 339 check->client()->OnCheckApiBlacklistUrlResult(check->url(), md); | 109 check->client()->OnCheckApiBlacklistUrlResult(check->url(), md); |
| 340 api_checks_.erase(it); | 110 api_checks_.erase(it); |
| 341 delete check; | |
| 342 } | |
| 343 | |
| 344 // TODO(kcarattini): This is O(N^2). Look at improving performance by | |
| 345 // using a map, sorting or doing binary search etc.. | |
| 346 bool SafeBrowsingDatabaseManager::PopulateApiMetadataResult( | |
| 347 const std::vector<SBFullHashResult>& results, | |
| 348 const std::vector<SBFullHash>& full_hashes, | |
| 349 ThreatMetadata* md) { | |
| 350 DCHECK(md); | |
| 351 bool hit = false; | |
| 352 for (const SBFullHashResult& result : results) { | |
| 353 for (const SBFullHash& full_hash : full_hashes) { | |
| 354 if (SBFullHashEqual(full_hash, result.hash)) { | |
| 355 md->api_permissions.insert(result.metadata.api_permissions.begin(), | |
| 356 result.metadata.api_permissions.end()); | |
| 357 hit = true; | |
| 358 break; | |
| 359 } | |
| 360 } | |
| 361 } | |
| 362 return hit; | |
| 363 } | 111 } |
| 364 | 112 |
| 365 SafeBrowsingDatabaseManager::SafeBrowsingApiCheck::SafeBrowsingApiCheck( | 113 SafeBrowsingDatabaseManager::SafeBrowsingApiCheck::SafeBrowsingApiCheck( |
| 366 const GURL& url, | 114 const GURL& url, |
| 367 const std::vector<SBPrefix>& prefixes, | |
| 368 const std::vector<SBFullHash>& full_hashes, | |
| 369 const std::vector<SBFullHashResult>& cached_results, | |
| 370 Client* client) | 115 Client* client) |
| 371 : url_(url), prefixes_(prefixes), full_hashes_(full_hashes), | 116 : url_(url), client_(client) {} |
| 372 cached_results_(cached_results), client_(client) { | |
| 373 } | |
| 374 | 117 |
| 375 SafeBrowsingDatabaseManager::SafeBrowsingApiCheck::~SafeBrowsingApiCheck() { | 118 SafeBrowsingDatabaseManager::SafeBrowsingApiCheck::~SafeBrowsingApiCheck() {} |
| 376 } | |
| 377 | 119 |
| 378 } // namespace safe_browsing | 120 } // namespace safe_browsing |
| OLD | NEW |