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. | 38 // This cancels all in-flight GetHash requests. |
92 if (v4_get_hash_protocol_manager_) { | 39 v4_get_hash_protocol_manager_.reset(); |
93 delete v4_get_hash_protocol_manager_; | |
94 v4_get_hash_protocol_manager_ = NULL; | |
95 } | |
96 | 40 |
97 // Delete pending checks, calling back any clients with empty metadata. | 41 // Delete pending checks, calling back any clients with empty metadata. |
98 for (auto* check : api_checks_) { | 42 for (const SafeBrowsingApiCheck* check : api_checks_) { |
99 if (check->client()) { | 43 if (check->client()) { |
100 check->client()-> | 44 check->client()-> |
101 OnCheckApiBlacklistUrlResult(check->url(), ThreatMetadata()); | 45 OnCheckApiBlacklistUrlResult(check->url(), ThreatMetadata()); |
102 } | 46 } |
103 } | 47 } |
104 base::STLDeleteElements(&api_checks_); | |
105 } | 48 } |
106 | 49 |
107 SafeBrowsingDatabaseManager::ApiCheckSet::iterator | 50 SafeBrowsingDatabaseManager::ApiCheckSet::iterator |
108 SafeBrowsingDatabaseManager::FindClientApiCheck(Client* client) { | 51 SafeBrowsingDatabaseManager::FindClientApiCheck(Client* client) { |
109 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 52 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
110 for (ApiCheckSet::iterator it = api_checks_.begin(); | 53 for (ApiCheckSet::iterator it = api_checks_.begin(); |
111 it != api_checks_.end(); ++it) { | 54 it != api_checks_.end(); ++it) { |
112 if ((*it)->client() == client) { | 55 if ((*it)->client() == client) { |
113 return it; | 56 return it; |
114 } | 57 } |
115 } | 58 } |
116 return api_checks_.end(); | 59 return api_checks_.end(); |
117 } | 60 } |
118 | 61 |
119 bool SafeBrowsingDatabaseManager::CancelApiCheck(Client* client) { | 62 bool SafeBrowsingDatabaseManager::CancelApiCheck(Client* client) { |
120 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 63 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
121 ApiCheckSet::iterator it = FindClientApiCheck(client); | 64 ApiCheckSet::iterator it = FindClientApiCheck(client); |
122 if (it != api_checks_.end()) { | 65 if (it != api_checks_.end()) { |
123 delete *it; | |
124 api_checks_.erase(it); | 66 api_checks_.erase(it); |
125 return true; | 67 return true; |
126 } | 68 } |
127 NOTREACHED(); | 69 NOTREACHED(); |
128 return false; | 70 return false; |
129 } | 71 } |
130 | 72 |
131 bool SafeBrowsingDatabaseManager::CheckApiBlacklistUrl(const GURL& url, | 73 bool SafeBrowsingDatabaseManager::CheckApiBlacklistUrl(const GURL& url, |
132 Client* client) { | 74 Client* client) { |
133 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 75 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
134 DCHECK(v4_get_hash_protocol_manager_); | 76 DCHECK(v4_get_hash_protocol_manager_); |
135 | 77 |
136 // Make sure we can check this url. | 78 // Make sure we can check this url. |
137 if (!(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme))) { | 79 if (!(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme))) { |
138 return true; | 80 return true; |
139 } | 81 } |
140 | 82 |
141 // There can only be one in-progress check for the same client at a time. | 83 // There can only be one in-progress check for the same client at a time. |
142 DCHECK(FindClientApiCheck(client) == api_checks_.end()); | 84 DCHECK(FindClientApiCheck(client) == api_checks_.end()); |
143 | 85 |
144 // Compute a list of hashes for this url. | 86 std::unique_ptr<SafeBrowsingApiCheck> check( |
145 std::vector<SBFullHash> full_hashes; | 87 new SafeBrowsingApiCheck(url, client)); |
146 UrlToFullHashes(url, false, &full_hashes); | 88 api_checks_.insert(check.get()); |
147 if (full_hashes.empty()) | 89 v4_get_hash_protocol_manager_->GetFullHashesWithApis( |
148 return true; | 90 url, base::Bind(&SafeBrowsingDatabaseManager::OnThreatMetadataResponse, |
149 | 91 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 | 92 |
182 return false; | 93 return false; |
183 } | 94 } |
184 | 95 |
185 void SafeBrowsingDatabaseManager::GetFullHashCachedResults( | 96 void SafeBrowsingDatabaseManager::OnThreatMetadataResponse( |
186 const SBThreatType& threat_type, | 97 std::unique_ptr<SafeBrowsingApiCheck> check, |
187 const std::vector<SBFullHash>& full_hashes, | 98 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); | 99 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
291 DCHECK(check); | 100 DCHECK(check); |
292 | 101 |
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 | 102 // If the check is not in |api_checks_| then the request was cancelled by the |
316 // client. | 103 // client. |
317 ApiCheckSet::iterator it = api_checks_.find(check); | 104 ApiCheckSet::iterator it = api_checks_.find(check.get()); |
318 if (it == api_checks_.end()) | 105 if (it == api_checks_.end()) |
319 return; | 106 return; |
320 | 107 |
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); | 108 check->client()->OnCheckApiBlacklistUrlResult(check->url(), md); |
340 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 } | 109 } |
364 | 110 |
365 SafeBrowsingDatabaseManager::SafeBrowsingApiCheck::SafeBrowsingApiCheck( | 111 SafeBrowsingDatabaseManager::SafeBrowsingApiCheck::SafeBrowsingApiCheck( |
366 const GURL& url, | 112 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) | 113 Client* client) |
371 : url_(url), prefixes_(prefixes), full_hashes_(full_hashes), | 114 : url_(url), client_(client) {} |
372 cached_results_(cached_results), client_(client) { | |
373 } | |
374 | 115 |
375 SafeBrowsingDatabaseManager::SafeBrowsingApiCheck::~SafeBrowsingApiCheck() { | 116 SafeBrowsingDatabaseManager::SafeBrowsingApiCheck::~SafeBrowsingApiCheck() { |
376 } | 117 } |
377 | 118 |
378 } // namespace safe_browsing | 119 } // namespace safe_browsing |
OLD | NEW |