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

Side by Side Diff: components/safe_browsing/base_safe_browsing_resource_throttle.cc

Issue 2623733002: Componentize SafeBrowsingBlockingPage for WebView use (Closed)
Patch Set: address final comments Created 3 years, 11 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 (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 }
OLDNEW
« no previous file with comments | « components/safe_browsing/base_safe_browsing_resource_throttle.h ('k') | components/safe_browsing/base_ui_manager.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698