OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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/v4_get_hash_protocol_manager.h" | 5 #include "components/safe_browsing_db/v4_get_hash_protocol_manager.h" |
6 | 6 |
7 #include <utility> | 7 #include <utility> |
8 | 8 |
9 #include "base/base64url.h" | 9 #include "base/base64url.h" |
10 #include "base/macros.h" | 10 #include "base/macros.h" |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
45 INCONSISTENT_THREAT_TYPE_ERROR = 5, | 45 INCONSISTENT_THREAT_TYPE_ERROR = 5, |
46 | 46 |
47 // A match in the response contained a metadata, but the metadata is invalid. | 47 // A match in the response contained a metadata, but the metadata is invalid. |
48 UNEXPECTED_METADATA_VALUE_ERROR = 6, | 48 UNEXPECTED_METADATA_VALUE_ERROR = 6, |
49 | 49 |
50 // Memory space for histograms is determined by the max. ALWAYS | 50 // Memory space for histograms is determined by the max. ALWAYS |
51 // ADD NEW VALUES BEFORE THIS ONE. | 51 // ADD NEW VALUES BEFORE THIS ONE. |
52 PARSE_RESULT_TYPE_MAX = 7, | 52 PARSE_RESULT_TYPE_MAX = 7, |
53 }; | 53 }; |
54 | 54 |
55 // Enumerate full hash cache hits/misses for histogramming purposes. | |
56 // DO NOT CHANGE THE ORDERING OF THESE VALUES. | |
57 enum V4FullHashCacheResultType { | |
58 // Full hashes for which there is no cache hit. | |
59 FULL_HASH_CACHE_MISS = 0, | |
60 | |
61 // Full hashes with a cache hit. | |
62 FULL_HASH_CACHE_HIT = 1, | |
63 | |
64 // Full hashes with a negative cache hit. | |
65 FULL_HASH_NEGATIVE_CACHE_HIT = 2, | |
66 | |
67 // Memory space for histograms is determined by the max. ALWAYS | |
68 // ADD NEW VALUES BEFORE THIS ONE. | |
69 FULL_HASH_CACHE_RESULT_MAX | |
70 }; | |
71 | |
55 // Record parsing errors of a GetHash result. | 72 // Record parsing errors of a GetHash result. |
56 void RecordParseGetHashResult(ParseResultType result_type) { | 73 void RecordParseGetHashResult(ParseResultType result_type) { |
57 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type, | 74 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type, |
58 PARSE_RESULT_TYPE_MAX); | 75 PARSE_RESULT_TYPE_MAX); |
59 } | 76 } |
60 | 77 |
61 // Record a GetHash result. | 78 // Record a GetHash result. |
62 void RecordGetHashResult(safe_browsing::V4OperationResult result) { | 79 void RecordGetHashResult(safe_browsing::V4OperationResult result) { |
63 UMA_HISTOGRAM_ENUMERATION( | 80 UMA_HISTOGRAM_ENUMERATION( |
64 "SafeBrowsing.GetV4HashResult", result, | 81 "SafeBrowsing.GetV4HashResult", result, |
65 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX); | 82 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX); |
66 } | 83 } |
67 | 84 |
85 // Record a full hash cache hit result. | |
86 void RecordV4FullHashCacheResult( | |
87 V4FullHashCacheResultType result_type) { | |
88 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4FullHashCacheResult", result_type, | |
89 FULL_HASH_CACHE_RESULT_MAX); | |
90 } | |
91 | |
68 } // namespace | 92 } // namespace |
69 | 93 |
70 namespace safe_browsing { | 94 namespace safe_browsing { |
71 | 95 |
72 const char kUmaV4HashResponseMetricName[] = | 96 const char kUmaV4HashResponseMetricName[] = |
73 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode"; | 97 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode"; |
74 | 98 |
75 // The default V4GetHashProtocolManagerFactory. | 99 // The default V4GetHashProtocolManagerFactory. |
76 class V4GetHashProtocolManagerFactoryImpl | 100 class V4GetHashProtocolManagerFactoryImpl |
77 : public V4GetHashProtocolManagerFactory { | 101 : public V4GetHashProtocolManagerFactory { |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
113 : gethash_error_count_(0), | 137 : gethash_error_count_(0), |
114 gethash_back_off_mult_(1), | 138 gethash_back_off_mult_(1), |
115 next_gethash_time_(Time::FromDoubleT(0)), | 139 next_gethash_time_(Time::FromDoubleT(0)), |
116 config_(config), | 140 config_(config), |
117 request_context_getter_(request_context_getter), | 141 request_context_getter_(request_context_getter), |
118 url_fetcher_id_(0), | 142 url_fetcher_id_(0), |
119 clock_(new base::DefaultClock()) {} | 143 clock_(new base::DefaultClock()) {} |
120 | 144 |
121 V4GetHashProtocolManager::~V4GetHashProtocolManager() { | 145 V4GetHashProtocolManager::~V4GetHashProtocolManager() { |
122 // Delete in-progress SafeBrowsing requests. | 146 // Delete in-progress SafeBrowsing requests. |
123 STLDeleteContainerPairFirstPointers(hash_requests_.begin(), | 147 STLDeleteContainerPairFirstPointers(pending_hash_requests_.begin(), |
124 hash_requests_.end()); | 148 pending_hash_requests_.end()); |
125 hash_requests_.clear(); | 149 pending_hash_requests_.clear(); |
126 } | 150 } |
127 | 151 |
128 // static | 152 // static |
129 void V4GetHashProtocolManager::RegisterFactory( | 153 void V4GetHashProtocolManager::RegisterFactory( |
130 std::unique_ptr<V4GetHashProtocolManagerFactory> factory) { | 154 std::unique_ptr<V4GetHashProtocolManagerFactory> factory) { |
131 if (factory_) | 155 if (factory_) |
132 delete factory_; | 156 delete factory_; |
133 factory_ = factory.release(); | 157 factory_ = factory.release(); |
134 } | 158 } |
135 | 159 |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
205 if (expected_threat_type == THREAT_TYPE_UNSPECIFIED) { | 229 if (expected_threat_type == THREAT_TYPE_UNSPECIFIED) { |
206 expected_threat_type = match.threat_type(); | 230 expected_threat_type = match.threat_type(); |
207 } else if (match.threat_type() != expected_threat_type) { | 231 } else if (match.threat_type() != expected_threat_type) { |
208 RecordParseGetHashResult(INCONSISTENT_THREAT_TYPE_ERROR); | 232 RecordParseGetHashResult(INCONSISTENT_THREAT_TYPE_ERROR); |
209 return false; | 233 return false; |
210 } | 234 } |
211 | 235 |
212 // Fill in the full hash. | 236 // Fill in the full hash. |
213 SBFullHashResult result; | 237 SBFullHashResult result; |
214 result.hash = StringToSBFullHash(match.threat().hash()); | 238 result.hash = StringToSBFullHash(match.threat().hash()); |
215 | |
216 if (match.has_cache_duration()) { | 239 if (match.has_cache_duration()) { |
217 // Seconds resolution is good enough so we ignore the nanos field. | 240 // Seconds resolution is good enough so we ignore the nanos field. |
218 result.cache_expire_after = | 241 result.cache_expire_after = |
219 clock_->Now() + | 242 clock_->Now() + |
220 base::TimeDelta::FromSeconds(match.cache_duration().seconds()); | 243 base::TimeDelta::FromSeconds(match.cache_duration().seconds()); |
221 } else { | 244 } else { |
222 result.cache_expire_after = clock_->Now(); | 245 result.cache_expire_after = clock_->Now(); |
223 } | 246 } |
224 | 247 |
225 // Different threat types will handle the metadata differently. | 248 // Different threat types will handle the metadata differently. |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
321 std::string req_base64 = GetHashRequest(prefixes, platforms, threat_type); | 344 std::string req_base64 = GetHashRequest(prefixes, platforms, threat_type); |
322 GURL gethash_url; | 345 GURL gethash_url; |
323 net::HttpRequestHeaders headers; | 346 net::HttpRequestHeaders headers; |
324 GetHashUrlAndHeaders(req_base64, &gethash_url, &headers); | 347 GetHashUrlAndHeaders(req_base64, &gethash_url, &headers); |
325 | 348 |
326 net::URLFetcher* fetcher = | 349 net::URLFetcher* fetcher = |
327 net::URLFetcher::Create(url_fetcher_id_++, gethash_url, | 350 net::URLFetcher::Create(url_fetcher_id_++, gethash_url, |
328 net::URLFetcher::GET, this) | 351 net::URLFetcher::GET, this) |
329 .release(); | 352 .release(); |
330 fetcher->SetExtraRequestHeaders(headers.ToString()); | 353 fetcher->SetExtraRequestHeaders(headers.ToString()); |
331 hash_requests_[fetcher] = callback; | 354 pending_hash_requests_[fetcher] = std::make_pair(prefixes, callback); |
332 | 355 |
333 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); | 356 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); |
334 fetcher->SetRequestContext(request_context_getter_.get()); | 357 fetcher->SetRequestContext(request_context_getter_.get()); |
335 fetcher->Start(); | 358 fetcher->Start(); |
336 } | 359 } |
337 | 360 |
338 void V4GetHashProtocolManager::GetFullHashesWithApis( | 361 void V4GetHashProtocolManager::GetFullHashesWithApis( |
339 const std::vector<SBPrefix>& prefixes, | 362 const std::vector<SBPrefix>& prefixes, |
340 FullHashCallback callback) { | 363 FullHashCallback callback) { |
341 std::vector<PlatformType> platform = {CHROME_PLATFORM}; | 364 std::vector<PlatformType> platform = {CHROME_PLATFORM}; |
342 GetFullHashes(prefixes, platform, API_ABUSE, callback); | 365 GetFullHashes(prefixes, platform, API_ABUSE, callback); |
343 } | 366 } |
344 | 367 |
345 void V4GetHashProtocolManager::SetClockForTests( | 368 void V4GetHashProtocolManager::SetClockForTests( |
346 std::unique_ptr<base::Clock> clock) { | 369 std::unique_ptr<base::Clock> clock) { |
347 clock_ = std::move(clock); | 370 clock_ = std::move(clock); |
348 } | 371 } |
349 | 372 |
350 // net::URLFetcherDelegate implementation ---------------------------------- | 373 // net::URLFetcherDelegate implementation ---------------------------------- |
351 | 374 |
352 // SafeBrowsing request responses are handled here. | 375 // SafeBrowsing request responses are handled here. |
353 void V4GetHashProtocolManager::OnURLFetchComplete( | 376 void V4GetHashProtocolManager::OnURLFetchComplete( |
354 const net::URLFetcher* source) { | 377 const net::URLFetcher* source) { |
355 DCHECK(CalledOnValidThread()); | 378 DCHECK(CalledOnValidThread()); |
356 | 379 |
357 HashRequests::iterator it = hash_requests_.find(source); | 380 PendingHashRequests::iterator it = pending_hash_requests_.find(source); |
358 DCHECK(it != hash_requests_.end()) << "Request not found"; | 381 DCHECK(it != pending_hash_requests_.end()) << "Request not found"; |
359 | 382 |
360 // FindFullHashes response. | 383 // FindFullHashes response. |
361 // Reset the scoped pointer so the fetcher gets destroyed properly. | 384 // Reset the scoped pointer so the fetcher gets destroyed properly. |
362 std::unique_ptr<const net::URLFetcher> fetcher(it->first); | 385 std::unique_ptr<const net::URLFetcher> fetcher(it->first); |
363 | 386 |
364 int response_code = source->GetResponseCode(); | 387 int response_code = source->GetResponseCode(); |
365 net::URLRequestStatus status = source->GetStatus(); | 388 net::URLRequestStatus status = source->GetStatus(); |
366 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( | 389 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( |
367 kUmaV4HashResponseMetricName, status, response_code); | 390 kUmaV4HashResponseMetricName, status, response_code); |
368 | 391 |
369 const FullHashCallback& callback = it->second; | 392 const FullHashCallback& callback = it->second.second; |
Nathan Parker
2016/08/15 19:54:47
nit: I might vote for a simple struct rather than
| |
370 std::vector<SBFullHashResult> full_hashes; | 393 std::vector<SBFullHashResult> full_hashes; |
371 base::Time negative_cache_expire; | 394 base::Time negative_cache_expire; |
372 if (status.is_success() && response_code == net::HTTP_OK) { | 395 if (status.is_success() && response_code == net::HTTP_OK) { |
373 RecordGetHashResult(V4OperationResult::STATUS_200); | 396 RecordGetHashResult(V4OperationResult::STATUS_200); |
374 ResetGetHashErrors(); | 397 ResetGetHashErrors(); |
375 std::string data; | 398 std::string data; |
376 source->GetResponseAsString(&data); | 399 source->GetResponseAsString(&data); |
377 if (!ParseHashResponse(data, &full_hashes, &negative_cache_expire)) { | 400 if (!ParseHashResponse(data, &full_hashes, &negative_cache_expire)) { |
378 full_hashes.clear(); | 401 full_hashes.clear(); |
379 RecordGetHashResult(V4OperationResult::PARSE_ERROR); | 402 RecordGetHashResult(V4OperationResult::PARSE_ERROR); |
380 } | 403 } |
381 } else { | 404 } else { |
382 HandleGetHashError(clock_->Now()); | 405 HandleGetHashError(clock_->Now()); |
383 | 406 |
384 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: " | 407 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: " |
385 << source->GetURL() << " failed with error: " << status.error() | 408 << source->GetURL() << " failed with error: " << status.error() |
386 << " and response code: " << response_code; | 409 << " and response code: " << response_code; |
387 | 410 |
388 if (status.status() == net::URLRequestStatus::FAILED) { | 411 if (status.status() == net::URLRequestStatus::FAILED) { |
389 RecordGetHashResult(V4OperationResult::NETWORK_ERROR); | 412 RecordGetHashResult(V4OperationResult::NETWORK_ERROR); |
390 } else { | 413 } else { |
391 RecordGetHashResult(V4OperationResult::HTTP_ERROR); | 414 RecordGetHashResult(V4OperationResult::HTTP_ERROR); |
392 } | 415 } |
393 } | 416 } |
394 | 417 |
418 const std::vector<SBPrefix>& prefixes = it->second.first; | |
419 // If the time is uninitialized, don't cache the results. | |
420 if (!negative_cache_expire.is_null()) { | |
421 // Cache the results. | |
422 // Create or reset all cached results for this prefix. | |
423 for (const SBPrefix& prefix : prefixes) { | |
424 v4_full_hash_cache_[SB_THREAT_TYPE_API_ABUSE][prefix] = | |
Nathan Parker
2016/08/15 19:54:47
The threat type shouldn't be hardcoded here... it
| |
425 SBCachedFullHashResult(negative_cache_expire); | |
426 } | |
427 // Insert any full hash hits. Note that there may be one, multiple, or no | |
428 // full hashes for any given prefix. | |
429 for (const SBFullHashResult& result : full_hashes) { | |
430 v4_full_hash_cache_[SB_THREAT_TYPE_API_ABUSE][result.hash.prefix]. | |
431 full_hashes.push_back(result); | |
432 } | |
433 } | |
434 | |
395 // Invoke the callback with full_hashes, even if there was a parse error or | 435 // Invoke the callback with full_hashes, even if there was a parse error or |
396 // an error response code (in which case full_hashes will be empty). The | 436 // an error response code (in which case full_hashes will be empty). The |
397 // caller can't be blocked indefinitely. | 437 // caller can't be blocked indefinitely. |
398 callback.Run(full_hashes, negative_cache_expire); | 438 callback.Run(full_hashes, negative_cache_expire); |
399 | 439 |
400 hash_requests_.erase(it); | 440 pending_hash_requests_.erase(it); |
401 } | 441 } |
402 | 442 |
403 void V4GetHashProtocolManager::HandleGetHashError(const Time& now) { | 443 void V4GetHashProtocolManager::HandleGetHashError(const Time& now) { |
404 DCHECK(CalledOnValidThread()); | 444 DCHECK(CalledOnValidThread()); |
405 base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( | 445 base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( |
406 &gethash_error_count_, &gethash_back_off_mult_); | 446 &gethash_error_count_, &gethash_back_off_mult_); |
407 next_gethash_time_ = now + next; | 447 next_gethash_time_ = now + next; |
408 } | 448 } |
409 | 449 |
410 void V4GetHashProtocolManager::GetHashUrlAndHeaders( | 450 void V4GetHashProtocolManager::GetHashUrlAndHeaders( |
411 const std::string& req_base64, | 451 const std::string& req_base64, |
412 GURL* gurl, | 452 GURL* gurl, |
413 net::HttpRequestHeaders* headers) const { | 453 net::HttpRequestHeaders* headers) const { |
414 V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find", | 454 V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find", |
415 config_, gurl, headers); | 455 config_, gurl, headers); |
416 } | 456 } |
417 | 457 |
458 void V4GetHashProtocolManager::GetFullHashCachedResults( | |
459 const SBThreatType& threat_type, | |
460 const std::vector<SBFullHash>& full_hashes, | |
461 base::Time now, | |
462 std::vector<SBPrefix>* prefixes_needing_reqs, | |
463 std::vector<SBFullHashResult>* cached_results) { | |
464 DCHECK(prefixes_needing_reqs); | |
465 prefixes_needing_reqs->clear(); | |
466 DCHECK(cached_results); | |
467 cached_results->clear(); | |
468 | |
469 // Caching behavior is documented here: | |
470 // https://developers.google.com/safe-browsing/v4/caching#about-caching | |
471 // | |
472 // The cache operates as follows: | |
473 // Lookup: | |
474 // Case 1: The prefix is in the cache. | |
475 // Case a: The full hash is in the cache. | |
476 // Case i : The positive full hash result has not expired. | |
477 // The result is unsafe and we do not need to send a new | |
478 // request. | |
479 // Case ii: The positive full hash result has expired. | |
480 // We need to send a request for full hashes. | |
481 // Case b: The full hash is not in the cache. | |
482 // Case i : The negative cache entry has not expired. | |
483 // The result is still safe and we do not need to send a | |
484 // new request. | |
485 // Case ii: The negative cache entry has expired. | |
486 // We need to send a request for full hashes. | |
487 // Case 2: The prefix is not in the cache. | |
488 // We need to send a request for full hashes. | |
489 // | |
490 // Eviction: | |
491 // SBCachedFullHashResult entries can be removed from the cache only when | |
492 // the negative cache expire time and the cache expire time of all full | |
493 // hash results for that prefix have expired. | |
494 // Individual full hash results can be removed from the prefix's | |
495 // cache entry if they expire AND their expire time is after the negative | |
496 // cache expire time. | |
497 for (const SBFullHash& full_hash : full_hashes) { | |
498 auto entry = v4_full_hash_cache_[threat_type].find(full_hash.prefix); | |
499 if (entry != v4_full_hash_cache_[threat_type].end()) { | |
500 // Case 1. | |
501 SBCachedFullHashResult& cache_result = entry->second; | |
502 | |
503 const SBFullHashResult* found_full_hash = nullptr; | |
504 size_t matched_idx = 0; | |
505 for (const SBFullHashResult& hash_result : cache_result.full_hashes) { | |
506 if (SBFullHashEqual(full_hash, hash_result.hash)) { | |
507 found_full_hash = &hash_result; | |
508 break; | |
509 } | |
510 ++matched_idx; | |
511 } | |
512 | |
513 if (found_full_hash) { | |
514 // Case a. | |
515 if (found_full_hash->cache_expire_after > now) { | |
516 // Case i. | |
517 cached_results->push_back(*found_full_hash); | |
518 RecordV4FullHashCacheResult(FULL_HASH_CACHE_HIT); | |
519 } else { | |
520 // Case ii. | |
521 prefixes_needing_reqs->push_back(full_hash.prefix); | |
522 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); | |
523 // If the negative cache expire time has passed, evict this full hash | |
524 // result from the cache. | |
525 if (cache_result.expire_after <= now) { | |
526 cache_result.full_hashes.erase( | |
527 cache_result.full_hashes.begin() + matched_idx); | |
528 // If there are no more full hashes, we can evict the entire entry. | |
529 if (cache_result.full_hashes.empty()) { | |
530 v4_full_hash_cache_[threat_type].erase(entry); | |
531 } | |
532 } | |
533 } | |
534 } else { | |
535 // Case b. | |
536 if (cache_result.expire_after > now) { | |
537 // Case i. | |
538 RecordV4FullHashCacheResult(FULL_HASH_NEGATIVE_CACHE_HIT); | |
539 } else { | |
540 // Case ii. | |
541 prefixes_needing_reqs->push_back(full_hash.prefix); | |
542 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); | |
543 } | |
544 } | |
545 } else { | |
546 // Case 2. | |
547 prefixes_needing_reqs->push_back(full_hash.prefix); | |
548 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS); | |
549 } | |
550 } | |
551 | |
552 // Multiple full hashes could share a prefix, remove duplicates. | |
553 // TODO(kcarattini): Make |prefixes_needing_reqs| a set. | |
554 std::sort(prefixes_needing_reqs->begin(), prefixes_needing_reqs->end()); | |
555 prefixes_needing_reqs->erase(std::unique(prefixes_needing_reqs->begin(), | |
556 prefixes_needing_reqs->end()), prefixes_needing_reqs->end()); | |
557 } | |
558 | |
418 } // namespace safe_browsing | 559 } // namespace safe_browsing |
OLD | NEW |