OLD | NEW |
| (Empty) |
1 // Copyright (c) 2017 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 "components/safe_browsing/base_safe_browsing_resource_throttle.h" | |
6 | |
7 #include <iterator> | |
8 #include <utility> | |
9 | |
10 #include "base/debug/alias.h" | |
11 #include "base/logging.h" | |
12 #include "base/metrics/histogram_macros.h" | |
13 #include "base/trace_event/trace_event.h" | |
14 #include "base/values.h" | |
15 #include "components/safe_browsing/base_ui_manager.h" | |
16 #include "components/safe_browsing_db/util.h" | |
17 #include "components/security_interstitials/content/unsafe_resource.h" | |
18 #include "components/subresource_filter/content/browser/content_subresource_filt
er_driver_factory.h" | |
19 #include "content/public/browser/browser_thread.h" | |
20 #include "content/public/browser/resource_request_info.h" | |
21 #include "content/public/browser/web_contents.h" | |
22 #include "net/base/load_flags.h" | |
23 #include "net/log/net_log_capture_mode.h" | |
24 #include "net/log/net_log_source.h" | |
25 #include "net/log/net_log_source_type.h" | |
26 #include "net/url_request/redirect_info.h" | |
27 #include "net/url_request/url_request.h" | |
28 | |
29 using net::NetLogEventType; | |
30 using net::NetLogSourceType; | |
31 using safe_browsing::BaseSafeBrowsingUIManager; | |
32 | |
33 namespace { | |
34 | |
35 // Maximum time in milliseconds to wait for the safe browsing service to | |
36 // verify a URL. After this amount of time the outstanding check will be | |
37 // aborted, and the URL will be treated as if it were safe. | |
38 const int kCheckUrlTimeoutMs = 5000; | |
39 | |
40 // Return a dictionary with "url"=|url-spec| and optionally | |
41 // |name|=|value| (if not null), for netlogging. | |
42 // This will also add a reference to the original request's net_log ID. | |
43 std::unique_ptr<base::Value> NetLogUrlCallback( | |
44 const net::URLRequest* request, | |
45 const GURL& url, | |
46 const char* name, | |
47 const char* value, | |
48 net::NetLogCaptureMode /* capture_mode */) { | |
49 std::unique_ptr<base::DictionaryValue> event_params( | |
50 new base::DictionaryValue()); | |
51 event_params->SetString("url", url.spec()); | |
52 if (name && value) | |
53 event_params->SetString(name, value); | |
54 request->net_log().source().AddToEventParameters(event_params.get()); | |
55 return std::move(event_params); | |
56 } | |
57 | |
58 // Return a dictionary with |name|=|value|, for netlogging. | |
59 std::unique_ptr<base::Value> NetLogStringCallback(const char* name, | |
60 const char* value, | |
61 net::NetLogCaptureMode) { | |
62 std::unique_ptr<base::DictionaryValue> event_params( | |
63 new base::DictionaryValue()); | |
64 if (name && value) | |
65 event_params->SetString(name, value); | |
66 return std::move(event_params); | |
67 } | |
68 | |
69 } // namespace | |
70 | |
71 // TODO(eroman): Downgrade these CHECK()s to DCHECKs once there is more | |
72 // unit test coverage. | |
73 | |
74 BaseSafeBrowsingResourceThrottle::BaseSafeBrowsingResourceThrottle( | |
75 const net::URLRequest* request, | |
76 content::ResourceType resource_type, | |
77 scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager, | |
78 scoped_refptr<safe_browsing::BaseSafeBrowsingUIManager> ui_manager) | |
79 : ui_manager_(ui_manager), | |
80 threat_type_(safe_browsing::SB_THREAT_TYPE_SAFE), | |
81 database_manager_(database_manager), | |
82 request_(request), | |
83 state_(STATE_NONE), | |
84 defer_state_(DEFERRED_NONE), | |
85 resource_type_(resource_type), | |
86 net_log_with_source_( | |
87 net::NetLogWithSource::Make(request->net_log().net_log(), | |
88 NetLogSourceType::SAFE_BROWSING)) {} | |
89 | |
90 // static | |
91 BaseSafeBrowsingResourceThrottle* BaseSafeBrowsingResourceThrottle::MaybeCreate( | |
92 net::URLRequest* request, | |
93 content::ResourceType resource_type, | |
94 scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager, | |
95 scoped_refptr<safe_browsing::BaseSafeBrowsingUIManager> ui_manager) { | |
96 if (database_manager->IsSupported()) { | |
97 return new BaseSafeBrowsingResourceThrottle(request, resource_type, | |
98 database_manager, ui_manager); | |
99 } | |
100 return nullptr; | |
101 } | |
102 | |
103 BaseSafeBrowsingResourceThrottle::~BaseSafeBrowsingResourceThrottle() { | |
104 if (defer_state_ != DEFERRED_NONE) { | |
105 EndNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, nullptr, nullptr); | |
106 } | |
107 | |
108 if (state_ == STATE_CHECKING_URL) { | |
109 database_manager_->CancelCheck(this); | |
110 EndNetLogEvent(NetLogEventType::SAFE_BROWSING_CHECKING_URL, "result", | |
111 "request_canceled"); | |
112 } | |
113 } | |
114 | |
115 // Note on net_log calls: SAFE_BROWSING_DEFERRED events must be wholly | |
116 // nested within SAFE_BROWSING_CHECKING_URL events. Synchronous checks | |
117 // are not logged at all. | |
118 void BaseSafeBrowsingResourceThrottle::BeginNetLogEvent(NetLogEventType type, | |
119 const GURL& url, | |
120 const char* name, | |
121 const char* value) { | |
122 net_log_with_source_.BeginEvent( | |
123 type, base::Bind(&NetLogUrlCallback, request_, url, name, value)); | |
124 request_->net_log().AddEvent( | |
125 type, net_log_with_source_.source().ToEventParametersCallback()); | |
126 } | |
127 | |
128 void BaseSafeBrowsingResourceThrottle::EndNetLogEvent(NetLogEventType type, | |
129 const char* name, | |
130 const char* value) { | |
131 net_log_with_source_.EndEvent(type, | |
132 base::Bind(&NetLogStringCallback, name, value)); | |
133 request_->net_log().AddEvent( | |
134 type, net_log_with_source_.source().ToEventParametersCallback()); | |
135 } | |
136 | |
137 void BaseSafeBrowsingResourceThrottle::WillStartRequest(bool* defer) { | |
138 // We need to check the new URL before starting the request. | |
139 if (CheckUrl(request_->url())) | |
140 return; | |
141 | |
142 // We let the check run in parallel with resource load only if this | |
143 // db_manager only supports asynchronous checks, like on mobile. | |
144 // Otherwise, we defer now. | |
145 if (database_manager_->ChecksAreAlwaysAsync()) | |
146 return; | |
147 | |
148 // If the URL couldn't be verified synchronously, defer starting the | |
149 // request until the check has completed. | |
150 defer_state_ = DEFERRED_START; | |
151 defer_start_time_ = base::TimeTicks::Now(); | |
152 *defer = true; | |
153 BeginNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, request_->url(), | |
154 "defer_reason", "at_start"); | |
155 } | |
156 | |
157 void BaseSafeBrowsingResourceThrottle::WillProcessResponse(bool* defer) { | |
158 CHECK_EQ(defer_state_, DEFERRED_NONE); | |
159 // TODO(nparker): Maybe remove this check, since it should have no effect. | |
160 if (!database_manager_->ChecksAreAlwaysAsync()) | |
161 return; | |
162 | |
163 if (state_ == STATE_CHECKING_URL || | |
164 state_ == STATE_DISPLAYING_BLOCKING_PAGE) { | |
165 defer_state_ = DEFERRED_PROCESSING; | |
166 defer_start_time_ = base::TimeTicks::Now(); | |
167 *defer = true; | |
168 BeginNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, request_->url(), | |
169 "defer_reason", "at_response"); | |
170 } | |
171 } | |
172 | |
173 bool BaseSafeBrowsingResourceThrottle::MustProcessResponseBeforeReadingBody() { | |
174 // On Android, SafeBrowsing may only decide to cancel the request when the | |
175 // response has been received. Therefore, no part of it should be cached | |
176 // until this ResourceThrottle has been able to check the response. This | |
177 // prevents the following scenario: | |
178 // 1) A request is made for foo.com which has been hacked. | |
179 // 2) The request is only canceled at WillProcessResponse stage, but part of | |
180 // it has been cached. | |
181 // 3) foo.com is no longer hacked and removed from the SafeBrowsing list. | |
182 // 4) The user requests foo.com, which is not on the SafeBrowsing list. This | |
183 // is deemed safe. However, the resource is actually served from cache, | |
184 // using the version that was previously stored. | |
185 // 5) This results in the user accessing an unsafe resource without being | |
186 // notified that it's dangerous. | |
187 // TODO(clamy): Add a browser test that checks this specific scenario. | |
188 return true; | |
189 } | |
190 | |
191 void BaseSafeBrowsingResourceThrottle::WillRedirectRequest( | |
192 const net::RedirectInfo& redirect_info, | |
193 bool* defer) { | |
194 CHECK_EQ(defer_state_, DEFERRED_NONE); | |
195 | |
196 // Prev check completed and was safe. | |
197 if (state_ == STATE_NONE) { | |
198 // Save the redirect urls for possible malware detail reporting later. | |
199 redirect_urls_.push_back(redirect_info.new_url); | |
200 | |
201 // We need to check the new URL before following the redirect. | |
202 if (CheckUrl(redirect_info.new_url)) | |
203 return; | |
204 defer_state_ = DEFERRED_REDIRECT; | |
205 } else { | |
206 CHECK(state_ == STATE_CHECKING_URL || | |
207 state_ == STATE_DISPLAYING_BLOCKING_PAGE); | |
208 // We can't check this new URL until we have finished checking | |
209 // the prev one, or resumed from the blocking page. | |
210 unchecked_redirect_url_ = redirect_info.new_url; | |
211 defer_state_ = DEFERRED_UNCHECKED_REDIRECT; | |
212 } | |
213 | |
214 defer_start_time_ = base::TimeTicks::Now(); | |
215 *defer = true; | |
216 BeginNetLogEvent( | |
217 NetLogEventType::SAFE_BROWSING_DEFERRED, redirect_info.new_url, | |
218 "defer_reason", | |
219 defer_state_ == DEFERRED_REDIRECT ? "redirect" : "unchecked_redirect"); | |
220 } | |
221 | |
222 const char* BaseSafeBrowsingResourceThrottle::GetNameForLogging() const { | |
223 return "BaseSafeBrowsingResourceThrottle"; | |
224 } | |
225 | |
226 void BaseSafeBrowsingResourceThrottle::MaybeDestroyPrerenderContents( | |
227 const content::ResourceRequestInfo* info) {} | |
228 | |
229 // SafeBrowsingService::Client implementation, called on the IO thread once | |
230 // the URL has been classified. | |
231 void BaseSafeBrowsingResourceThrottle::OnCheckBrowseUrlResult( | |
232 const GURL& url, | |
233 safe_browsing::SBThreatType threat_type, | |
234 const safe_browsing::ThreatMetadata& metadata) { | |
235 CHECK_EQ(state_, STATE_CHECKING_URL); | |
236 // TODO(vakh): The following base::debug::Alias() and CHECK calls should be | |
237 // removed after http://crbug.com/660293 is fixed. | |
238 CHECK(url.is_valid()); | |
239 CHECK(url_being_checked_.is_valid()); | |
240 if (url != url_being_checked_) { | |
241 bool url_had_timed_out = timed_out_urls_.count(url) > 0; | |
242 char buf[1000]; | |
243 snprintf(buf, sizeof(buf), "sbtr::ocbur:%d:%s -- %s\n", url_had_timed_out, | |
244 url.spec().c_str(), url_being_checked_.spec().c_str()); | |
245 base::debug::Alias(buf); | |
246 CHECK(false) << "buf: " << buf; | |
247 } | |
248 | |
249 timer_.Stop(); // Cancel the timeout timer. | |
250 threat_type_ = threat_type; | |
251 state_ = STATE_NONE; | |
252 | |
253 if (defer_state_ != DEFERRED_NONE) { | |
254 EndNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, nullptr, nullptr); | |
255 } | |
256 EndNetLogEvent( | |
257 NetLogEventType::SAFE_BROWSING_CHECKING_URL, "result", | |
258 threat_type_ == safe_browsing::SB_THREAT_TYPE_SAFE ? "safe" : "unsafe"); | |
259 | |
260 if (threat_type == safe_browsing::SB_THREAT_TYPE_SAFE) { | |
261 if (defer_state_ != DEFERRED_NONE) { | |
262 // Log how much time the safe browsing check cost us. | |
263 ui_manager_->LogPauseDelay(base::TimeTicks::Now() - defer_start_time_); | |
264 ResumeRequest(); | |
265 } else { | |
266 ui_manager_->LogPauseDelay(base::TimeDelta()); | |
267 } | |
268 return; | |
269 } | |
270 | |
271 const content::ResourceRequestInfo* info = | |
272 content::ResourceRequestInfo::ForRequest(request_); | |
273 | |
274 if (request_->load_flags() & net::LOAD_PREFETCH) { | |
275 // Destroy the prefetch with FINAL_STATUS_SAFEBROSWING. | |
276 if (resource_type_ == content::RESOURCE_TYPE_MAIN_FRAME) { | |
277 MaybeDestroyPrerenderContents(info); | |
278 } | |
279 // Don't prefetch resources that fail safe browsing, disallow them. | |
280 Cancel(); | |
281 UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.UnsafePrefetchCanceled", | |
282 resource_type_, content::RESOURCE_TYPE_LAST_TYPE); | |
283 return; | |
284 } | |
285 | |
286 UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.Unsafe", resource_type_, | |
287 content::RESOURCE_TYPE_LAST_TYPE); | |
288 | |
289 security_interstitials::UnsafeResource resource; | |
290 resource.url = url; | |
291 resource.original_url = request_->original_url(); | |
292 resource.redirect_urls = redirect_urls_; | |
293 resource.is_subresource = resource_type_ != content::RESOURCE_TYPE_MAIN_FRAME; | |
294 resource.is_subframe = resource_type_ == content::RESOURCE_TYPE_SUB_FRAME; | |
295 resource.threat_type = threat_type; | |
296 resource.threat_metadata = metadata; | |
297 resource.callback = base::Bind( | |
298 &BaseSafeBrowsingResourceThrottle::OnBlockingPageComplete, AsWeakPtr()); | |
299 resource.callback_thread = content::BrowserThread::GetTaskRunnerForThread( | |
300 content::BrowserThread::IO); | |
301 resource.web_contents_getter = info->GetWebContentsGetterForRequest(); | |
302 resource.threat_source = database_manager_->GetThreatSource(); | |
303 | |
304 state_ = STATE_DISPLAYING_BLOCKING_PAGE; | |
305 | |
306 StartDisplayingBlockingPageHelper(resource); | |
307 } | |
308 | |
309 void BaseSafeBrowsingResourceThrottle::StartDisplayingBlockingPageHelper( | |
310 security_interstitials::UnsafeResource resource) { | |
311 content::BrowserThread::PostTask( | |
312 content::BrowserThread::UI, FROM_HERE, | |
313 base::Bind(&BaseSafeBrowsingResourceThrottle::StartDisplayingBlockingPage, | |
314 AsWeakPtr(), ui_manager_, resource)); | |
315 } | |
316 | |
317 // Static | |
318 void BaseSafeBrowsingResourceThrottle::StartDisplayingBlockingPage( | |
319 const base::WeakPtr<BaseSafeBrowsingResourceThrottle>& throttle, | |
320 scoped_refptr<BaseSafeBrowsingUIManager> ui_manager, | |
321 const security_interstitials::UnsafeResource& resource) { | |
322 content::WebContents* web_contents = resource.web_contents_getter.Run(); | |
323 if (web_contents) { | |
324 // Once activated, the subresource filter will filter subresources, but is | |
325 // triggered when the main frame document matches Safe Browsing blacklists. | |
326 if (!resource.is_subresource) { | |
327 using subresource_filter::ContentSubresourceFilterDriverFactory; | |
328 ContentSubresourceFilterDriverFactory* driver_factory = | |
329 ContentSubresourceFilterDriverFactory::FromWebContents(web_contents); | |
330 DCHECK(driver_factory); | |
331 | |
332 // For a redirect chain of A -> B -> C, the subresource filter expects C | |
333 // as the resource URL and [A, B] as redirect URLs. | |
334 std::vector<GURL> redirect_parent_urls; | |
335 if (!resource.redirect_urls.empty()) { | |
336 redirect_parent_urls.push_back(resource.original_url); | |
337 redirect_parent_urls.insert(redirect_parent_urls.end(), | |
338 resource.redirect_urls.begin(), | |
339 std::prev(resource.redirect_urls.end())); | |
340 } | |
341 | |
342 driver_factory->OnMainResourceMatchedSafeBrowsingBlacklist( | |
343 resource.url, redirect_parent_urls, resource.threat_type, | |
344 resource.threat_metadata.threat_pattern_type); | |
345 } | |
346 | |
347 ui_manager->DisplayBlockingPage(resource); | |
348 return; | |
349 } | |
350 | |
351 // Tab is gone or it's being prerendered. | |
352 content::BrowserThread::PostTask( | |
353 content::BrowserThread::IO, FROM_HERE, | |
354 base::Bind(&BaseSafeBrowsingResourceThrottle::Cancel, throttle)); | |
355 } | |
356 | |
357 void BaseSafeBrowsingResourceThrottle::OnBlockingPageComplete(bool proceed) { | |
358 CHECK_EQ(state_, STATE_DISPLAYING_BLOCKING_PAGE); | |
359 state_ = STATE_NONE; | |
360 | |
361 if (proceed) { | |
362 threat_type_ = safe_browsing::SB_THREAT_TYPE_SAFE; | |
363 if (defer_state_ != DEFERRED_NONE) { | |
364 ResumeRequest(); | |
365 } | |
366 } else { | |
367 Cancel(); | |
368 } | |
369 } | |
370 | |
371 bool BaseSafeBrowsingResourceThrottle::CheckUrl(const GURL& url) { | |
372 TRACE_EVENT1("loader", "BaseSafeBrowsingResourceThrottle::CheckUrl", "url", | |
373 url.spec()); | |
374 CHECK_EQ(state_, STATE_NONE); | |
375 // To reduce aggregate latency on mobile, check only the most dangerous | |
376 // resource types. | |
377 if (!database_manager_->CanCheckResourceType(resource_type_)) { | |
378 // TODO(vakh): Consider changing this metric to SafeBrowsing.V4ResourceType | |
379 // to be consistent with the other PVer4 metrics. | |
380 UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.Skipped", resource_type_, | |
381 content::RESOURCE_TYPE_LAST_TYPE); | |
382 return true; | |
383 } | |
384 | |
385 // TODO(vakh): Consider changing this metric to SafeBrowsing.V4ResourceType to | |
386 // be consistent with the other PVer4 metrics. | |
387 UMA_HISTOGRAM_ENUMERATION("SB2.ResourceTypes2.Checked", resource_type_, | |
388 content::RESOURCE_TYPE_LAST_TYPE); | |
389 | |
390 if (database_manager_->CheckBrowseUrl(url, this)) { | |
391 threat_type_ = safe_browsing::SB_THREAT_TYPE_SAFE; | |
392 ui_manager_->LogPauseDelay(base::TimeDelta()); // No delay. | |
393 return true; | |
394 } | |
395 | |
396 state_ = STATE_CHECKING_URL; | |
397 url_being_checked_ = url; | |
398 BeginNetLogEvent(NetLogEventType::SAFE_BROWSING_CHECKING_URL, url, nullptr, | |
399 nullptr); | |
400 | |
401 // Start a timer to abort the check if it takes too long. | |
402 // TODO(nparker): Set this only when we defer, based on remaining time, | |
403 // so we don't cancel earlier than necessary. | |
404 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCheckUrlTimeoutMs), | |
405 this, &BaseSafeBrowsingResourceThrottle::OnCheckUrlTimeout); | |
406 | |
407 return false; | |
408 } | |
409 | |
410 void BaseSafeBrowsingResourceThrottle::OnCheckUrlTimeout() { | |
411 CHECK_EQ(state_, STATE_CHECKING_URL); | |
412 | |
413 database_manager_->CancelCheck(this); | |
414 | |
415 OnCheckBrowseUrlResult(url_being_checked_, safe_browsing::SB_THREAT_TYPE_SAFE, | |
416 safe_browsing::ThreatMetadata()); | |
417 | |
418 timed_out_urls_.insert(url_being_checked_); | |
419 } | |
420 | |
421 void BaseSafeBrowsingResourceThrottle::ResumeRequest() { | |
422 CHECK_EQ(state_, STATE_NONE); | |
423 CHECK_NE(defer_state_, DEFERRED_NONE); | |
424 | |
425 bool resume = true; | |
426 if (defer_state_ == DEFERRED_UNCHECKED_REDIRECT) { | |
427 // Save the redirect urls for possible malware detail reporting later. | |
428 redirect_urls_.push_back(unchecked_redirect_url_); | |
429 if (!CheckUrl(unchecked_redirect_url_)) { | |
430 // We're now waiting for the unchecked_redirect_url_. | |
431 defer_state_ = DEFERRED_REDIRECT; | |
432 resume = false; | |
433 BeginNetLogEvent(NetLogEventType::SAFE_BROWSING_DEFERRED, | |
434 unchecked_redirect_url_, "defer_reason", | |
435 "resumed_redirect"); | |
436 } | |
437 } | |
438 | |
439 if (resume) { | |
440 defer_state_ = DEFERRED_NONE; | |
441 Resume(); | |
442 } | |
443 } | |
OLD | NEW |