Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(883)

Side by Side Diff: components/safe_browsing_db/v4_get_hash_protocol_manager.cc

Issue 2233103002: Move full hash caching logic to v4_get_hash_protocol_manager (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: shess@ feedback - part 2. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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"
11 #include "base/memory/ptr_util.h" 11 #include "base/memory/ptr_util.h"
12 #include "base/metrics/histogram_macros.h" 12 #include "base/metrics/histogram_macros.h"
13 #include "base/timer/timer.h" 13 #include "base/timer/timer.h"
14 #include "net/base/load_flags.h" 14 #include "net/base/load_flags.h"
15 #include "net/http/http_response_headers.h" 15 #include "net/http/http_response_headers.h"
16 #include "net/http/http_status_code.h" 16 #include "net/http/http_status_code.h"
17 #include "net/url_request/url_fetcher.h" 17 #include "net/url_request/url_fetcher.h"
18 #include "net/url_request/url_request_context_getter.h" 18 #include "net/url_request/url_request_context_getter.h"
19 19
20 using base::Time; 20 using base::Time;
21 using base::TimeDelta; 21 using base::TimeDelta;
22 22
23 namespace { 23 namespace {
24 24
25 // Record a GetHash result.
26 void RecordGetHashResult(safe_browsing::V4OperationResult result) {
27 UMA_HISTOGRAM_ENUMERATION(
28 "SafeBrowsing.GetV4HashResult", result,
29 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX);
30 }
31
25 // Enumerate parsing failures for histogramming purposes. DO NOT CHANGE 32 // Enumerate parsing failures for histogramming purposes. DO NOT CHANGE
26 // THE ORDERING OF THESE VALUES. 33 // THE ORDERING OF THESE VALUES.
27 enum ParseResultType { 34 enum ParseResultType {
28 // Error parsing the protocol buffer from a string. 35 // Error parsing the protocol buffer from a string.
29 PARSE_FROM_STRING_ERROR = 0, 36 PARSE_FROM_STRING_ERROR = 0,
30 37
31 // A match in the response had an unexpected THREAT_ENTRY_TYPE. 38 // A match in the response had an unexpected THREAT_ENTRY_TYPE.
32 UNEXPECTED_THREAT_ENTRY_TYPE_ERROR = 1, 39 UNEXPECTED_THREAT_ENTRY_TYPE_ERROR = 1,
33 40
34 // A match in the response had an unexpected THREAT_TYPE. 41 // A match in the response had an unexpected THREAT_TYPE.
35 UNEXPECTED_THREAT_TYPE_ERROR = 2, 42 UNEXPECTED_THREAT_TYPE_ERROR = 2,
36 43
37 // A match in the response had an unexpected PLATFORM_TYPE. 44 // A match in the response had an unexpected PLATFORM_TYPE.
38 UNEXPECTED_PLATFORM_TYPE_ERROR = 3, 45 UNEXPECTED_PLATFORM_TYPE_ERROR = 3,
39 46
40 // A match in the response contained no metadata where metadata was 47 // A match in the response contained no metadata where metadata was
41 // expected. 48 // expected.
42 NO_METADATA_ERROR = 4, 49 NO_METADATA_ERROR = 4,
43 50
44 // A match in the response contained a ThreatType that was inconsistent 51 // A match in the response contained a ThreatType that was inconsistent
45 // with the other matches. 52 // with the other matches.
46 INCONSISTENT_THREAT_TYPE_ERROR = 5, 53 INCONSISTENT_THREAT_TYPE_ERROR = 5,
47 54
48 // A match in the response contained a metadata, but the metadata is invalid. 55 // A match in the response contained a metadata, but the metadata is invalid.
49 UNEXPECTED_METADATA_VALUE_ERROR = 6, 56 UNEXPECTED_METADATA_VALUE_ERROR = 6,
50 57
58 // A match in the response had no information in the threat field.
59 NO_THREAT_ERROR = 7,
60
51 // Memory space for histograms is determined by the max. ALWAYS 61 // Memory space for histograms is determined by the max. ALWAYS
52 // ADD NEW VALUES BEFORE THIS ONE. 62 // ADD NEW VALUES BEFORE THIS ONE.
53 PARSE_RESULT_TYPE_MAX = 7, 63 PARSE_RESULT_TYPE_MAX = 8,
54 }; 64 };
55 65
56 // Record parsing errors of a GetHash result. 66 // Record parsing errors of a GetHash result.
57 void RecordParseGetHashResult(ParseResultType result_type) { 67 void RecordParseGetHashResult(ParseResultType result_type) {
58 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type, 68 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type,
59 PARSE_RESULT_TYPE_MAX); 69 PARSE_RESULT_TYPE_MAX);
60 } 70 }
61 71
62 // Record a GetHash result. 72 // Enumerate full hash cache hits/misses for histogramming purposes.
63 void RecordGetHashResult(safe_browsing::V4OperationResult result) { 73 // DO NOT CHANGE THE ORDERING OF THESE VALUES.
64 UMA_HISTOGRAM_ENUMERATION( 74 enum V4FullHashCacheResultType {
65 "SafeBrowsing.GetV4HashResult", result, 75 // Full hashes for which there is no cache hit.
66 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX); 76 FULL_HASH_CACHE_MISS = 0,
77
78 // Full hashes with a cache hit.
79 FULL_HASH_CACHE_HIT = 1,
80
81 // Full hashes with a negative cache hit.
82 FULL_HASH_NEGATIVE_CACHE_HIT = 2,
83
84 // Memory space for histograms is determined by the max. ALWAYS
85 // ADD NEW VALUES BEFORE THIS ONE.
86 FULL_HASH_CACHE_RESULT_MAX
87 };
88
89 // Record a full hash cache hit result.
90 void RecordV4FullHashCacheResult(V4FullHashCacheResultType result_type) {
91 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4FullHashCacheResult", result_type,
92 FULL_HASH_CACHE_RESULT_MAX);
67 } 93 }
68 94
95 // Enumerate GetHash hits/misses for histogramming purposes. DO NOT CHANGE THE
96 // ORDERING OF THESE VALUES.
97 enum V4GetHashCheckResultType {
98 // Successful responses which returned no full hashes.
99 GET_HASH_CHECK_EMPTY = 0,
100
101 // Successful responses for which one or more of the full hashes matched.
102 GET_HASH_CHECK_HIT = 1,
103
104 // Successful responses which weren't empty but have no matches.
105 GET_HASH_CHECK_MISS = 2,
106
107 // Memory space for histograms is determined by the max. ALWAYS
108 // ADD NEW VALUES BEFORE THIS ONE.
109 GET_HASH_CHECK_RESULT_MAX
110 };
111
112 // Record a GetHash hit result.
113 void RecordV4GetHashCheckResult(V4GetHashCheckResultType result_type) {
114 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4GetHashCheckResult", result_type,
115 GET_HASH_CHECK_RESULT_MAX);
116 }
117
118 const char kPermission[] = "permission";
119 const char kPhaPatternType[] = "pha_pattern_type";
120 const char kMalwarePatternType[] = "malware_pattern_type";
121 const char kSePatternType[] = "se_pattern_type";
122 const char kLanding[] = "LANDING";
123 const char kDistribution[] = "DISTRIBUTION";
124 const char kSocialEngineeringAds[] = "SOCIAL_ENGINEERING_ADS";
125 const char kSocialEngineeringLanding[] = "SOCIAL_ENGINEERING_LANDING";
126 const char kPhishing[] = "PHISHING";
127
69 } // namespace 128 } // namespace
70 129
71 namespace safe_browsing { 130 namespace safe_browsing {
72 131
73 const char kUmaV4HashResponseMetricName[] = 132 const char kUmaV4HashResponseMetricName[] =
74 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode"; 133 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode";
75 134
76 // The default V4GetHashProtocolManagerFactory. 135 // The default V4GetHashProtocolManagerFactory.
77 class V4GetHashProtocolManagerFactoryImpl 136 class V4GetHashProtocolManagerFactoryImpl
78 : public V4GetHashProtocolManagerFactory { 137 : public V4GetHashProtocolManagerFactory {
79 public: 138 public:
80 V4GetHashProtocolManagerFactoryImpl() {} 139 V4GetHashProtocolManagerFactoryImpl() {}
81 ~V4GetHashProtocolManagerFactoryImpl() override {} 140 ~V4GetHashProtocolManagerFactoryImpl() override {}
82 V4GetHashProtocolManager* CreateProtocolManager( 141 std::unique_ptr<V4GetHashProtocolManager> CreateProtocolManager(
83 net::URLRequestContextGetter* request_context_getter, 142 net::URLRequestContextGetter* request_context_getter,
143 const base::hash_set<UpdateListIdentifier>& stores_to_request,
84 const V4ProtocolConfig& config) override { 144 const V4ProtocolConfig& config) override {
85 return new V4GetHashProtocolManager(request_context_getter, config); 145 return base::WrapUnique(new V4GetHashProtocolManager(
146 request_context_getter, stores_to_request, config));
86 } 147 }
87 148
88 private: 149 private:
89 DISALLOW_COPY_AND_ASSIGN(V4GetHashProtocolManagerFactoryImpl); 150 DISALLOW_COPY_AND_ASSIGN(V4GetHashProtocolManagerFactoryImpl);
90 }; 151 };
91 152
153 // ----------------------------------------------------------------
154
155 CachedHashPrefixInfo::CachedHashPrefixInfo() {}
156
157 CachedHashPrefixInfo::CachedHashPrefixInfo(const CachedHashPrefixInfo& other) =
158 default;
159
160 CachedHashPrefixInfo::~CachedHashPrefixInfo() {}
161
162 // ----------------------------------------------------------------
163
164 FullHashCallbackInfo::FullHashCallbackInfo() {}
165
166 FullHashCallbackInfo::FullHashCallbackInfo(
167 const std::vector<FullHashInfo>& cached_full_hash_infos,
168 const std::vector<HashPrefix>& prefixes_requested,
169 std::unique_ptr<net::URLFetcher> fetcher,
170 const FullHashToStoreAndHashPrefixesMap&
171 full_hash_to_store_and_hash_prefixes,
172 const FullHashCallback& callback)
173 : cached_full_hash_infos(cached_full_hash_infos),
174 callback(callback),
175 fetcher(std::move(fetcher)),
176 full_hash_to_store_and_hash_prefixes(
177 full_hash_to_store_and_hash_prefixes),
178 prefixes_requested(prefixes_requested) {}
179
180 FullHashCallbackInfo::~FullHashCallbackInfo() {}
181
182 // ----------------------------------------------------------------
183
184 FullHashInfo::FullHashInfo(const FullHash& full_hash,
185 const UpdateListIdentifier& list_id,
186 const base::Time& positive_expiry)
187 : full_hash(full_hash),
188 list_id(list_id),
189 positive_expiry(positive_expiry) {}
190
191 FullHashInfo::FullHashInfo(const FullHashInfo& other) = default;
192
193 FullHashInfo::~FullHashInfo() {}
194
195 bool FullHashInfo::operator==(const FullHashInfo& other) const {
196 return full_hash == other.full_hash && list_id == other.list_id &&
197 positive_expiry == other.positive_expiry && metadata == other.metadata;
198 }
199
200 bool FullHashInfo::operator!=(const FullHashInfo& other) const {
201 return !operator==(other);
202 }
203
92 // V4GetHashProtocolManager implementation -------------------------------- 204 // V4GetHashProtocolManager implementation --------------------------------
93 205
94 // static 206 // static
95 V4GetHashProtocolManagerFactory* V4GetHashProtocolManager::factory_ = NULL; 207 V4GetHashProtocolManagerFactory* V4GetHashProtocolManager::factory_ = NULL;
96 208
97 // static 209 // static
98 V4GetHashProtocolManager* V4GetHashProtocolManager::Create( 210 std::unique_ptr<V4GetHashProtocolManager> V4GetHashProtocolManager::Create(
99 net::URLRequestContextGetter* request_context_getter, 211 net::URLRequestContextGetter* request_context_getter,
212 const base::hash_set<UpdateListIdentifier>& stores_to_request,
100 const V4ProtocolConfig& config) { 213 const V4ProtocolConfig& config) {
101 if (!factory_) 214 if (!factory_)
102 factory_ = new V4GetHashProtocolManagerFactoryImpl(); 215 factory_ = new V4GetHashProtocolManagerFactoryImpl();
103 return factory_->CreateProtocolManager(request_context_getter, config); 216 return factory_->CreateProtocolManager(request_context_getter,
217 stores_to_request, config);
104 } 218 }
105 219
106 void V4GetHashProtocolManager::ResetGetHashErrors() { 220 // static
107 gethash_error_count_ = 0; 221 void V4GetHashProtocolManager::RegisterFactory(
108 gethash_back_off_mult_ = 1; 222 std::unique_ptr<V4GetHashProtocolManagerFactory> factory) {
223 if (factory_)
224 delete factory_;
225 factory_ = factory.release();
109 } 226 }
110 227
111 V4GetHashProtocolManager::V4GetHashProtocolManager( 228 V4GetHashProtocolManager::V4GetHashProtocolManager(
112 net::URLRequestContextGetter* request_context_getter, 229 net::URLRequestContextGetter* request_context_getter,
230 const base::hash_set<UpdateListIdentifier>& stores_to_request,
113 const V4ProtocolConfig& config) 231 const V4ProtocolConfig& config)
114 : gethash_error_count_(0), 232 : gethash_error_count_(0),
115 gethash_back_off_mult_(1), 233 gethash_back_off_mult_(1),
116 next_gethash_time_(Time::FromDoubleT(0)), 234 next_gethash_time_(Time::FromDoubleT(0)),
117 config_(config), 235 config_(config),
118 request_context_getter_(request_context_getter), 236 request_context_getter_(request_context_getter),
119 url_fetcher_id_(0), 237 url_fetcher_id_(0),
120 clock_(new base::DefaultClock()) {} 238 clock_(new base::DefaultClock()) {
121 239 DCHECK(!stores_to_request.empty());
122 V4GetHashProtocolManager::~V4GetHashProtocolManager() { 240 for (const UpdateListIdentifier& store : stores_to_request) {
123 } 241 platform_types_.insert(store.platform_type);
124 242 threat_entry_types_.insert(store.threat_entry_type);
125 // static 243 threat_types_.insert(store.threat_type);
126 void V4GetHashProtocolManager::RegisterFactory( 244 }
127 std::unique_ptr<V4GetHashProtocolManagerFactory> factory) { 245 }
128 if (factory_) 246
129 delete factory_; 247 V4GetHashProtocolManager::~V4GetHashProtocolManager() {}
130 factory_ = factory.release(); 248
249 void V4GetHashProtocolManager::ClearCache() {
250 DCHECK(CalledOnValidThread());
251 full_hash_cache_.clear();
252 }
253
254 void V4GetHashProtocolManager::GetFullHashes(
255 const FullHashToStoreAndHashPrefixesMap&
256 full_hash_to_store_and_hash_prefixes,
257 FullHashCallback callback) {
258 DCHECK(CalledOnValidThread());
259 DCHECK(!full_hash_to_store_and_hash_prefixes.empty());
260
261 std::vector<HashPrefix> prefixes_to_request;
262 std::vector<FullHashInfo> cached_full_hash_infos;
263 GetFullHashCachedResults(full_hash_to_store_and_hash_prefixes, Time::Now(),
264 &prefixes_to_request, &cached_full_hash_infos);
265
266 if (prefixes_to_request.empty()) {
267 // 100% cache hits (positive or negative) so we can call the callback right
268 // away.
269 callback.Run(cached_full_hash_infos);
270 return;
271 }
272
273 // We need to wait the minimum waiting duration, and if we are in backoff,
274 // we need to check if we're past the next allowed time. If we are, we can
275 // proceed with the request. If not, we are required to return empty results
276 // (i.e. just use the results from cache and potentially report an unsafe
277 // resource as safe).
278 if (clock_->Now() <= next_gethash_time_) {
279 if (gethash_error_count_) {
280 RecordGetHashResult(V4OperationResult::BACKOFF_ERROR);
281 } else {
282 RecordGetHashResult(V4OperationResult::MIN_WAIT_DURATION_ERROR);
283 }
284 callback.Run(cached_full_hash_infos);
285 return;
286 }
287
288 std::string req_base64 = GetHashRequest(prefixes_to_request);
289 GURL gethash_url;
290 net::HttpRequestHeaders headers;
291 GetHashUrlAndHeaders(req_base64, &gethash_url, &headers);
292
293 std::unique_ptr<net::URLFetcher> owned_fetcher = net::URLFetcher::Create(
294 url_fetcher_id_++, gethash_url, net::URLFetcher::GET, this);
295 net::URLFetcher* fetcher = owned_fetcher.get();
296 pending_hash_requests_[fetcher].reset(new FullHashCallbackInfo(
297 cached_full_hash_infos, prefixes_to_request, std::move(owned_fetcher),
298 full_hash_to_store_and_hash_prefixes, callback));
299
300 fetcher->SetExtraRequestHeaders(headers.ToString());
301 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE);
302 fetcher->SetRequestContext(request_context_getter_.get());
303 fetcher->Start();
304 }
305
306 void V4GetHashProtocolManager::GetFullHashesWithApis(
307 const GURL& url,
308 ThreatMetadataForApiCallback api_callback) {
309 DCHECK(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme));
310
311 base::hash_set<FullHash> full_hashes;
312 V4ProtocolManagerUtil::UrlToFullHashes(url, &full_hashes);
313
314 FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes;
315 for (const FullHash& full_hash : full_hashes) {
316 HashPrefix prefix;
317 bool result =
318 V4ProtocolManagerUtil::FullHashToSmallestHashPrefix(full_hash, &prefix);
319 DCHECK(result);
320 full_hash_to_store_and_hash_prefixes[full_hash].emplace_back(
321 GetChromeUrlApiId(), prefix);
322 }
323
324 GetFullHashes(full_hash_to_store_and_hash_prefixes,
325 base::Bind(&V4GetHashProtocolManager::OnFullHashForApi,
326 base::Unretained(this), api_callback, full_hashes));
327 }
328
329 void V4GetHashProtocolManager::GetFullHashCachedResults(
330 const FullHashToStoreAndHashPrefixesMap&
331 full_hash_to_store_and_hash_prefixes,
332 const Time& now,
333 std::vector<HashPrefix>* prefixes_to_request,
334 std::vector<FullHashInfo>* cached_full_hash_infos) const {
335 DCHECK(!full_hash_to_store_and_hash_prefixes.empty());
336 DCHECK(prefixes_to_request->empty());
337 DCHECK(cached_full_hash_infos->empty());
338
339 // Caching behavior is documented here:
340 // https://developers.google.com/safe-browsing/v4/caching#about-caching
341 //
342 // The cache operates as follows:
343 // Lookup:
344 // Case 1: The prefix is in the cache.
345 // Case a: The full hash is in the cache.
346 // Case i : The positive full hash result has not expired.
347 // The result is unsafe and we do not need to send a new
348 // request.
349 // Case ii: The positive full hash result has expired.
350 // We need to send a request for full hashes.
351 // Case b: The full hash is not in the cache.
352 // Case i : The negative cache entry has not expired.
353 // The result is still safe and we do not need to send a
354 // new request.
355 // Case ii: The negative cache entry has expired.
356 // We need to send a request for full hashes.
357 // Case 2: The prefix is not in the cache.
358 // We need to send a request for full hashes.
359 //
360 // Note on eviction:
361 // CachedHashPrefixInfo entries can be removed from the cache only when
362 // the negative cache expire time and the cache expire time of all full
363 // hash results for that prefix have expired.
364 // Individual full hash results can be removed from the prefix's
365 // cache entry if they expire AND their expire time is after the negative
366 // cache expire time.
367
368 base::hash_set<HashPrefix> unique_prefixes_to_request;
369 for (const auto& it : full_hash_to_store_and_hash_prefixes) {
370 const FullHash& full_hash = it.first;
371 const StoreAndHashPrefixes& matched = it.second;
372 for (const StoreAndHashPrefix& matched_it : matched) {
373 const UpdateListIdentifier& list_id = matched_it.list_id;
374 const HashPrefix& prefix = matched_it.hash_prefix;
375 auto prefix_entry = full_hash_cache_.find(prefix);
376 if (prefix_entry != full_hash_cache_.end()) {
377 // Case 1.
378 const CachedHashPrefixInfo& cached_prefix_info = prefix_entry->second;
379 bool found_full_hash = false;
380 for (const FullHashInfo& full_hash_info :
381 cached_prefix_info.full_hash_infos) {
382 if (full_hash_info.full_hash == full_hash &&
383 full_hash_info.list_id == list_id) {
384 // Case a.
385 found_full_hash = true;
386 if (full_hash_info.positive_expiry > now) {
387 // Case i.
388 cached_full_hash_infos->push_back(full_hash_info);
389 RecordV4FullHashCacheResult(FULL_HASH_CACHE_HIT);
390 } else {
391 // Case ii.
392 unique_prefixes_to_request.insert(prefix);
393 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS);
394 }
395 break;
396 }
397 }
398
399 if (!found_full_hash) {
400 // Case b.
401 if (cached_prefix_info.negative_expiry > now) {
402 // Case i.
403 RecordV4FullHashCacheResult(FULL_HASH_NEGATIVE_CACHE_HIT);
404 } else {
405 // Case ii.
406 unique_prefixes_to_request.insert(prefix);
407 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS);
408 }
409 }
410 } else {
411 // Case 2.
412 unique_prefixes_to_request.insert(prefix);
413 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS);
414 }
415 }
416 }
417
418 prefixes_to_request->insert(prefixes_to_request->begin(),
419 unique_prefixes_to_request.begin(),
420 unique_prefixes_to_request.end());
131 } 421 }
132 422
133 std::string V4GetHashProtocolManager::GetHashRequest( 423 std::string V4GetHashProtocolManager::GetHashRequest(
134 const std::vector<SBPrefix>& prefixes, 424 const std::vector<HashPrefix>& prefixes_to_request) {
135 const std::vector<PlatformType>& platforms, 425 DCHECK(!prefixes_to_request.empty());
136 ThreatType threat_type) { 426
137 // Build the request. Client info and client states are not added to the
138 // request protocol buffer. Client info is passed as params in the url.
139 FindFullHashesRequest req; 427 FindFullHashesRequest req;
140 ThreatInfo* info = req.mutable_threat_info(); 428 ThreatInfo* info = req.mutable_threat_info();
141 info->add_threat_types(threat_type); 429 for (const PlatformType p : platform_types_) {
142 info->add_threat_entry_types(URL);
143 for (const PlatformType p : platforms) {
144 info->add_platform_types(p); 430 info->add_platform_types(p);
145 } 431 }
146 for (const SBPrefix& prefix : prefixes) { 432 for (const ThreatEntryType tet : threat_entry_types_) {
147 std::string hash(reinterpret_cast<const char*>(&prefix), sizeof(SBPrefix)); 433 info->add_threat_entry_types(tet);
148 info->add_threat_entries()->set_hash(hash); 434 }
435 for (const ThreatType tt : threat_types_) {
436 info->add_threat_types(tt);
437 }
438 for (const HashPrefix& prefix : prefixes_to_request) {
439 info->add_threat_entries()->set_hash(prefix);
149 } 440 }
150 441
151 // Serialize and Base64 encode. 442 // Serialize and Base64 encode.
152 std::string req_data, req_base64; 443 std::string req_data, req_base64;
153 req.SerializeToString(&req_data); 444 req.SerializeToString(&req_data);
154 base::Base64UrlEncode(req_data, base::Base64UrlEncodePolicy::INCLUDE_PADDING, 445 base::Base64UrlEncode(req_data, base::Base64UrlEncodePolicy::INCLUDE_PADDING,
155 &req_base64); 446 &req_base64);
156 return req_base64; 447 return req_base64;
157 } 448 }
158 449
450 void V4GetHashProtocolManager::GetHashUrlAndHeaders(
451 const std::string& req_base64,
452 GURL* gurl,
453 net::HttpRequestHeaders* headers) const {
454 V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find",
455 config_, gurl, headers);
456 }
457
458 void V4GetHashProtocolManager::HandleGetHashError(const Time& now) {
459 DCHECK(CalledOnValidThread());
460 TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval(
461 &gethash_error_count_, &gethash_back_off_mult_);
462 next_gethash_time_ = now + next;
463 }
464
465 void V4GetHashProtocolManager::OnFullHashForApi(
466 const ThreatMetadataForApiCallback& api_callback,
467 const base::hash_set<FullHash>& full_hashes,
468 const std::vector<FullHashInfo>& full_hash_infos) {
469 ThreatMetadata md;
470 for (const FullHashInfo& full_hash_info : full_hash_infos) {
471 DCHECK_EQ(GetChromeUrlApiId(), full_hash_info.list_id);
472 DCHECK(full_hashes.find(full_hash_info.full_hash) != full_hashes.end());
473 md.api_permissions.insert(full_hash_info.metadata.api_permissions.begin(),
474 full_hash_info.metadata.api_permissions.end());
475 }
476
477 api_callback.Run(md);
478 }
479
159 bool V4GetHashProtocolManager::ParseHashResponse( 480 bool V4GetHashProtocolManager::ParseHashResponse(
160 const std::string& data, 481 const std::string& response_data,
161 std::vector<SBFullHashResult>* full_hashes, 482 std::vector<FullHashInfo>* full_hash_infos,
162 base::Time* negative_cache_expire) { 483 Time* negative_cache_expire) {
163 FindFullHashesResponse response; 484 FindFullHashesResponse response;
164 485
165 if (!response.ParseFromString(data)) { 486 if (!response.ParseFromString(response_data)) {
166 RecordParseGetHashResult(PARSE_FROM_STRING_ERROR); 487 RecordParseGetHashResult(PARSE_FROM_STRING_ERROR);
167 return false; 488 return false;
168 } 489 }
169 490
170 // negative_cache_duration should always be set. 491 // negative_cache_duration should always be set.
171 DCHECK(response.has_negative_cache_duration()); 492 DCHECK(response.has_negative_cache_duration());
493
172 // Seconds resolution is good enough so we ignore the nanos field. 494 // Seconds resolution is good enough so we ignore the nanos field.
173 *negative_cache_expire = 495 *negative_cache_expire =
174 clock_->Now() + base::TimeDelta::FromSeconds( 496 clock_->Now() +
175 response.negative_cache_duration().seconds()); 497 TimeDelta::FromSeconds(response.negative_cache_duration().seconds());
176 498
177 if (response.has_minimum_wait_duration()) { 499 if (response.has_minimum_wait_duration()) {
178 // Seconds resolution is good enough so we ignore the nanos field. 500 // Seconds resolution is good enough so we ignore the nanos field.
179 next_gethash_time_ = 501 next_gethash_time_ =
180 clock_->Now() + base::TimeDelta::FromSeconds( 502 clock_->Now() +
181 response.minimum_wait_duration().seconds()); 503 TimeDelta::FromSeconds(response.minimum_wait_duration().seconds());
182 } 504 }
183 505
184 // We only expect one threat type per request, so we make sure
185 // the threat types are consistent between matches.
186 ThreatType expected_threat_type = THREAT_TYPE_UNSPECIFIED;
187
188 // Loop over the threat matches and fill in full_hashes.
189 for (const ThreatMatch& match : response.matches()) { 506 for (const ThreatMatch& match : response.matches()) {
190 // Make sure the platform and threat entry type match. 507 if (!match.has_platform_type()) {
191 if (!(match.has_threat_entry_type() && match.threat_entry_type() == URL && 508 RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR);
192 match.has_threat())) { 509 return false;
510 }
511 if (!match.has_threat_entry_type()) {
193 RecordParseGetHashResult(UNEXPECTED_THREAT_ENTRY_TYPE_ERROR); 512 RecordParseGetHashResult(UNEXPECTED_THREAT_ENTRY_TYPE_ERROR);
194 return false; 513 return false;
195 } 514 }
196
197 if (!match.has_threat_type()) { 515 if (!match.has_threat_type()) {
198 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR); 516 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR);
199 return false; 517 return false;
200 } 518 }
201 519 if (!match.has_threat()) {
202 if (expected_threat_type == THREAT_TYPE_UNSPECIFIED) { 520 RecordParseGetHashResult(NO_THREAT_ERROR);
203 expected_threat_type = match.threat_type(); 521 return false;
204 } else if (match.threat_type() != expected_threat_type) { 522 }
205 RecordParseGetHashResult(INCONSISTENT_THREAT_TYPE_ERROR); 523
206 return false; 524 UpdateListIdentifier list_id(
207 } 525 match.platform_type(), match.threat_entry_type(), match.threat_type());
208 526 base::Time positive_expiry;
209 // Fill in the full hash.
210 SBFullHashResult result;
211 result.hash = StringToSBFullHash(match.threat().hash());
212
213 if (match.has_cache_duration()) { 527 if (match.has_cache_duration()) {
214 // Seconds resolution is good enough so we ignore the nanos field. 528 // Seconds resolution is good enough so we ignore the nanos field.
215 result.cache_expire_after = 529 positive_expiry = clock_->Now() + TimeDelta::FromSeconds(
216 clock_->Now() + 530 match.cache_duration().seconds());
217 base::TimeDelta::FromSeconds(match.cache_duration().seconds());
218 } else { 531 } else {
219 result.cache_expire_after = clock_->Now(); 532 positive_expiry = clock_->Now() - base::TimeDelta::FromSeconds(1);
220 } 533 }
221 534 FullHashInfo full_hash_info(match.threat().hash(), list_id,
222 // Different threat types will handle the metadata differently. 535 positive_expiry);
223 if (match.threat_type() == API_ABUSE) { 536 if (!ParseMetadata(match, &full_hash_info.metadata)) {
224 if (match.has_platform_type() && 537 return false;
225 match.platform_type() == CHROME_PLATFORM) { 538 }
226 if (match.has_threat_entry_metadata()) { 539
227 // For API Abuse, store a list of the returned permissions. 540 full_hash_infos->push_back(full_hash_info);
228 for (const ThreatEntryMetadata::MetadataEntry& m : 541 }
229 match.threat_entry_metadata().entries()) { 542 return true;
230 if (m.key() == "permission") { 543 }
231 result.metadata.api_permissions.insert(m.value()); 544
232 } else { 545 bool V4GetHashProtocolManager::ParseMetadata(const ThreatMatch& match,
233 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR); 546 ThreatMetadata* metadata) {
234 return false; 547 // Different threat types will handle the metadata differently.
235 } 548 if (match.threat_type() == API_ABUSE) {
236 } 549 if (!match.has_platform_type() ||
550 match.platform_type() != CHROME_PLATFORM) {
551 RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR);
552 return false;
553 }
554
555 if (!match.has_threat_entry_metadata()) {
556 RecordParseGetHashResult(NO_METADATA_ERROR);
557 return false;
558 }
559 // For API Abuse, store a list of the returned permissions.
560 for (const ThreatEntryMetadata::MetadataEntry& m :
561 match.threat_entry_metadata().entries()) {
562 if (m.key() != kPermission) {
563 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR);
564 return false;
565 }
566 metadata->api_permissions.insert(m.value());
567 }
568 } else if (match.threat_type() == MALWARE_THREAT ||
569 match.threat_type() == POTENTIALLY_HARMFUL_APPLICATION) {
570 for (const ThreatEntryMetadata::MetadataEntry& m :
571 match.threat_entry_metadata().entries()) {
572 // TODO: Need to confirm the below key/value pairs with CSD backend.
573 if (m.key() == kPhaPatternType || m.key() == kMalwarePatternType) {
574 if (m.value() == kLanding) {
575 metadata->threat_pattern_type = ThreatPatternType::MALWARE_LANDING;
576 break;
577 } else if (m.value() == kDistribution) {
578 metadata->threat_pattern_type =
579 ThreatPatternType::MALWARE_DISTRIBUTION;
580 break;
237 } else { 581 } else {
238 RecordParseGetHashResult(NO_METADATA_ERROR); 582 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR);
239 return false; 583 return false;
240 } 584 }
241 } else { 585 }
242 RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR); 586 }
243 return false; 587 } else if (match.threat_type() == SOCIAL_ENGINEERING_PUBLIC) {
244 } 588 for (const ThreatEntryMetadata::MetadataEntry& m :
245 } else if (match.threat_type() == MALWARE_THREAT || 589 match.threat_entry_metadata().entries()) {
246 match.threat_type() == POTENTIALLY_HARMFUL_APPLICATION) { 590 if (m.key() == kSePatternType) {
247 for (const ThreatEntryMetadata::MetadataEntry& m : 591 if (m.value() == kSocialEngineeringAds) {
248 match.threat_entry_metadata().entries()) { 592 metadata->threat_pattern_type =
249 // TODO: Need to confirm the below key/value pairs with CSD backend. 593 ThreatPatternType::SOCIAL_ENGINEERING_ADS;
250 if (m.key() == "pha_pattern_type" || 594 break;
251 m.key() == "malware_pattern_type") { 595 } else if (m.value() == kSocialEngineeringLanding) {
252 if (m.value() == "LANDING") { 596 metadata->threat_pattern_type =
253 result.metadata.threat_pattern_type = 597 ThreatPatternType::SOCIAL_ENGINEERING_LANDING;
254 ThreatPatternType::MALWARE_LANDING; 598 break;
255 break; 599 } else if (m.value() == kPhishing) {
256 } else if (m.value() == "DISTRIBUTION") { 600 metadata->threat_pattern_type = ThreatPatternType::PHISHING;
257 result.metadata.threat_pattern_type = 601 break;
258 ThreatPatternType::MALWARE_DISTRIBUTION; 602 } else {
259 break; 603 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR);
260 } else { 604 return false;
261 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR);
262 return false;
263 }
264 } 605 }
265 } 606 }
266 } else if (match.threat_type() == SOCIAL_ENGINEERING_PUBLIC) { 607 }
267 for (const ThreatEntryMetadata::MetadataEntry& m : 608 } else {
268 match.threat_entry_metadata().entries()) { 609 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR);
269 if (m.key() == "se_pattern_type") { 610 return false;
270 if (m.value() == "SOCIAL_ENGINEERING_ADS") { 611 }
271 result.metadata.threat_pattern_type = 612
272 ThreatPatternType::SOCIAL_ENGINEERING_ADS;
273 break;
274 } else if (m.value() == "SOCIAL_ENGINEERING_LANDING") {
275 result.metadata.threat_pattern_type =
276 ThreatPatternType::SOCIAL_ENGINEERING_LANDING;
277 break;
278 } else if (m.value() == "PHISHING") {
279 result.metadata.threat_pattern_type = ThreatPatternType::PHISHING;
280 break;
281 } else {
282 RecordParseGetHashResult(UNEXPECTED_METADATA_VALUE_ERROR);
283 return false;
284 }
285 }
286 }
287 } else {
288 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR);
289 return false;
290 }
291
292 full_hashes->push_back(result);
293 }
294 return true; 613 return true;
295 } 614 }
296 615
297 void V4GetHashProtocolManager::GetFullHashes( 616 void V4GetHashProtocolManager::ResetGetHashErrors() {
298 const std::vector<SBPrefix>& prefixes, 617 gethash_error_count_ = 0;
299 const std::vector<PlatformType>& platforms, 618 gethash_back_off_mult_ = 1;
300 ThreatType threat_type,
301 FullHashCallback callback) {
302 DCHECK(CalledOnValidThread());
303 // We need to wait the minimum waiting duration, and if we are in backoff,
304 // we need to check if we're past the next allowed time. If we are, we can
305 // proceed with the request. If not, we are required to return empty results
306 // (i.e. treat the page as safe).
307 if (clock_->Now() <= next_gethash_time_) {
308 if (gethash_error_count_) {
309 RecordGetHashResult(V4OperationResult::BACKOFF_ERROR);
310 } else {
311 RecordGetHashResult(V4OperationResult::MIN_WAIT_DURATION_ERROR);
312 }
313 std::vector<SBFullHashResult> full_hashes;
314 callback.Run(full_hashes, base::Time());
315 return;
316 }
317
318 std::string req_base64 = GetHashRequest(prefixes, platforms, threat_type);
319 GURL gethash_url;
320 net::HttpRequestHeaders headers;
321 GetHashUrlAndHeaders(req_base64, &gethash_url, &headers);
322
323 std::unique_ptr<net::URLFetcher> owned_fetcher = net::URLFetcher::Create(
324 url_fetcher_id_++, gethash_url, net::URLFetcher::GET, this);
325 net::URLFetcher* fetcher = owned_fetcher.get();
326 fetcher->SetExtraRequestHeaders(headers.ToString());
327 hash_requests_[fetcher] = std::make_pair(std::move(owned_fetcher), callback);
328
329 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE);
330 fetcher->SetRequestContext(request_context_getter_.get());
331 fetcher->Start();
332 }
333
334 void V4GetHashProtocolManager::GetFullHashesWithApis(
335 const std::vector<SBPrefix>& prefixes,
336 FullHashCallback callback) {
337 std::vector<PlatformType> platform = {CHROME_PLATFORM};
338 GetFullHashes(prefixes, platform, API_ABUSE, callback);
339 } 619 }
340 620
341 void V4GetHashProtocolManager::SetClockForTests( 621 void V4GetHashProtocolManager::SetClockForTests(
342 std::unique_ptr<base::Clock> clock) { 622 std::unique_ptr<base::Clock> clock) {
343 clock_ = std::move(clock); 623 clock_ = std::move(clock);
344 } 624 }
345 625
626 void V4GetHashProtocolManager::UpdateCache(
627 const std::vector<HashPrefix>& prefixes_requested,
628 const std::vector<FullHashInfo>& full_hash_infos,
629 const Time& negative_cache_expire) {
630 // If negative_cache_expire is null, don't cache the results since it's not
631 // clear till what time they should be considered valid.
632 if (negative_cache_expire.is_null()) {
633 return;
634 }
635
636 for (const HashPrefix& prefix : prefixes_requested) {
637 // Create or reset the cached result for this prefix.
638 CachedHashPrefixInfo& chpi = full_hash_cache_[prefix];
639 chpi.full_hash_infos.clear();
640 chpi.negative_expiry = negative_cache_expire;
641
642 for (const FullHashInfo& full_hash_info : full_hash_infos) {
643 if (V4ProtocolManagerUtil::FullHashMatchesHashPrefix(
644 full_hash_info.full_hash, prefix)) {
645 chpi.full_hash_infos.push_back(full_hash_info);
646 }
647 }
648 }
649 }
650
651 void V4GetHashProtocolManager::MergeResults(
652 const FullHashToStoreAndHashPrefixesMap&
653 full_hash_to_store_and_hash_prefixes,
654 const std::vector<FullHashInfo>& full_hash_infos,
655 std::vector<FullHashInfo>* merged_full_hash_infos) {
656 bool get_hash_hit = false;
657 for (const FullHashInfo& fhi : full_hash_infos) {
658 auto it = full_hash_to_store_and_hash_prefixes.find(fhi.full_hash);
659 if (full_hash_to_store_and_hash_prefixes.end() != it) {
660 for (const StoreAndHashPrefix& sahp : it->second) {
661 if (fhi.list_id == sahp.list_id) {
662 merged_full_hash_infos->push_back(fhi);
663 get_hash_hit = true;
664 break;
665 }
666 }
667 }
668 }
669
670 if (get_hash_hit) {
671 RecordV4GetHashCheckResult(GET_HASH_CHECK_HIT);
672 } else if (full_hash_infos.empty()) {
673 RecordV4GetHashCheckResult(GET_HASH_CHECK_EMPTY);
674 } else {
675 RecordV4GetHashCheckResult(GET_HASH_CHECK_MISS);
676 }
677 }
678
346 // net::URLFetcherDelegate implementation ---------------------------------- 679 // net::URLFetcherDelegate implementation ----------------------------------
347 680
348 // SafeBrowsing request responses are handled here. 681 // SafeBrowsing request responses are handled here.
349 void V4GetHashProtocolManager::OnURLFetchComplete( 682 void V4GetHashProtocolManager::OnURLFetchComplete(
350 const net::URLFetcher* source) { 683 const net::URLFetcher* source) {
351 DCHECK(CalledOnValidThread()); 684 DCHECK(CalledOnValidThread());
352 685
353 HashRequests::iterator it = hash_requests_.find(source); 686 PendingHashRequests::iterator it = pending_hash_requests_.find(source);
354 DCHECK(it != hash_requests_.end()) << "Request not found"; 687 DCHECK(it != pending_hash_requests_.end()) << "Request not found";
355 688
356 int response_code = source->GetResponseCode(); 689 int response_code = source->GetResponseCode();
357 net::URLRequestStatus status = source->GetStatus(); 690 net::URLRequestStatus status = source->GetStatus();
358 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( 691 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode(
359 kUmaV4HashResponseMetricName, status, response_code); 692 kUmaV4HashResponseMetricName, status, response_code);
360 693
361 const FullHashCallback& callback = it->second.second; 694 std::vector<FullHashInfo> full_hash_infos;
362 std::vector<SBFullHashResult> full_hashes; 695 Time negative_cache_expire;
363 base::Time negative_cache_expire;
364 if (status.is_success() && response_code == net::HTTP_OK) { 696 if (status.is_success() && response_code == net::HTTP_OK) {
365 RecordGetHashResult(V4OperationResult::STATUS_200); 697 RecordGetHashResult(V4OperationResult::STATUS_200);
366 ResetGetHashErrors(); 698 ResetGetHashErrors();
367 std::string data; 699 std::string data;
368 source->GetResponseAsString(&data); 700 source->GetResponseAsString(&data);
369 if (!ParseHashResponse(data, &full_hashes, &negative_cache_expire)) { 701 if (!ParseHashResponse(data, &full_hash_infos, &negative_cache_expire)) {
370 full_hashes.clear(); 702 full_hash_infos.clear();
371 RecordGetHashResult(V4OperationResult::PARSE_ERROR); 703 RecordGetHashResult(V4OperationResult::PARSE_ERROR);
372 } 704 }
373 } else { 705 } else {
374 HandleGetHashError(clock_->Now()); 706 HandleGetHashError(clock_->Now());
375 707
376 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: " 708 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: "
377 << source->GetURL() << " failed with error: " << status.error() 709 << source->GetURL() << " failed with error: " << status.error()
378 << " and response code: " << response_code; 710 << " and response code: " << response_code;
379 711
380 if (status.status() == net::URLRequestStatus::FAILED) { 712 if (status.status() == net::URLRequestStatus::FAILED) {
381 RecordGetHashResult(V4OperationResult::NETWORK_ERROR); 713 RecordGetHashResult(V4OperationResult::NETWORK_ERROR);
382 } else { 714 } else {
383 RecordGetHashResult(V4OperationResult::HTTP_ERROR); 715 RecordGetHashResult(V4OperationResult::HTTP_ERROR);
384 } 716 }
385 } 717 }
386 718
387 // Invoke the callback with full_hashes, even if there was a parse error or 719 const std::unique_ptr<FullHashCallbackInfo>& fhci = it->second;
388 // an error response code (in which case full_hashes will be empty). The 720 UpdateCache(fhci->prefixes_requested, full_hash_infos, negative_cache_expire);
389 // caller can't be blocked indefinitely. 721 MergeResults(fhci->full_hash_to_store_and_hash_prefixes, full_hash_infos,
390 callback.Run(full_hashes, negative_cache_expire); 722 &fhci->cached_full_hash_infos);
391 723
392 hash_requests_.erase(it); 724 fhci->callback.Run(fhci->cached_full_hash_infos);
725
726 pending_hash_requests_.erase(it);
393 } 727 }
394 728
395 void V4GetHashProtocolManager::HandleGetHashError(const Time& now) { 729 #ifndef DEBUG
396 DCHECK(CalledOnValidThread()); 730 std::ostream& operator<<(std::ostream& os, const FullHashInfo& fhi) {
397 base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( 731 os << "{full_hash: " << fhi.full_hash << "; list_id: " << fhi.list_id
398 &gethash_error_count_, &gethash_back_off_mult_); 732 << "; positive_expiry: " << fhi.positive_expiry
399 next_gethash_time_ = now + next; 733 << "; metadata.api_permissions.size(): "
734 << fhi.metadata.api_permissions.size() << "}";
735 return os;
400 } 736 }
401 737 #endif
402 void V4GetHashProtocolManager::GetHashUrlAndHeaders(
403 const std::string& req_base64,
404 GURL* gurl,
405 net::HttpRequestHeaders* headers) const {
406 V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find",
407 config_, gurl, headers);
408 }
409 738
410 } // namespace safe_browsing 739 } // namespace safe_browsing
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698