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 |