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