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