OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/safe_browsing/incident_reporting_service.h" | 5 #include "chrome/browser/safe_browsing/incident_reporting_service.h" |
6 | 6 |
7 #include <math.h> | 7 #include <math.h> |
8 | 8 |
| 9 #include <algorithm> |
| 10 #include <vector> |
| 11 |
9 #include "base/metrics/histogram.h" | 12 #include "base/metrics/histogram.h" |
10 #include "base/prefs/pref_service.h" | 13 #include "base/prefs/pref_service.h" |
11 #include "base/process/process_info.h" | 14 #include "base/process/process_info.h" |
12 #include "base/stl_util.h" | 15 #include "base/stl_util.h" |
13 #include "base/threading/sequenced_worker_pool.h" | 16 #include "base/threading/sequenced_worker_pool.h" |
14 #include "chrome/browser/browser_process.h" | 17 #include "chrome/browser/browser_process.h" |
15 #include "chrome/browser/chrome_notification_types.h" | 18 #include "chrome/browser/chrome_notification_types.h" |
16 #include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h" | 19 #include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h" |
17 #include "chrome/browser/profiles/profile.h" | 20 #include "chrome/browser/profiles/profile.h" |
18 #include "chrome/browser/safe_browsing/database_manager.h" | 21 #include "chrome/browser/safe_browsing/database_manager.h" |
(...skipping 21 matching lines...) Expand all Loading... |
40 enum IncidentDisposition { | 43 enum IncidentDisposition { |
41 DROPPED, | 44 DROPPED, |
42 ACCEPTED, | 45 ACCEPTED, |
43 }; | 46 }; |
44 | 47 |
45 const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute | 48 const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute |
46 | 49 |
47 void LogIncidentDataType( | 50 void LogIncidentDataType( |
48 IncidentDisposition disposition, | 51 IncidentDisposition disposition, |
49 const ClientIncidentReport_IncidentData& incident_data) { | 52 const ClientIncidentReport_IncidentData& incident_data) { |
50 static const char kAcceptedMetric[] = "SBIRS.Incident"; | |
51 static const char kDroppedMetric[] = "SBIRS.DroppedIncident"; | |
52 | |
53 IncidentType type = TRACKED_PREFERENCE; | 53 IncidentType type = TRACKED_PREFERENCE; |
54 | 54 |
55 // Add a switch statement once other types are supported. | 55 // Add a switch statement once other types are supported. |
56 DCHECK(incident_data.has_tracked_preference()); | 56 DCHECK(incident_data.has_tracked_preference()); |
57 | 57 |
58 if (disposition == ACCEPTED) { | 58 if (disposition == ACCEPTED) { |
59 UMA_HISTOGRAM_ENUMERATION(kAcceptedMetric, type, NUM_INCIDENT_TYPES); | 59 UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES); |
60 } else { | 60 } else { |
61 DCHECK_EQ(disposition, DROPPED); | 61 DCHECK_EQ(disposition, DROPPED); |
62 UMA_HISTOGRAM_ENUMERATION(kDroppedMetric, type, NUM_INCIDENT_TYPES); | 62 UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type, |
| 63 NUM_INCIDENT_TYPES); |
63 } | 64 } |
64 } | 65 } |
65 | 66 |
66 } // namespace | 67 } // namespace |
67 | 68 |
68 struct IncidentReportingService::ProfileContext { | 69 struct IncidentReportingService::ProfileContext { |
69 ProfileContext(); | 70 ProfileContext(); |
70 ~ProfileContext(); | 71 ~ProfileContext(); |
71 | 72 |
72 // The incidents collected for this profile pending creation and/or upload. | 73 // The incidents collected for this profile pending creation and/or upload. |
73 ScopedVector<ClientIncidentReport_IncidentData> incidents; | 74 ScopedVector<ClientIncidentReport_IncidentData> incidents; |
74 | 75 |
75 // False until PROFILE_CREATED notification is received. | 76 // False until PROFILE_CREATED notification is received. |
76 bool created; | 77 bool created; |
77 | 78 |
78 private: | 79 private: |
79 DISALLOW_COPY_AND_ASSIGN(ProfileContext); | 80 DISALLOW_COPY_AND_ASSIGN(ProfileContext); |
80 }; | 81 }; |
81 | 82 |
82 class IncidentReportingService::UploadContext { | 83 class IncidentReportingService::UploadContext { |
83 public: | 84 public: |
84 explicit UploadContext(scoped_ptr<ClientIncidentReport> report); | 85 explicit UploadContext(scoped_ptr<ClientIncidentReport> report); |
85 ~UploadContext(); | 86 ~UploadContext(); |
86 | 87 |
| 88 // The report being uploaded. |
87 scoped_ptr<ClientIncidentReport> report; | 89 scoped_ptr<ClientIncidentReport> report; |
| 90 |
| 91 // The uploader in use. This is NULL until the CSD killswitch is checked. |
88 scoped_ptr<IncidentReportUploader> uploader; | 92 scoped_ptr<IncidentReportUploader> uploader; |
89 | 93 |
| 94 // The set of profiles from which incidents in |report| originated. |
| 95 std::vector<Profile*> profiles; |
| 96 |
90 private: | 97 private: |
91 DISALLOW_COPY_AND_ASSIGN(UploadContext); | 98 DISALLOW_COPY_AND_ASSIGN(UploadContext); |
92 }; | 99 }; |
93 | 100 |
94 IncidentReportingService::ProfileContext::ProfileContext() : created() { | 101 IncidentReportingService::ProfileContext::ProfileContext() : created() { |
95 } | 102 } |
96 | 103 |
97 IncidentReportingService::ProfileContext::~ProfileContext() { | 104 IncidentReportingService::ProfileContext::~ProfileContext() { |
98 } | 105 } |
99 | 106 |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
232 ProfileContextCollection::iterator it = profiles_.find(profile); | 239 ProfileContextCollection::iterator it = profiles_.find(profile); |
233 if (it == profiles_.end()) | 240 if (it == profiles_.end()) |
234 return; | 241 return; |
235 | 242 |
236 // TODO(grt): Persist incidents for upload on future profile load. | 243 // TODO(grt): Persist incidents for upload on future profile load. |
237 | 244 |
238 // Forget about this profile. Incidents not yet sent for upload are lost. | 245 // Forget about this profile. Incidents not yet sent for upload are lost. |
239 // No new incidents will be accepted for it. | 246 // No new incidents will be accepted for it. |
240 delete it->second; | 247 delete it->second; |
241 profiles_.erase(it); | 248 profiles_.erase(it); |
| 249 |
| 250 // Remove the association with this profile from any pending uploads. |
| 251 for (size_t i = 0; i < uploads_.size(); ++i) { |
| 252 UploadContext* upload = uploads_[i]; |
| 253 std::vector<Profile*>::iterator it = |
| 254 std::find(upload->profiles.begin(), upload->profiles.end(), profile); |
| 255 if (it != upload->profiles.end()) { |
| 256 *it = upload->profiles.back(); |
| 257 upload->profiles.resize(upload->profiles.size() - 1); |
| 258 break; |
| 259 } |
| 260 } |
242 } | 261 } |
243 | 262 |
244 void IncidentReportingService::AddIncident( | 263 void IncidentReportingService::AddIncident( |
245 Profile* profile, | 264 Profile* profile, |
246 scoped_ptr<ClientIncidentReport_IncidentData> incident_data) { | 265 scoped_ptr<ClientIncidentReport_IncidentData> incident_data) { |
247 DCHECK(thread_checker_.CalledOnValidThread()); | 266 DCHECK(thread_checker_.CalledOnValidThread()); |
248 // Incidents outside the context of a profile are not supported at the moment. | 267 // Incidents outside the context of a profile are not supported at the moment. |
249 DCHECK(profile); | 268 DCHECK(profile); |
250 | 269 |
251 ProfileContext* context = GetProfileContext(profile); | 270 ProfileContext* context = GetProfileContext(profile); |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
370 | 389 |
371 UploadIfCollectionComplete(); | 390 UploadIfCollectionComplete(); |
372 } | 391 } |
373 | 392 |
374 void IncidentReportingService::CollectDownloadDetails( | 393 void IncidentReportingService::CollectDownloadDetails( |
375 ClientIncidentReport_DownloadDetails* download_details) { | 394 ClientIncidentReport_DownloadDetails* download_details) { |
376 DCHECK(thread_checker_.CalledOnValidThread()); | 395 DCHECK(thread_checker_.CalledOnValidThread()); |
377 // TODO(grt): collect download info; http://crbug.com/383042. | 396 // TODO(grt): collect download info; http://crbug.com/383042. |
378 } | 397 } |
379 | 398 |
380 void IncidentReportingService::RecordReportedIncidents() { | |
381 // TODO(grt): store state about reported incidents. | |
382 } | |
383 | |
384 void IncidentReportingService::PruneReportedIncidents( | |
385 ClientIncidentReport* report) { | |
386 // TODO(grt): remove previously reported incidents; http://crbug.com/383043. | |
387 } | |
388 | |
389 void IncidentReportingService::UploadIfCollectionComplete() { | 399 void IncidentReportingService::UploadIfCollectionComplete() { |
390 DCHECK(report_); | 400 DCHECK(report_); |
391 // Bail out if there are still outstanding collection tasks. | 401 // Bail out if there are still outstanding collection tasks. |
392 if (environment_collection_pending_ || collection_timeout_pending_) | 402 if (environment_collection_pending_ || collection_timeout_pending_) |
393 return; | 403 return; |
394 | 404 |
395 // Take ownership of the report and clear things for future reports. | 405 // Take ownership of the report and clear things for future reports. |
396 scoped_ptr<ClientIncidentReport> report(report_.Pass()); | 406 scoped_ptr<ClientIncidentReport> report(report_.Pass()); |
397 last_incident_time_ = base::TimeTicks(); | 407 last_incident_time_ = base::TimeTicks(); |
398 | 408 |
399 ClientIncidentReport_EnvironmentData_Process* process = | 409 ClientIncidentReport_EnvironmentData_Process* process = |
400 report->mutable_environment()->mutable_process(); | 410 report->mutable_environment()->mutable_process(); |
401 | 411 |
402 // Not all platforms have a metrics reporting preference. | 412 // Not all platforms have a metrics reporting preference. |
403 if (g_browser_process->local_state()->FindPreference( | 413 if (g_browser_process->local_state()->FindPreference( |
404 prefs::kMetricsReportingEnabled)) { | 414 prefs::kMetricsReportingEnabled)) { |
405 process->set_metrics_consent(g_browser_process->local_state()->GetBoolean( | 415 process->set_metrics_consent(g_browser_process->local_state()->GetBoolean( |
406 prefs::kMetricsReportingEnabled)); | 416 prefs::kMetricsReportingEnabled)); |
407 } | 417 } |
| 418 |
408 // Check for extended consent in any profile while collecting incidents. | 419 // Check for extended consent in any profile while collecting incidents. |
409 process->set_extended_consent(false); | 420 process->set_extended_consent(false); |
410 // Collect incidents across all profiles participating in safe browsing. Drop | 421 // Collect incidents across all profiles participating in safe browsing. Drop |
411 // incidents if the profile stopped participating before collection completed. | 422 // incidents if the profile stopped participating before collection completed. |
| 423 // Prune incidents if the profile has already submitted any incidents. |
| 424 // Associate the participating profiles with the upload. |
| 425 size_t prune_count = 0; |
| 426 std::vector<Profile*> profiles; |
412 for (ProfileContextCollection::iterator scan = profiles_.begin(); | 427 for (ProfileContextCollection::iterator scan = profiles_.begin(); |
413 scan != profiles_.end(); | 428 scan != profiles_.end(); |
414 ++scan) { | 429 ++scan) { |
415 PrefService* prefs = scan->first->GetPrefs(); | 430 PrefService* prefs = scan->first->GetPrefs(); |
416 if (process && | 431 if (process && |
417 prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) { | 432 prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) { |
418 process->set_extended_consent(true); | 433 process->set_extended_consent(true); |
419 process = NULL; // Don't check any more once one is found. | 434 process = NULL; // Don't check any more once one is found. |
420 } | 435 } |
421 ProfileContext* context = scan->second; | 436 ProfileContext* context = scan->second; |
422 if (context->incidents.empty()) | 437 if (context->incidents.empty()) |
423 continue; | 438 continue; |
424 if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) { | 439 if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) { |
| 440 for (size_t i = 0; i < context->incidents.size(); ++i) { |
| 441 LogIncidentDataType(DROPPED, *context->incidents[i]); |
| 442 } |
| 443 context->incidents.clear(); |
| 444 } else if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) { |
| 445 // Prune all incidents. |
| 446 // TODO(grt): Only prune previously submitted incidents; |
| 447 // http://crbug.com/383043. |
| 448 prune_count += context->incidents.size(); |
| 449 context->incidents.clear(); |
| 450 } else { |
425 for (size_t i = 0; i < context->incidents.size(); ++i) { | 451 for (size_t i = 0; i < context->incidents.size(); ++i) { |
426 ClientIncidentReport_IncidentData* incident = context->incidents[i]; | 452 ClientIncidentReport_IncidentData* incident = context->incidents[i]; |
427 LogIncidentDataType(ACCEPTED, *incident); | 453 LogIncidentDataType(ACCEPTED, *incident); |
428 // Ownership of the incident is passed to the report. | 454 // Ownership of the incident is passed to the report. |
429 report->mutable_incident()->AddAllocated(incident); | 455 report->mutable_incident()->AddAllocated(incident); |
430 } | 456 } |
431 context->incidents.weak_clear(); | 457 context->incidents.weak_clear(); |
432 } else { | 458 profiles.push_back(scan->first); |
433 for (size_t i = 0; i < context->incidents.size(); ++i) { | |
434 LogIncidentDataType(DROPPED, *context->incidents[i]); | |
435 } | |
436 context->incidents.clear(); | |
437 } | 459 } |
438 } | 460 } |
439 | 461 |
440 const int original_count = report->incident_size(); | 462 const int count = report->incident_size(); |
441 // Abandon the request if all incidents were dropped. | 463 // Abandon the request if all incidents were dropped with none pruned. |
442 if (!original_count) | 464 if (!count && !prune_count) |
443 return; | 465 return; |
444 | 466 |
445 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", original_count); | 467 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count); |
446 | 468 |
447 PruneReportedIncidents(report.get()); | |
448 | |
449 int final_count = report->incident_size(); | |
450 { | 469 { |
451 double prune_pct = static_cast<double>(original_count - final_count); | 470 double prune_pct = static_cast<double>(prune_count); |
452 prune_pct = prune_pct * 100.0 / original_count; | 471 prune_pct = prune_pct * 100.0 / (count + prune_count); |
453 prune_pct = round(prune_pct); | 472 prune_pct = round(prune_pct); |
454 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct)); | 473 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct)); |
455 } | 474 } |
456 // Abandon the report if all incidents were pruned. | 475 // Abandon the report if all incidents were pruned. |
457 if (!final_count) | 476 if (!count) |
458 return; | 477 return; |
459 | 478 |
460 scoped_ptr<UploadContext> context(new UploadContext(report.Pass())); | 479 scoped_ptr<UploadContext> context(new UploadContext(report.Pass())); |
| 480 context->profiles.swap(profiles); |
461 if (!database_manager_) { | 481 if (!database_manager_) { |
462 // No database manager during testing. Take ownership of the context and | 482 // No database manager during testing. Take ownership of the context and |
463 // continue processing. | 483 // continue processing. |
464 UploadContext* temp_context = context.get(); | 484 UploadContext* temp_context = context.get(); |
465 uploads_.push_back(context.release()); | 485 uploads_.push_back(context.release()); |
466 IncidentReportingService::OnKillSwitchResult(temp_context, false); | 486 IncidentReportingService::OnKillSwitchResult(temp_context, false); |
467 } else { | 487 } else { |
468 if (content::BrowserThread::PostTaskAndReplyWithResult( | 488 if (content::BrowserThread::PostTaskAndReplyWithResult( |
469 content::BrowserThread::IO, | 489 content::BrowserThread::IO, |
470 FROM_HERE, | 490 FROM_HERE, |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
504 IncidentReportUploader::UPLOAD_INVALID_REQUEST, | 524 IncidentReportUploader::UPLOAD_INVALID_REQUEST, |
505 scoped_ptr<ClientIncidentResponse>()); | 525 scoped_ptr<ClientIncidentResponse>()); |
506 } | 526 } |
507 } else { | 527 } else { |
508 OnReportUploadResult(context, | 528 OnReportUploadResult(context, |
509 IncidentReportUploader::UPLOAD_SUPPRESSED, | 529 IncidentReportUploader::UPLOAD_SUPPRESSED, |
510 scoped_ptr<ClientIncidentResponse>()); | 530 scoped_ptr<ClientIncidentResponse>()); |
511 } | 531 } |
512 } | 532 } |
513 | 533 |
514 void IncidentReportingService::HandleResponse( | 534 void IncidentReportingService::HandleResponse(const UploadContext& context) { |
515 scoped_ptr<ClientIncidentReport> report, | 535 for (size_t i = 0; i < context.profiles.size(); ++i) { |
516 scoped_ptr<ClientIncidentResponse> response) { | 536 context.profiles[i]->GetPrefs()->SetBoolean( |
517 RecordReportedIncidents(); | 537 prefs::kSafeBrowsingIncidentReportSent, true); |
| 538 } |
518 } | 539 } |
519 | 540 |
520 void IncidentReportingService::OnReportUploadResult( | 541 void IncidentReportingService::OnReportUploadResult( |
521 UploadContext* context, | 542 UploadContext* context, |
522 IncidentReportUploader::Result result, | 543 IncidentReportUploader::Result result, |
523 scoped_ptr<ClientIncidentResponse> response) { | 544 scoped_ptr<ClientIncidentResponse> response) { |
524 DCHECK(thread_checker_.CalledOnValidThread()); | 545 DCHECK(thread_checker_.CalledOnValidThread()); |
525 | 546 |
526 UMA_HISTOGRAM_ENUMERATION( | 547 UMA_HISTOGRAM_ENUMERATION( |
527 "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS); | 548 "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS); |
528 | 549 |
529 // The upload is no longer outstanding, so take ownership of the context (from | 550 // The upload is no longer outstanding, so take ownership of the context (from |
530 // the collection of outstanding uploads) in this scope. | 551 // the collection of outstanding uploads) in this scope. |
531 ScopedVector<UploadContext>::iterator it( | 552 ScopedVector<UploadContext>::iterator it( |
532 std::find(uploads_.begin(), uploads_.end(), context)); | 553 std::find(uploads_.begin(), uploads_.end(), context)); |
533 DCHECK(it != uploads_.end()); | 554 DCHECK(it != uploads_.end()); |
534 scoped_ptr<UploadContext> upload(context); // == *it | 555 scoped_ptr<UploadContext> upload(context); // == *it |
535 *it = uploads_.back(); | 556 *it = uploads_.back(); |
536 uploads_.weak_erase(uploads_.end() - 1); | 557 uploads_.weak_erase(uploads_.end() - 1); |
537 | 558 |
538 if (result == IncidentReportUploader::UPLOAD_SUCCESS) | 559 if (result == IncidentReportUploader::UPLOAD_SUCCESS) |
539 HandleResponse(upload->report.Pass(), response.Pass()); | 560 HandleResponse(*upload); |
540 // else retry? | 561 // else retry? |
541 } | 562 } |
542 | 563 |
543 void IncidentReportingService::Observe( | 564 void IncidentReportingService::Observe( |
544 int type, | 565 int type, |
545 const content::NotificationSource& source, | 566 const content::NotificationSource& source, |
546 const content::NotificationDetails& details) { | 567 const content::NotificationDetails& details) { |
547 switch (type) { | 568 switch (type) { |
548 case chrome::NOTIFICATION_PROFILE_CREATED: { | 569 case chrome::NOTIFICATION_PROFILE_CREATED: { |
549 Profile* profile = content::Source<Profile>(source).ptr(); | 570 Profile* profile = content::Source<Profile>(source).ptr(); |
550 if (!profile->IsOffTheRecord()) | 571 if (!profile->IsOffTheRecord()) |
551 OnProfileCreated(profile); | 572 OnProfileCreated(profile); |
552 break; | 573 break; |
553 } | 574 } |
554 case chrome::NOTIFICATION_PROFILE_DESTROYED: { | 575 case chrome::NOTIFICATION_PROFILE_DESTROYED: { |
555 Profile* profile = content::Source<Profile>(source).ptr(); | 576 Profile* profile = content::Source<Profile>(source).ptr(); |
556 if (!profile->IsOffTheRecord()) | 577 if (!profile->IsOffTheRecord()) |
557 OnProfileDestroyed(profile); | 578 OnProfileDestroyed(profile); |
558 break; | 579 break; |
559 } | 580 } |
560 default: | 581 default: |
561 break; | 582 break; |
562 } | 583 } |
563 } | 584 } |
564 | 585 |
565 } // namespace safe_browsing | 586 } // namespace safe_browsing |
OLD | NEW |