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

Side by Side Diff: chrome/browser/safe_browsing/v4_protocol_manager.cc

Issue 1658913003: SafeBrowsing: Move V4ProtocolManager to components. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@sb-v4-3
Patch Set: Add build dependency Created 4 years, 10 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
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/safe_browsing/v4_protocol_manager.h"
6
7 #include <utility>
8
9 #include "base/base64.h"
10 #include "base/logging.h"
11 #include "base/macros.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/metrics/sparse_histogram.h"
14 #include "base/rand_util.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/timer/timer.h"
19 #include "net/base/escape.h"
20 #include "net/base/load_flags.h"
21 #include "net/base/net_errors.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/http/http_status_code.h"
24 #include "net/url_request/url_fetcher.h"
25 #include "net/url_request/url_request_context_getter.h"
26 #include "net/url_request/url_request_status.h"
27
28 using base::Time;
29 using base::TimeDelta;
30
31 namespace {
32
33 // Enumerate parsing failures for histogramming purposes. DO NOT CHANGE
34 // THE ORDERING OF THESE VALUES.
35 enum ParseResultType {
36 // Error parsing the protocol buffer from a string.
37 PARSE_FROM_STRING_ERROR = 0,
38
39 // A match in the response had an unexpected THREAT_ENTRY_TYPE.
40 UNEXPECTED_THREAT_ENTRY_TYPE_ERROR = 1,
41
42 // A match in the response had an unexpected THREAT_TYPE.
43 UNEXPECTED_THREAT_TYPE_ERROR = 2,
44
45 // A match in the response had an unexpected PLATFORM_TYPE.
46 UNEXPECTED_PLATFORM_TYPE_ERROR = 3,
47
48 // A match in the response contained no metadata where metadata was
49 // expected.
50 NO_METADATA_ERROR = 4,
51
52 // A match in the response contained a ThreatType that was inconsistent
53 // with the other matches.
54 INCONSISTENT_THREAT_TYPE_ERROR = 5,
55
56 // Memory space for histograms is determined by the max. ALWAYS
57 // ADD NEW VALUES BEFORE THIS ONE.
58 PARSE_GET_HASH_RESULT_MAX = 6
59 };
60
61 // Record parsing errors of a GetHash result.
62 void RecordParseGetHashResult(ParseResultType result_type) {
63 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type,
64 PARSE_GET_HASH_RESULT_MAX);
65 }
66
67 } // namespace
68
69 namespace safe_browsing {
70
71 const char kUmaV4ResponseMetricName[] =
72 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode";
73
74 // The URL prefix where browser fetches hashes from the server.
75 const char kSbV4UrlPrefix[] = "https://safebrowsing.googleapis.com/v4";
76
77 // The default SBProtocolManagerFactory.
78 class V4ProtocolManagerFactoryImpl : public V4ProtocolManagerFactory {
79 public:
80 V4ProtocolManagerFactoryImpl() {}
81 ~V4ProtocolManagerFactoryImpl() override {}
82 V4ProtocolManager* CreateProtocolManager(
83 net::URLRequestContextGetter* request_context_getter,
84 const V4ProtocolConfig& config) override {
85 return new V4ProtocolManager(request_context_getter, config);
86 }
87
88 private:
89 DISALLOW_COPY_AND_ASSIGN(V4ProtocolManagerFactoryImpl);
90 };
91
92 // V4ProtocolManager implementation --------------------------------
93
94 // static
95 V4ProtocolManagerFactory* V4ProtocolManager::factory_ = NULL;
96
97 // static
98 V4ProtocolManager* V4ProtocolManager::Create(
99 net::URLRequestContextGetter* request_context_getter,
100 const V4ProtocolConfig& config) {
101 if (!factory_)
102 factory_ = new V4ProtocolManagerFactoryImpl();
103 return factory_->CreateProtocolManager(request_context_getter, config);
104 }
105
106 // static
107 // Backoff interval is MIN(((2^(n-1))*15 minutes) * (RAND + 1), 24 hours) where
108 // n is the number of consecutive errors.
109 base::TimeDelta V4ProtocolManager::GetNextBackOffInterval(size_t* error_count,
110 size_t* multiplier) {
111 DCHECK(multiplier && error_count);
112 (*error_count)++;
113 if (*error_count > 1 && *error_count < 9) {
114 // With error count 9 and above we will hit the 24 hour max interval.
115 // Cap the multiplier here to prevent integer overflow errors.
116 *multiplier *= 2;
117 }
118 base::TimeDelta next =
119 base::TimeDelta::FromMinutes(*multiplier * (1 + base::RandDouble()) * 15);
120
121 base::TimeDelta day = base::TimeDelta::FromHours(24);
122
123 if (next < day)
124 return next;
125 else
126 return day;
127 }
128
129 void V4ProtocolManager::ResetGetHashErrors() {
130 gethash_error_count_ = 0;
131 gethash_back_off_mult_ = 1;
132 }
133
134 V4ProtocolManager::V4ProtocolManager(
135 net::URLRequestContextGetter* request_context_getter,
136 const V4ProtocolConfig& config)
137 : gethash_error_count_(0),
138 gethash_back_off_mult_(1),
139 next_gethash_time_(Time::FromDoubleT(0)),
140 version_(config.version),
141 client_name_(config.client_name),
142 key_param_(config.key_param),
143 request_context_getter_(request_context_getter),
144 url_fetcher_id_(0) {
145 DCHECK(!version_.empty());
146 }
147
148 // static
149 void V4ProtocolManager::RecordGetHashResult(ResultType result_type) {
150 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.GetV4HashResult", result_type,
151 GET_HASH_RESULT_MAX);
152 }
153
154 void V4ProtocolManager::RecordHttpResponseOrErrorCode(
155 const char* metric_name,
156 const net::URLRequestStatus& status,
157 int response_code) {
158 UMA_HISTOGRAM_SPARSE_SLOWLY(
159 metric_name, status.is_success() ? response_code : status.error());
160 }
161
162 V4ProtocolManager::~V4ProtocolManager() {
163 // Delete in-progress SafeBrowsing requests.
164 STLDeleteContainerPairFirstPointers(hash_requests_.begin(),
165 hash_requests_.end());
166 hash_requests_.clear();
167 }
168
169 std::string V4ProtocolManager::GetHashRequest(
170 const std::vector<SBPrefix>& prefixes,
171 const std::vector<PlatformType>& platforms,
172 ThreatType threat_type) {
173 // Build the request. Client info and client states are not added to the
174 // request protocol buffer. Client info is passed as params in the url.
175 FindFullHashesRequest req;
176 ThreatInfo* info = req.mutable_threat_info();
177 info->add_threat_types(threat_type);
178 info->add_threat_entry_types(URL_EXPRESSION);
179 for (const PlatformType p : platforms) {
180 info->add_platform_types(p);
181 }
182 for (const SBPrefix& prefix : prefixes) {
183 std::string hash(reinterpret_cast<const char*>(&prefix), sizeof(SBPrefix));
184 info->add_threat_entries()->set_hash(hash);
185 }
186
187 // Serialize and Base64 encode.
188 std::string req_data, req_base64;
189 req.SerializeToString(&req_data);
190 base::Base64Encode(req_data, &req_base64);
191
192 return req_base64;
193 }
194
195 bool V4ProtocolManager::ParseHashResponse(
196 const std::string& data,
197 std::vector<SBFullHashResult>* full_hashes,
198 base::TimeDelta* negative_cache_duration) {
199 FindFullHashesResponse response;
200
201 if (!response.ParseFromString(data)) {
202 RecordParseGetHashResult(PARSE_FROM_STRING_ERROR);
203 return false;
204 }
205
206 if (response.has_negative_cache_duration()) {
207 // Seconds resolution is good enough so we ignore the nanos field.
208 *negative_cache_duration = base::TimeDelta::FromSeconds(
209 response.negative_cache_duration().seconds());
210 }
211
212 if (response.has_minimum_wait_duration()) {
213 // Seconds resolution is good enough so we ignore the nanos field.
214 next_gethash_time_ =
215 Time::Now() + base::TimeDelta::FromSeconds(
216 response.minimum_wait_duration().seconds());
217 }
218
219 // We only expect one threat type per request, so we make sure
220 // the threat types are consistent between matches.
221 ThreatType expected_threat_type = THREAT_TYPE_UNSPECIFIED;
222
223 // Loop over the threat matches and fill in full_hashes.
224 for (const ThreatMatch& match : response.matches()) {
225 // Make sure the platform and threat entry type match.
226 if (!(match.has_threat_entry_type() &&
227 match.threat_entry_type() == URL_EXPRESSION && match.has_threat())) {
228 RecordParseGetHashResult(UNEXPECTED_THREAT_ENTRY_TYPE_ERROR);
229 return false;
230 }
231
232 if (!match.has_threat_type()) {
233 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR);
234 return false;
235 }
236
237 if (expected_threat_type == THREAT_TYPE_UNSPECIFIED) {
238 expected_threat_type = match.threat_type();
239 } else if (match.threat_type() != expected_threat_type) {
240 RecordParseGetHashResult(INCONSISTENT_THREAT_TYPE_ERROR);
241 return false;
242 }
243
244 // Fill in the full hash.
245 SBFullHashResult result;
246 result.hash = StringToSBFullHash(match.threat().hash());
247
248 if (match.has_cache_duration()) {
249 // Seconds resolution is good enough so we ignore the nanos field.
250 result.cache_duration =
251 base::TimeDelta::FromSeconds(match.cache_duration().seconds());
252 }
253
254 // Different threat types will handle the metadata differently.
255 if (match.threat_type() == API_ABUSE) {
256 if (match.has_platform_type() &&
257 match.platform_type() == CHROME_PLATFORM) {
258 if (match.has_threat_entry_metadata()) {
259 // For API Abuse, store a csv of the returned permissions.
260 for (const ThreatEntryMetadata::MetadataEntry& m :
261 match.threat_entry_metadata().entries()) {
262 if (m.key() == "permission") {
263 result.metadata += m.value() + ",";
264 }
265 }
266 } else {
267 RecordParseGetHashResult(NO_METADATA_ERROR);
268 return false;
269 }
270 } else {
271 RecordParseGetHashResult(UNEXPECTED_PLATFORM_TYPE_ERROR);
272 return false;
273 }
274 } else {
275 RecordParseGetHashResult(UNEXPECTED_THREAT_TYPE_ERROR);
276 return false;
277 }
278
279 full_hashes->push_back(result);
280 }
281 return true;
282 }
283
284 void V4ProtocolManager::GetFullHashes(
285 const std::vector<SBPrefix>& prefixes,
286 const std::vector<PlatformType>& platforms,
287 ThreatType threat_type,
288 FullHashCallback callback) {
289 DCHECK(CalledOnValidThread());
290 // We need to wait the minimum waiting duration, and if we are in backoff,
291 // we need to check if we're past the next allowed time. If we are, we can
292 // proceed with the request. If not, we are required to return empty results
293 // (i.e. treat the page as safe).
294 if (Time::Now() <= next_gethash_time_) {
295 if (gethash_error_count_) {
296 RecordGetHashResult(GET_HASH_BACKOFF_ERROR);
297 } else {
298 RecordGetHashResult(GET_HASH_MIN_WAIT_DURATION_ERROR);
299 }
300 std::vector<SBFullHashResult> full_hashes;
301 callback.Run(full_hashes, base::TimeDelta());
302 return;
303 }
304
305 std::string req_base64 = GetHashRequest(prefixes, platforms, threat_type);
306 GURL gethash_url = GetHashUrl(req_base64);
307
308 net::URLFetcher* fetcher =
309 net::URLFetcher::Create(url_fetcher_id_++, gethash_url,
310 net::URLFetcher::GET, this)
311 .release();
312 hash_requests_[fetcher] = callback;
313
314 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE);
315 fetcher->SetRequestContext(request_context_getter_.get());
316 fetcher->Start();
317 }
318
319 void V4ProtocolManager::GetFullHashesWithApis(
320 const std::vector<SBPrefix>& prefixes,
321 FullHashCallback callback) {
322 std::vector<PlatformType> platform = {CHROME_PLATFORM};
323 GetFullHashes(prefixes, platform, API_ABUSE, callback);
324 }
325
326 // net::URLFetcherDelegate implementation ----------------------------------
327
328 // SafeBrowsing request responses are handled here.
329 void V4ProtocolManager::OnURLFetchComplete(const net::URLFetcher* source) {
330 DCHECK(CalledOnValidThread());
331
332 HashRequests::iterator it = hash_requests_.find(source);
333 DCHECK(it != hash_requests_.end()) << "Request not found";
334
335 // FindFullHashes response.
336 // Reset the scoped pointer so the fetcher gets destroyed properly.
337 scoped_ptr<const net::URLFetcher> fetcher(it->first);
338
339 int response_code = source->GetResponseCode();
340 net::URLRequestStatus status = source->GetStatus();
341 RecordHttpResponseOrErrorCode(kUmaV4ResponseMetricName, status,
342 response_code);
343
344 const FullHashCallback& callback = it->second;
345 std::vector<SBFullHashResult> full_hashes;
346 base::TimeDelta negative_cache_duration;
347 if (status.is_success() && response_code == net::HTTP_OK) {
348 RecordGetHashResult(GET_HASH_STATUS_200);
349 ResetGetHashErrors();
350 std::string data;
351 source->GetResponseAsString(&data);
352 if (!ParseHashResponse(data, &full_hashes, &negative_cache_duration)) {
353 full_hashes.clear();
354 RecordGetHashResult(GET_HASH_PARSE_ERROR);
355 }
356 } else {
357 HandleGetHashError(Time::Now());
358
359 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: "
360 << source->GetURL() << " failed with error: " << status.error()
361 << " and response code: " << response_code;
362
363 if (status.status() == net::URLRequestStatus::FAILED) {
364 RecordGetHashResult(GET_HASH_NETWORK_ERROR);
365 } else {
366 RecordGetHashResult(GET_HASH_HTTP_ERROR);
367 }
368 }
369
370 // Invoke the callback with full_hashes, even if there was a parse error or
371 // an error response code (in which case full_hashes will be empty). The
372 // caller can't be blocked indefinitely.
373 callback.Run(full_hashes, negative_cache_duration);
374
375 hash_requests_.erase(it);
376 }
377
378 void V4ProtocolManager::HandleGetHashError(const Time& now) {
379 DCHECK(CalledOnValidThread());
380 base::TimeDelta next = GetNextBackOffInterval(&gethash_error_count_,
381 &gethash_back_off_mult_);
382 next_gethash_time_ = now + next;
383 }
384
385 // The API hash call uses the pver4 Safe Browsing server.
386 GURL V4ProtocolManager::GetHashUrl(const std::string& request_base64) const {
387 std::string url = ComposePver4Url(kSbV4UrlPrefix, "encodedFullHashes",
388 request_base64, client_name_, version_, key_param_);
389 return GURL(url);
390 }
391
392 // static
393 std::string V4ProtocolManager::ComposePver4Url(const std::string& prefix,
394 const std::string& method,
395 const std::string& request_base64,
396 const std::string& client_id,
397 const std::string& version,
398 const std::string& key_param) {
399 DCHECK(!prefix.empty() && !method.empty() &&
400 !client_id.empty() && !version.empty());
401 std::string url = base::StringPrintf(
402 "%s/%s/%s?alt=proto&client_id=%s&client_version=%s",
403 prefix.c_str(), method.c_str(), request_base64.c_str(),
404 client_id.c_str(), version.c_str());
405 if (!key_param.empty()) {
406 base::StringAppendF(&url, "&key=%s",
407 net::EscapeQueryParamValue(key_param, true).c_str());
408 }
409 return url;
410 }
411
412 } // namespace safe_browsing
OLDNEW
« no previous file with comments | « chrome/browser/safe_browsing/v4_protocol_manager.h ('k') | chrome/browser/safe_browsing/v4_protocol_manager_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698