| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 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/incident_reporting_service.h" | |
| 6 | |
| 7 #include <math.h> | |
| 8 | |
| 9 #include <algorithm> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/metrics/histogram.h" | |
| 13 #include "base/prefs/pref_service.h" | |
| 14 #include "base/prefs/scoped_user_pref_update.h" | |
| 15 #include "base/process/process_info.h" | |
| 16 #include "base/single_thread_task_runner.h" | |
| 17 #include "base/stl_util.h" | |
| 18 #include "base/strings/string_number_conversions.h" | |
| 19 #include "base/thread_task_runner_handle.h" | |
| 20 #include "base/threading/sequenced_worker_pool.h" | |
| 21 #include "base/values.h" | |
| 22 #include "chrome/browser/browser_process.h" | |
| 23 #include "chrome/browser/chrome_notification_types.h" | |
| 24 #include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h" | |
| 25 #include "chrome/browser/profiles/profile.h" | |
| 26 #include "chrome/browser/safe_browsing/binary_integrity_incident_handlers.h" | |
| 27 #include "chrome/browser/safe_browsing/database_manager.h" | |
| 28 #include "chrome/browser/safe_browsing/environment_data_collection.h" | |
| 29 #include "chrome/browser/safe_browsing/incident_report_uploader_impl.h" | |
| 30 #include "chrome/browser/safe_browsing/preference_validation_delegate.h" | |
| 31 #include "chrome/browser/safe_browsing/safe_browsing_service.h" | |
| 32 #include "chrome/browser/safe_browsing/tracked_preference_incident_handlers.h" | |
| 33 #include "chrome/common/pref_names.h" | |
| 34 #include "chrome/common/safe_browsing/csd.pb.h" | |
| 35 #include "content/public/browser/browser_thread.h" | |
| 36 #include "content/public/browser/notification_service.h" | |
| 37 #include "net/url_request/url_request_context_getter.h" | |
| 38 | |
| 39 namespace safe_browsing { | |
| 40 | |
| 41 namespace { | |
| 42 | |
| 43 // The type of an incident. Used for user metrics and for pruning of | |
| 44 // previously-reported incidents. | |
| 45 enum IncidentType { | |
| 46 // Start with 1 rather than zero; otherwise there won't be enough buckets for | |
| 47 // the histogram. | |
| 48 TRACKED_PREFERENCE = 1, | |
| 49 BINARY_INTEGRITY = 2, | |
| 50 // Values for new incident types go here. | |
| 51 NUM_INCIDENT_TYPES | |
| 52 }; | |
| 53 | |
| 54 // The action taken for an incident; used for user metrics (see | |
| 55 // LogIncidentDataType). | |
| 56 enum IncidentDisposition { | |
| 57 DROPPED, | |
| 58 ACCEPTED, | |
| 59 }; | |
| 60 | |
| 61 // The state persisted for a specific instance of an incident to enable pruning | |
| 62 // of previously-reported incidents. | |
| 63 struct PersistentIncidentState { | |
| 64 // The type of the incident. | |
| 65 IncidentType type; | |
| 66 | |
| 67 // The key for a specific instance of an incident. | |
| 68 std::string key; | |
| 69 | |
| 70 // A hash digest representing a specific instance of an incident. | |
| 71 uint32_t digest; | |
| 72 }; | |
| 73 | |
| 74 // The amount of time the service will wait to collate incidents. | |
| 75 const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute | |
| 76 | |
| 77 // The amount of time between running delayed analysis callbacks. | |
| 78 const int64 kDefaultCallbackIntervalMs = 1000 * 20; | |
| 79 | |
| 80 // Returns the number of incidents contained in |incident|. The result is | |
| 81 // expected to be 1. Used in DCHECKs. | |
| 82 size_t CountIncidents(const ClientIncidentReport_IncidentData& incident) { | |
| 83 size_t result = 0; | |
| 84 if (incident.has_tracked_preference()) | |
| 85 ++result; | |
| 86 if (incident.has_binary_integrity()) | |
| 87 ++result; | |
| 88 // Add detection for new incident types here. | |
| 89 return result; | |
| 90 } | |
| 91 | |
| 92 // Returns the type of incident contained in |incident_data|. | |
| 93 IncidentType GetIncidentType( | |
| 94 const ClientIncidentReport_IncidentData& incident_data) { | |
| 95 if (incident_data.has_tracked_preference()) | |
| 96 return TRACKED_PREFERENCE; | |
| 97 if (incident_data.has_binary_integrity()) | |
| 98 return BINARY_INTEGRITY; | |
| 99 | |
| 100 // Add detection for new incident types here. | |
| 101 COMPILE_ASSERT(BINARY_INTEGRITY + 1 == NUM_INCIDENT_TYPES, | |
| 102 add_support_for_new_types); | |
| 103 NOTREACHED(); | |
| 104 return NUM_INCIDENT_TYPES; | |
| 105 } | |
| 106 | |
| 107 // Logs the type of incident in |incident_data| to a user metrics histogram. | |
| 108 void LogIncidentDataType( | |
| 109 IncidentDisposition disposition, | |
| 110 const ClientIncidentReport_IncidentData& incident_data) { | |
| 111 IncidentType type = GetIncidentType(incident_data); | |
| 112 if (disposition == ACCEPTED) { | |
| 113 UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES); | |
| 114 } else { | |
| 115 DCHECK_EQ(disposition, DROPPED); | |
| 116 UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type, | |
| 117 NUM_INCIDENT_TYPES); | |
| 118 } | |
| 119 } | |
| 120 | |
| 121 // Computes the persistent state for an incident. | |
| 122 PersistentIncidentState ComputeIncidentState( | |
| 123 const ClientIncidentReport_IncidentData& incident) { | |
| 124 PersistentIncidentState state = {GetIncidentType(incident)}; | |
| 125 switch (state.type) { | |
| 126 case TRACKED_PREFERENCE: | |
| 127 state.key = GetTrackedPreferenceIncidentKey(incident); | |
| 128 state.digest = GetTrackedPreferenceIncidentDigest(incident); | |
| 129 break; | |
| 130 case BINARY_INTEGRITY: | |
| 131 state.key = GetBinaryIntegrityIncidentKey(incident); | |
| 132 state.digest = GetBinaryIntegrityIncidentDigest(incident); | |
| 133 break; | |
| 134 // Add handling for new incident types here. | |
| 135 default: | |
| 136 COMPILE_ASSERT(BINARY_INTEGRITY + 1 == NUM_INCIDENT_TYPES, | |
| 137 add_support_for_new_types); | |
| 138 NOTREACHED(); | |
| 139 break; | |
| 140 } | |
| 141 return state; | |
| 142 } | |
| 143 | |
| 144 // Returns true if the incident described by |state| has already been reported | |
| 145 // based on the bookkeeping in the |incidents_sent| preference dictionary. | |
| 146 bool IncidentHasBeenReported(const base::DictionaryValue* incidents_sent, | |
| 147 const PersistentIncidentState& state) { | |
| 148 const base::DictionaryValue* type_dict = NULL; | |
| 149 std::string digest_string; | |
| 150 return (incidents_sent && | |
| 151 incidents_sent->GetDictionaryWithoutPathExpansion( | |
| 152 base::IntToString(state.type), &type_dict) && | |
| 153 type_dict->GetStringWithoutPathExpansion(state.key, &digest_string) && | |
| 154 digest_string == base::UintToString(state.digest)); | |
| 155 } | |
| 156 | |
| 157 // Marks the incidents described by |states| as having been reported | |
| 158 // in |incidents_set|. | |
| 159 void MarkIncidentsAsReported(const std::vector<PersistentIncidentState>& states, | |
| 160 base::DictionaryValue* incidents_sent) { | |
| 161 for (size_t i = 0; i < states.size(); ++i) { | |
| 162 const PersistentIncidentState& data = states[i]; | |
| 163 base::DictionaryValue* type_dict = NULL; | |
| 164 const std::string type_string(base::IntToString(data.type)); | |
| 165 if (!incidents_sent->GetDictionaryWithoutPathExpansion(type_string, | |
| 166 &type_dict)) { | |
| 167 type_dict = new base::DictionaryValue(); | |
| 168 incidents_sent->SetWithoutPathExpansion(type_string, type_dict); | |
| 169 } | |
| 170 type_dict->SetStringWithoutPathExpansion(data.key, | |
| 171 base::UintToString(data.digest)); | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 // Runs |callback| on the thread to which |thread_runner| belongs. The callback | |
| 176 // is run immediately if this function is called on |thread_runner|'s thread. | |
| 177 void AddIncidentOnOriginThread( | |
| 178 const AddIncidentCallback& callback, | |
| 179 scoped_refptr<base::SingleThreadTaskRunner> thread_runner, | |
| 180 scoped_ptr<ClientIncidentReport_IncidentData> incident) { | |
| 181 if (thread_runner->BelongsToCurrentThread()) | |
| 182 callback.Run(incident.Pass()); | |
| 183 else | |
| 184 thread_runner->PostTask(FROM_HERE, | |
| 185 base::Bind(callback, base::Passed(&incident))); | |
| 186 } | |
| 187 | |
| 188 } // namespace | |
| 189 | |
| 190 struct IncidentReportingService::ProfileContext { | |
| 191 ProfileContext(); | |
| 192 ~ProfileContext(); | |
| 193 | |
| 194 // The incidents collected for this profile pending creation and/or upload. | |
| 195 ScopedVector<ClientIncidentReport_IncidentData> incidents; | |
| 196 | |
| 197 // False until PROFILE_ADDED notification is received. | |
| 198 bool added; | |
| 199 | |
| 200 private: | |
| 201 DISALLOW_COPY_AND_ASSIGN(ProfileContext); | |
| 202 }; | |
| 203 | |
| 204 class IncidentReportingService::UploadContext { | |
| 205 public: | |
| 206 typedef std::map<Profile*, std::vector<PersistentIncidentState> > | |
| 207 PersistentIncidentStateCollection; | |
| 208 | |
| 209 explicit UploadContext(scoped_ptr<ClientIncidentReport> report); | |
| 210 ~UploadContext(); | |
| 211 | |
| 212 // The report being uploaded. | |
| 213 scoped_ptr<ClientIncidentReport> report; | |
| 214 | |
| 215 // The uploader in use. This is NULL until the CSD killswitch is checked. | |
| 216 scoped_ptr<IncidentReportUploader> uploader; | |
| 217 | |
| 218 // A mapping of profiles to the data to be persisted upon successful upload. | |
| 219 PersistentIncidentStateCollection profiles_to_state; | |
| 220 | |
| 221 private: | |
| 222 DISALLOW_COPY_AND_ASSIGN(UploadContext); | |
| 223 }; | |
| 224 | |
| 225 IncidentReportingService::ProfileContext::ProfileContext() : added() { | |
| 226 } | |
| 227 | |
| 228 IncidentReportingService::ProfileContext::~ProfileContext() { | |
| 229 } | |
| 230 | |
| 231 IncidentReportingService::UploadContext::UploadContext( | |
| 232 scoped_ptr<ClientIncidentReport> report) | |
| 233 : report(report.Pass()) { | |
| 234 } | |
| 235 | |
| 236 IncidentReportingService::UploadContext::~UploadContext() { | |
| 237 } | |
| 238 | |
| 239 IncidentReportingService::IncidentReportingService( | |
| 240 SafeBrowsingService* safe_browsing_service, | |
| 241 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter) | |
| 242 : database_manager_(safe_browsing_service ? | |
| 243 safe_browsing_service->database_manager() : NULL), | |
| 244 url_request_context_getter_(request_context_getter), | |
| 245 collect_environment_data_fn_(&CollectEnvironmentData), | |
| 246 environment_collection_task_runner_( | |
| 247 content::BrowserThread::GetBlockingPool() | |
| 248 ->GetTaskRunnerWithShutdownBehavior( | |
| 249 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), | |
| 250 environment_collection_pending_(), | |
| 251 collation_timeout_pending_(), | |
| 252 collation_timer_(FROM_HERE, | |
| 253 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs), | |
| 254 this, | |
| 255 &IncidentReportingService::OnCollationTimeout), | |
| 256 delayed_analysis_callbacks_( | |
| 257 base::TimeDelta::FromMilliseconds(kDefaultCallbackIntervalMs), | |
| 258 content::BrowserThread::GetBlockingPool() | |
| 259 ->GetTaskRunnerWithShutdownBehavior( | |
| 260 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), | |
| 261 receiver_weak_ptr_factory_(this), | |
| 262 weak_ptr_factory_(this) { | |
| 263 notification_registrar_.Add(this, | |
| 264 chrome::NOTIFICATION_PROFILE_ADDED, | |
| 265 content::NotificationService::AllSources()); | |
| 266 notification_registrar_.Add(this, | |
| 267 chrome::NOTIFICATION_PROFILE_DESTROYED, | |
| 268 content::NotificationService::AllSources()); | |
| 269 } | |
| 270 | |
| 271 IncidentReportingService::~IncidentReportingService() { | |
| 272 CancelIncidentCollection(); | |
| 273 | |
| 274 // Cancel all internal asynchronous tasks. | |
| 275 weak_ptr_factory_.InvalidateWeakPtrs(); | |
| 276 | |
| 277 CancelEnvironmentCollection(); | |
| 278 CancelDownloadCollection(); | |
| 279 CancelAllReportUploads(); | |
| 280 | |
| 281 STLDeleteValues(&profiles_); | |
| 282 } | |
| 283 | |
| 284 AddIncidentCallback IncidentReportingService::GetAddIncidentCallback( | |
| 285 Profile* profile) { | |
| 286 // Force the context to be created so that incidents added before | |
| 287 // OnProfileAdded is called are held until the profile's preferences can be | |
| 288 // queried. | |
| 289 ignore_result(GetOrCreateProfileContext(profile)); | |
| 290 | |
| 291 return base::Bind(&IncidentReportingService::AddIncident, | |
| 292 receiver_weak_ptr_factory_.GetWeakPtr(), | |
| 293 profile); | |
| 294 } | |
| 295 | |
| 296 scoped_ptr<TrackedPreferenceValidationDelegate> | |
| 297 IncidentReportingService::CreatePreferenceValidationDelegate(Profile* profile) { | |
| 298 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 299 | |
| 300 if (profile->IsOffTheRecord()) | |
| 301 return scoped_ptr<TrackedPreferenceValidationDelegate>(); | |
| 302 return scoped_ptr<TrackedPreferenceValidationDelegate>( | |
| 303 new PreferenceValidationDelegate(GetAddIncidentCallback(profile))); | |
| 304 } | |
| 305 | |
| 306 void IncidentReportingService::RegisterDelayedAnalysisCallback( | |
| 307 const DelayedAnalysisCallback& callback) { | |
| 308 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 309 | |
| 310 // |callback| will be run on the blocking pool, so it will likely run the | |
| 311 // AddIncidentCallback there as well. Bounce the run of that callback back to | |
| 312 // the current thread via AddIncidentOnOriginThread. | |
| 313 delayed_analysis_callbacks_.RegisterCallback( | |
| 314 base::Bind(callback, | |
| 315 base::Bind(&AddIncidentOnOriginThread, | |
| 316 GetAddIncidentCallback(NULL), | |
| 317 base::ThreadTaskRunnerHandle::Get()))); | |
| 318 | |
| 319 // Start running the callbacks if any profiles are participating in safe | |
| 320 // browsing. If none are now, running will commence if/when a participaing | |
| 321 // profile is added. | |
| 322 if (FindEligibleProfile()) | |
| 323 delayed_analysis_callbacks_.Start(); | |
| 324 } | |
| 325 | |
| 326 IncidentReportingService::IncidentReportingService( | |
| 327 SafeBrowsingService* safe_browsing_service, | |
| 328 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, | |
| 329 base::TimeDelta delayed_task_interval, | |
| 330 const scoped_refptr<base::TaskRunner>& delayed_task_runner) | |
| 331 : database_manager_(safe_browsing_service ? | |
| 332 safe_browsing_service->database_manager() : NULL), | |
| 333 url_request_context_getter_(request_context_getter), | |
| 334 collect_environment_data_fn_(&CollectEnvironmentData), | |
| 335 environment_collection_task_runner_( | |
| 336 content::BrowserThread::GetBlockingPool() | |
| 337 ->GetTaskRunnerWithShutdownBehavior( | |
| 338 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), | |
| 339 environment_collection_pending_(), | |
| 340 collation_timeout_pending_(), | |
| 341 collation_timer_(FROM_HERE, | |
| 342 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs), | |
| 343 this, | |
| 344 &IncidentReportingService::OnCollationTimeout), | |
| 345 delayed_analysis_callbacks_(delayed_task_interval, delayed_task_runner), | |
| 346 receiver_weak_ptr_factory_(this), | |
| 347 weak_ptr_factory_(this) { | |
| 348 notification_registrar_.Add(this, | |
| 349 chrome::NOTIFICATION_PROFILE_ADDED, | |
| 350 content::NotificationService::AllSources()); | |
| 351 notification_registrar_.Add(this, | |
| 352 chrome::NOTIFICATION_PROFILE_DESTROYED, | |
| 353 content::NotificationService::AllSources()); | |
| 354 } | |
| 355 | |
| 356 void IncidentReportingService::SetCollectEnvironmentHook( | |
| 357 CollectEnvironmentDataFn collect_environment_data_hook, | |
| 358 const scoped_refptr<base::TaskRunner>& task_runner) { | |
| 359 if (collect_environment_data_hook) { | |
| 360 collect_environment_data_fn_ = collect_environment_data_hook; | |
| 361 environment_collection_task_runner_ = task_runner; | |
| 362 } else { | |
| 363 collect_environment_data_fn_ = &CollectEnvironmentData; | |
| 364 environment_collection_task_runner_ = | |
| 365 content::BrowserThread::GetBlockingPool() | |
| 366 ->GetTaskRunnerWithShutdownBehavior( | |
| 367 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 void IncidentReportingService::OnProfileAdded(Profile* profile) { | |
| 372 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 373 | |
| 374 // Track the addition of all profiles even when no report is being assembled | |
| 375 // so that the service can determine whether or not it can evaluate a | |
| 376 // profile's preferences at the time of incident addition. | |
| 377 ProfileContext* context = GetOrCreateProfileContext(profile); | |
| 378 context->added = true; | |
| 379 | |
| 380 const bool safe_browsing_enabled = | |
| 381 profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled); | |
| 382 | |
| 383 // Start processing delayed analysis callbacks if this new profile | |
| 384 // participates in safe browsing. Start is idempotent, so this is safe even if | |
| 385 // they're already running. | |
| 386 if (safe_browsing_enabled) | |
| 387 delayed_analysis_callbacks_.Start(); | |
| 388 | |
| 389 // Start a new report if this profile participates in safe browsing and there | |
| 390 // are process-wide incidents. | |
| 391 if (safe_browsing_enabled && GetProfileContext(NULL)) | |
| 392 BeginReportProcessing(); | |
| 393 | |
| 394 // TODO(grt): register for pref change notifications to start delayed analysis | |
| 395 // and/or report processing if sb is currently disabled but subsequently | |
| 396 // enabled. | |
| 397 | |
| 398 // Nothing else to do if a report is not being assembled. | |
| 399 if (!report_) | |
| 400 return; | |
| 401 | |
| 402 // Drop all incidents associated with this profile that were received prior to | |
| 403 // its addition if the profile is not participating in safe browsing. | |
| 404 if (!context->incidents.empty() && !safe_browsing_enabled) { | |
| 405 for (size_t i = 0; i < context->incidents.size(); ++i) | |
| 406 LogIncidentDataType(DROPPED, *context->incidents[i]); | |
| 407 context->incidents.clear(); | |
| 408 } | |
| 409 | |
| 410 // Take another stab at finding the most recent download if a report is being | |
| 411 // assembled and one hasn't been found yet (the LastDownloadFinder operates | |
| 412 // only on profiles that have been added to the ProfileManager). | |
| 413 BeginDownloadCollection(); | |
| 414 } | |
| 415 | |
| 416 scoped_ptr<LastDownloadFinder> IncidentReportingService::CreateDownloadFinder( | |
| 417 const LastDownloadFinder::LastDownloadCallback& callback) { | |
| 418 return LastDownloadFinder::Create(callback).Pass(); | |
| 419 } | |
| 420 | |
| 421 scoped_ptr<IncidentReportUploader> IncidentReportingService::StartReportUpload( | |
| 422 const IncidentReportUploader::OnResultCallback& callback, | |
| 423 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, | |
| 424 const ClientIncidentReport& report) { | |
| 425 return IncidentReportUploaderImpl::UploadReport( | |
| 426 callback, request_context_getter, report).Pass(); | |
| 427 } | |
| 428 | |
| 429 IncidentReportingService::ProfileContext* | |
| 430 IncidentReportingService::GetOrCreateProfileContext(Profile* profile) { | |
| 431 ProfileContextCollection::iterator it = | |
| 432 profiles_.insert(ProfileContextCollection::value_type(profile, NULL)) | |
| 433 .first; | |
| 434 if (!it->second) | |
| 435 it->second = new ProfileContext(); | |
| 436 return it->second; | |
| 437 } | |
| 438 | |
| 439 IncidentReportingService::ProfileContext* | |
| 440 IncidentReportingService::GetProfileContext(Profile* profile) { | |
| 441 ProfileContextCollection::iterator it = profiles_.find(profile); | |
| 442 return it == profiles_.end() ? NULL : it->second; | |
| 443 } | |
| 444 | |
| 445 void IncidentReportingService::OnProfileDestroyed(Profile* profile) { | |
| 446 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 447 | |
| 448 ProfileContextCollection::iterator it = profiles_.find(profile); | |
| 449 if (it == profiles_.end()) | |
| 450 return; | |
| 451 | |
| 452 // TODO(grt): Persist incidents for upload on future profile load. | |
| 453 | |
| 454 // Forget about this profile. Incidents not yet sent for upload are lost. | |
| 455 // No new incidents will be accepted for it. | |
| 456 delete it->second; | |
| 457 profiles_.erase(it); | |
| 458 | |
| 459 // Remove the association with this profile from all pending uploads. | |
| 460 for (size_t i = 0; i < uploads_.size(); ++i) | |
| 461 uploads_[i]->profiles_to_state.erase(profile); | |
| 462 } | |
| 463 | |
| 464 Profile* IncidentReportingService::FindEligibleProfile() const { | |
| 465 Profile* candidate = NULL; | |
| 466 for (ProfileContextCollection::const_iterator scan = profiles_.begin(); | |
| 467 scan != profiles_.end(); | |
| 468 ++scan) { | |
| 469 // Skip over profiles that have yet to be added to the profile manager. | |
| 470 // This will also skip over the NULL-profile context used to hold | |
| 471 // process-wide incidents. | |
| 472 if (!scan->second->added) | |
| 473 continue; | |
| 474 PrefService* prefs = scan->first->GetPrefs(); | |
| 475 if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) { | |
| 476 if (!candidate) | |
| 477 candidate = scan->first; | |
| 478 if (prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) { | |
| 479 candidate = scan->first; | |
| 480 break; | |
| 481 } | |
| 482 } | |
| 483 } | |
| 484 return candidate; | |
| 485 } | |
| 486 | |
| 487 void IncidentReportingService::AddIncident( | |
| 488 Profile* profile, | |
| 489 scoped_ptr<ClientIncidentReport_IncidentData> incident_data) { | |
| 490 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 491 DCHECK_EQ(1U, CountIncidents(*incident_data)); | |
| 492 | |
| 493 ProfileContext* context = GetProfileContext(profile); | |
| 494 // It is forbidden to call this function with a destroyed profile. | |
| 495 DCHECK(context); | |
| 496 // If this is a process-wide incident, the context must not indicate that the | |
| 497 // profile (which is NULL) has been added to the profile manager. | |
| 498 DCHECK(profile || !context->added); | |
| 499 | |
| 500 // Drop the incident immediately if the profile has already been added to the | |
| 501 // manager and is not participating in safe browsing. Preference evaluation is | |
| 502 // deferred until OnProfileAdded() otherwise. | |
| 503 if (context->added && | |
| 504 !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) { | |
| 505 LogIncidentDataType(DROPPED, *incident_data); | |
| 506 return; | |
| 507 } | |
| 508 | |
| 509 // Provide time to the new incident if the caller didn't do so. | |
| 510 if (!incident_data->has_incident_time_msec()) | |
| 511 incident_data->set_incident_time_msec(base::Time::Now().ToJavaTime()); | |
| 512 | |
| 513 // Take ownership of the incident. | |
| 514 context->incidents.push_back(incident_data.release()); | |
| 515 | |
| 516 // Remember when the first incident for this report arrived. | |
| 517 if (first_incident_time_.is_null()) | |
| 518 first_incident_time_ = base::Time::Now(); | |
| 519 // Log the time between the previous incident and this one. | |
| 520 if (!last_incident_time_.is_null()) { | |
| 521 UMA_HISTOGRAM_TIMES("SBIRS.InterIncidentTime", | |
| 522 base::TimeTicks::Now() - last_incident_time_); | |
| 523 } | |
| 524 last_incident_time_ = base::TimeTicks::Now(); | |
| 525 | |
| 526 // Persist the incident data. | |
| 527 | |
| 528 // Start assembling a new report if this is the first incident ever or the | |
| 529 // first since the last upload. | |
| 530 BeginReportProcessing(); | |
| 531 } | |
| 532 | |
| 533 void IncidentReportingService::BeginReportProcessing() { | |
| 534 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 535 | |
| 536 // Creates a new report if needed. | |
| 537 if (!report_) | |
| 538 report_.reset(new ClientIncidentReport()); | |
| 539 | |
| 540 // Ensure that collection tasks are running (calls are idempotent). | |
| 541 BeginIncidentCollation(); | |
| 542 BeginEnvironmentCollection(); | |
| 543 BeginDownloadCollection(); | |
| 544 } | |
| 545 | |
| 546 void IncidentReportingService::BeginIncidentCollation() { | |
| 547 // Restart the delay timer to send the report upon expiration. | |
| 548 collation_timeout_pending_ = true; | |
| 549 collation_timer_.Reset(); | |
| 550 } | |
| 551 | |
| 552 void IncidentReportingService::BeginEnvironmentCollection() { | |
| 553 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 554 DCHECK(report_); | |
| 555 // Nothing to do if environment collection is pending or has already | |
| 556 // completed. | |
| 557 if (environment_collection_pending_ || report_->has_environment()) | |
| 558 return; | |
| 559 | |
| 560 environment_collection_begin_ = base::TimeTicks::Now(); | |
| 561 ClientIncidentReport_EnvironmentData* environment_data = | |
| 562 new ClientIncidentReport_EnvironmentData(); | |
| 563 environment_collection_pending_ = | |
| 564 environment_collection_task_runner_->PostTaskAndReply( | |
| 565 FROM_HERE, | |
| 566 base::Bind(collect_environment_data_fn_, environment_data), | |
| 567 base::Bind(&IncidentReportingService::OnEnvironmentDataCollected, | |
| 568 weak_ptr_factory_.GetWeakPtr(), | |
| 569 base::Passed(make_scoped_ptr(environment_data)))); | |
| 570 | |
| 571 // Posting the task will fail if the runner has been shut down. This should | |
| 572 // never happen since the blocking pool is shut down after this service. | |
| 573 DCHECK(environment_collection_pending_); | |
| 574 } | |
| 575 | |
| 576 bool IncidentReportingService::WaitingForEnvironmentCollection() { | |
| 577 return environment_collection_pending_; | |
| 578 } | |
| 579 | |
| 580 void IncidentReportingService::CancelEnvironmentCollection() { | |
| 581 environment_collection_begin_ = base::TimeTicks(); | |
| 582 environment_collection_pending_ = false; | |
| 583 if (report_) | |
| 584 report_->clear_environment(); | |
| 585 } | |
| 586 | |
| 587 void IncidentReportingService::OnEnvironmentDataCollected( | |
| 588 scoped_ptr<ClientIncidentReport_EnvironmentData> environment_data) { | |
| 589 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 590 DCHECK(environment_collection_pending_); | |
| 591 DCHECK(report_ && !report_->has_environment()); | |
| 592 environment_collection_pending_ = false; | |
| 593 | |
| 594 // CurrentProcessInfo::CreationTime() is missing on some platforms. | |
| 595 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) | |
| 596 base::TimeDelta uptime = | |
| 597 first_incident_time_ - base::CurrentProcessInfo::CreationTime(); | |
| 598 environment_data->mutable_process()->set_uptime_msec(uptime.InMilliseconds()); | |
| 599 #endif | |
| 600 | |
| 601 report_->set_allocated_environment(environment_data.release()); | |
| 602 | |
| 603 UMA_HISTOGRAM_TIMES("SBIRS.EnvCollectionTime", | |
| 604 base::TimeTicks::Now() - environment_collection_begin_); | |
| 605 environment_collection_begin_ = base::TimeTicks(); | |
| 606 | |
| 607 UploadIfCollectionComplete(); | |
| 608 } | |
| 609 | |
| 610 bool IncidentReportingService::WaitingToCollateIncidents() { | |
| 611 return collation_timeout_pending_; | |
| 612 } | |
| 613 | |
| 614 void IncidentReportingService::CancelIncidentCollection() { | |
| 615 collation_timeout_pending_ = false; | |
| 616 last_incident_time_ = base::TimeTicks(); | |
| 617 report_.reset(); | |
| 618 } | |
| 619 | |
| 620 void IncidentReportingService::OnCollationTimeout() { | |
| 621 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 622 | |
| 623 // Exit early if collection was cancelled. | |
| 624 if (!collation_timeout_pending_) | |
| 625 return; | |
| 626 | |
| 627 // Wait another round if profile-bound incidents have come in from a profile | |
| 628 // that has yet to complete creation. | |
| 629 for (ProfileContextCollection::iterator scan = profiles_.begin(); | |
| 630 scan != profiles_.end(); | |
| 631 ++scan) { | |
| 632 if (scan->first && !scan->second->added && | |
| 633 !scan->second->incidents.empty()) { | |
| 634 collation_timer_.Reset(); | |
| 635 return; | |
| 636 } | |
| 637 } | |
| 638 | |
| 639 collation_timeout_pending_ = false; | |
| 640 | |
| 641 UploadIfCollectionComplete(); | |
| 642 } | |
| 643 | |
| 644 void IncidentReportingService::BeginDownloadCollection() { | |
| 645 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 646 DCHECK(report_); | |
| 647 // Nothing to do if a search for the most recent download is already pending | |
| 648 // or if one has already been found. | |
| 649 if (last_download_finder_ || report_->has_download()) | |
| 650 return; | |
| 651 | |
| 652 last_download_begin_ = base::TimeTicks::Now(); | |
| 653 last_download_finder_ = CreateDownloadFinder( | |
| 654 base::Bind(&IncidentReportingService::OnLastDownloadFound, | |
| 655 weak_ptr_factory_.GetWeakPtr())); | |
| 656 // No instance is returned if there are no eligible loaded profiles. Another | |
| 657 // search will be attempted in OnProfileAdded() if another profile appears on | |
| 658 // the scene. | |
| 659 if (!last_download_finder_) | |
| 660 last_download_begin_ = base::TimeTicks(); | |
| 661 } | |
| 662 | |
| 663 bool IncidentReportingService::WaitingForMostRecentDownload() { | |
| 664 DCHECK(report_); // Only call this when a report is being assembled. | |
| 665 // The easy case: not waiting if a download has already been found. | |
| 666 if (report_->has_download()) | |
| 667 return false; | |
| 668 // The next easy case: waiting if the finder is operating. | |
| 669 if (last_download_finder_) | |
| 670 return true; | |
| 671 // The harder case: waiting if a profile has not yet been added. | |
| 672 for (ProfileContextCollection::const_iterator scan = profiles_.begin(); | |
| 673 scan != profiles_.end(); | |
| 674 ++scan) { | |
| 675 if (!scan->second->added) | |
| 676 return true; | |
| 677 } | |
| 678 // There is no most recent download and there's nothing more to wait for. | |
| 679 return false; | |
| 680 } | |
| 681 | |
| 682 void IncidentReportingService::CancelDownloadCollection() { | |
| 683 last_download_finder_.reset(); | |
| 684 last_download_begin_ = base::TimeTicks(); | |
| 685 if (report_) | |
| 686 report_->clear_download(); | |
| 687 } | |
| 688 | |
| 689 void IncidentReportingService::OnLastDownloadFound( | |
| 690 scoped_ptr<ClientIncidentReport_DownloadDetails> last_download) { | |
| 691 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 692 DCHECK(report_); | |
| 693 | |
| 694 UMA_HISTOGRAM_TIMES("SBIRS.FindDownloadedBinaryTime", | |
| 695 base::TimeTicks::Now() - last_download_begin_); | |
| 696 last_download_begin_ = base::TimeTicks(); | |
| 697 | |
| 698 // Harvest the finder. | |
| 699 last_download_finder_.reset(); | |
| 700 | |
| 701 if (last_download) | |
| 702 report_->set_allocated_download(last_download.release()); | |
| 703 | |
| 704 UploadIfCollectionComplete(); | |
| 705 } | |
| 706 | |
| 707 void IncidentReportingService::UploadIfCollectionComplete() { | |
| 708 DCHECK(report_); | |
| 709 // Bail out if there are still outstanding collection tasks. Completion of any | |
| 710 // of these will start another upload attempt. | |
| 711 if (WaitingForEnvironmentCollection() || | |
| 712 WaitingToCollateIncidents() || | |
| 713 WaitingForMostRecentDownload()) { | |
| 714 return; | |
| 715 } | |
| 716 | |
| 717 // Take ownership of the report and clear things for future reports. | |
| 718 scoped_ptr<ClientIncidentReport> report(report_.Pass()); | |
| 719 first_incident_time_ = base::Time(); | |
| 720 last_incident_time_ = base::TimeTicks(); | |
| 721 | |
| 722 // Drop the report if no executable download was found. | |
| 723 if (!report->has_download()) { | |
| 724 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult", | |
| 725 IncidentReportUploader::UPLOAD_NO_DOWNLOAD, | |
| 726 IncidentReportUploader::NUM_UPLOAD_RESULTS); | |
| 727 return; | |
| 728 } | |
| 729 | |
| 730 ClientIncidentReport_EnvironmentData_Process* process = | |
| 731 report->mutable_environment()->mutable_process(); | |
| 732 | |
| 733 // Not all platforms have a metrics reporting preference. | |
| 734 if (g_browser_process->local_state()->FindPreference( | |
| 735 prefs::kMetricsReportingEnabled)) { | |
| 736 process->set_metrics_consent(g_browser_process->local_state()->GetBoolean( | |
| 737 prefs::kMetricsReportingEnabled)); | |
| 738 } | |
| 739 | |
| 740 // Find the profile that benefits from the strongest protections. | |
| 741 Profile* eligible_profile = FindEligibleProfile(); | |
| 742 process->set_extended_consent( | |
| 743 eligible_profile ? eligible_profile->GetPrefs()->GetBoolean( | |
| 744 prefs::kSafeBrowsingExtendedReportingEnabled) : | |
| 745 false); | |
| 746 | |
| 747 // Associate process-wide incidents with the profile that benefits from the | |
| 748 // strongest safe browsing protections. | |
| 749 ProfileContext* null_context = GetProfileContext(NULL); | |
| 750 if (null_context && !null_context->incidents.empty() && eligible_profile) { | |
| 751 ProfileContext* eligible_context = GetProfileContext(eligible_profile); | |
| 752 // Move the incidents to the target context. | |
| 753 eligible_context->incidents.insert(eligible_context->incidents.end(), | |
| 754 null_context->incidents.begin(), | |
| 755 null_context->incidents.end()); | |
| 756 null_context->incidents.weak_clear(); | |
| 757 } | |
| 758 | |
| 759 // Collect incidents across all profiles participating in safe browsing. Drop | |
| 760 // incidents if the profile stopped participating before collection completed. | |
| 761 // Prune previously submitted incidents. | |
| 762 // Associate the profiles and their incident data with the upload. | |
| 763 size_t prune_count = 0; | |
| 764 UploadContext::PersistentIncidentStateCollection profiles_to_state; | |
| 765 for (ProfileContextCollection::iterator scan = profiles_.begin(); | |
| 766 scan != profiles_.end(); | |
| 767 ++scan) { | |
| 768 // Bypass process-wide incidents that have not yet been associated with a | |
| 769 // profile. | |
| 770 if (!scan->first) | |
| 771 continue; | |
| 772 PrefService* prefs = scan->first->GetPrefs(); | |
| 773 ProfileContext* context = scan->second; | |
| 774 if (context->incidents.empty()) | |
| 775 continue; | |
| 776 if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) { | |
| 777 for (size_t i = 0; i < context->incidents.size(); ++i) { | |
| 778 LogIncidentDataType(DROPPED, *context->incidents[i]); | |
| 779 } | |
| 780 context->incidents.clear(); | |
| 781 continue; | |
| 782 } | |
| 783 std::vector<PersistentIncidentState> states; | |
| 784 const base::DictionaryValue* incidents_sent = | |
| 785 prefs->GetDictionary(prefs::kSafeBrowsingIncidentsSent); | |
| 786 // Prep persistent data and prune any incidents already sent. | |
| 787 for (size_t i = 0; i < context->incidents.size(); ++i) { | |
| 788 ClientIncidentReport_IncidentData* incident = context->incidents[i]; | |
| 789 const PersistentIncidentState state = ComputeIncidentState(*incident); | |
| 790 if (IncidentHasBeenReported(incidents_sent, state)) { | |
| 791 ++prune_count; | |
| 792 delete context->incidents[i]; | |
| 793 context->incidents[i] = NULL; | |
| 794 } else { | |
| 795 states.push_back(state); | |
| 796 } | |
| 797 } | |
| 798 if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) { | |
| 799 // Prune all incidents as if they had been reported, migrating to the new | |
| 800 // technique. TODO(grt): remove this branch after it has shipped. | |
| 801 for (size_t i = 0; i < context->incidents.size(); ++i) { | |
| 802 if (context->incidents[i]) | |
| 803 ++prune_count; | |
| 804 } | |
| 805 context->incidents.clear(); | |
| 806 prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent); | |
| 807 DictionaryPrefUpdate pref_update(prefs, | |
| 808 prefs::kSafeBrowsingIncidentsSent); | |
| 809 MarkIncidentsAsReported(states, pref_update.Get()); | |
| 810 } else { | |
| 811 for (size_t i = 0; i < context->incidents.size(); ++i) { | |
| 812 ClientIncidentReport_IncidentData* incident = context->incidents[i]; | |
| 813 if (incident) { | |
| 814 LogIncidentDataType(ACCEPTED, *incident); | |
| 815 // Ownership of the incident is passed to the report. | |
| 816 report->mutable_incident()->AddAllocated(incident); | |
| 817 } | |
| 818 } | |
| 819 context->incidents.weak_clear(); | |
| 820 std::vector<PersistentIncidentState>& profile_states = | |
| 821 profiles_to_state[scan->first]; | |
| 822 profile_states.swap(states); | |
| 823 } | |
| 824 } | |
| 825 | |
| 826 const int count = report->incident_size(); | |
| 827 // Abandon the request if all incidents were dropped with none pruned. | |
| 828 if (!count && !prune_count) | |
| 829 return; | |
| 830 | |
| 831 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count); | |
| 832 | |
| 833 { | |
| 834 double prune_pct = static_cast<double>(prune_count); | |
| 835 prune_pct = prune_pct * 100.0 / (count + prune_count); | |
| 836 prune_pct = round(prune_pct); | |
| 837 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct)); | |
| 838 } | |
| 839 // Abandon the report if all incidents were pruned. | |
| 840 if (!count) | |
| 841 return; | |
| 842 | |
| 843 scoped_ptr<UploadContext> context(new UploadContext(report.Pass())); | |
| 844 context->profiles_to_state.swap(profiles_to_state); | |
| 845 if (!database_manager_) { | |
| 846 // No database manager during testing. Take ownership of the context and | |
| 847 // continue processing. | |
| 848 UploadContext* temp_context = context.get(); | |
| 849 uploads_.push_back(context.release()); | |
| 850 IncidentReportingService::OnKillSwitchResult(temp_context, false); | |
| 851 } else { | |
| 852 if (content::BrowserThread::PostTaskAndReplyWithResult( | |
| 853 content::BrowserThread::IO, | |
| 854 FROM_HERE, | |
| 855 base::Bind(&SafeBrowsingDatabaseManager::IsCsdWhitelistKillSwitchOn, | |
| 856 database_manager_), | |
| 857 base::Bind(&IncidentReportingService::OnKillSwitchResult, | |
| 858 weak_ptr_factory_.GetWeakPtr(), | |
| 859 context.get()))) { | |
| 860 uploads_.push_back(context.release()); | |
| 861 } // else should not happen. Let the context be deleted automatically. | |
| 862 } | |
| 863 } | |
| 864 | |
| 865 void IncidentReportingService::CancelAllReportUploads() { | |
| 866 for (size_t i = 0; i < uploads_.size(); ++i) { | |
| 867 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult", | |
| 868 IncidentReportUploader::UPLOAD_CANCELLED, | |
| 869 IncidentReportUploader::NUM_UPLOAD_RESULTS); | |
| 870 } | |
| 871 uploads_.clear(); | |
| 872 } | |
| 873 | |
| 874 void IncidentReportingService::OnKillSwitchResult(UploadContext* context, | |
| 875 bool is_killswitch_on) { | |
| 876 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 877 if (!is_killswitch_on) { | |
| 878 // Initiate the upload. | |
| 879 context->uploader = | |
| 880 StartReportUpload( | |
| 881 base::Bind(&IncidentReportingService::OnReportUploadResult, | |
| 882 weak_ptr_factory_.GetWeakPtr(), | |
| 883 context), | |
| 884 url_request_context_getter_, | |
| 885 *context->report).Pass(); | |
| 886 if (!context->uploader) { | |
| 887 OnReportUploadResult(context, | |
| 888 IncidentReportUploader::UPLOAD_INVALID_REQUEST, | |
| 889 scoped_ptr<ClientIncidentResponse>()); | |
| 890 } | |
| 891 } else { | |
| 892 OnReportUploadResult(context, | |
| 893 IncidentReportUploader::UPLOAD_SUPPRESSED, | |
| 894 scoped_ptr<ClientIncidentResponse>()); | |
| 895 } | |
| 896 } | |
| 897 | |
| 898 void IncidentReportingService::HandleResponse(const UploadContext& context) { | |
| 899 for (UploadContext::PersistentIncidentStateCollection::const_iterator scan = | |
| 900 context.profiles_to_state.begin(); | |
| 901 scan != context.profiles_to_state.end(); | |
| 902 ++scan) { | |
| 903 DictionaryPrefUpdate pref_update(scan->first->GetPrefs(), | |
| 904 prefs::kSafeBrowsingIncidentsSent); | |
| 905 MarkIncidentsAsReported(scan->second, pref_update.Get()); | |
| 906 } | |
| 907 } | |
| 908 | |
| 909 void IncidentReportingService::OnReportUploadResult( | |
| 910 UploadContext* context, | |
| 911 IncidentReportUploader::Result result, | |
| 912 scoped_ptr<ClientIncidentResponse> response) { | |
| 913 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 914 | |
| 915 UMA_HISTOGRAM_ENUMERATION( | |
| 916 "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS); | |
| 917 | |
| 918 // The upload is no longer outstanding, so take ownership of the context (from | |
| 919 // the collection of outstanding uploads) in this scope. | |
| 920 ScopedVector<UploadContext>::iterator it( | |
| 921 std::find(uploads_.begin(), uploads_.end(), context)); | |
| 922 DCHECK(it != uploads_.end()); | |
| 923 scoped_ptr<UploadContext> upload(context); // == *it | |
| 924 *it = uploads_.back(); | |
| 925 uploads_.weak_erase(uploads_.end() - 1); | |
| 926 | |
| 927 if (result == IncidentReportUploader::UPLOAD_SUCCESS) | |
| 928 HandleResponse(*upload); | |
| 929 // else retry? | |
| 930 } | |
| 931 | |
| 932 void IncidentReportingService::Observe( | |
| 933 int type, | |
| 934 const content::NotificationSource& source, | |
| 935 const content::NotificationDetails& details) { | |
| 936 switch (type) { | |
| 937 case chrome::NOTIFICATION_PROFILE_ADDED: { | |
| 938 Profile* profile = content::Source<Profile>(source).ptr(); | |
| 939 if (!profile->IsOffTheRecord()) | |
| 940 OnProfileAdded(profile); | |
| 941 break; | |
| 942 } | |
| 943 case chrome::NOTIFICATION_PROFILE_DESTROYED: { | |
| 944 Profile* profile = content::Source<Profile>(source).ptr(); | |
| 945 if (!profile->IsOffTheRecord()) | |
| 946 OnProfileDestroyed(profile); | |
| 947 break; | |
| 948 } | |
| 949 default: | |
| 950 break; | |
| 951 } | |
| 952 } | |
| 953 | |
| 954 } // namespace safe_browsing | |
| OLD | NEW |