Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(206)

Side by Side Diff: chrome/browser/safe_browsing/incident_reporting_service.cc

Issue 470213002: Moving all the incident reporting stuff to a common folder (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@binsign
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698