Skip to content

Commit

Permalink
More fine-grained pruning in safe browsing incident reporting service.
Browse files Browse the repository at this point in the history
BUG=396398

Review URL: https://codereview.chromium.org/411793004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285491 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
grt@chromium.org committed Jul 25, 2014
1 parent 9754c0b commit 93ef5ea
Show file tree
Hide file tree
Showing 13 changed files with 413 additions and 38 deletions.
7 changes: 6 additions & 1 deletion chrome/browser/prefs/chrome_pref_service_factory.cc
Expand Up @@ -195,10 +195,15 @@ const PrefHashFilter::TrackedPreferenceMetadata kTrackedPrefs[] = {
PrefHashFilter::ENFORCE_ON_LOAD,
PrefHashFilter::TRACKING_STRATEGY_ATOMIC
},
{
18, prefs::kSafeBrowsingIncidentsSent,
PrefHashFilter::ENFORCE_ON_LOAD,
PrefHashFilter::TRACKING_STRATEGY_ATOMIC
},
};

// The count of tracked preferences IDs across all platforms.
const size_t kTrackedPrefsReportingIDsCount = 18;
const size_t kTrackedPrefsReportingIDsCount = 19;
COMPILE_ASSERT(kTrackedPrefsReportingIDsCount >= arraysize(kTrackedPrefs),
need_to_increment_ids_count);

Expand Down
3 changes: 3 additions & 0 deletions chrome/browser/profiles/profile.cc
Expand Up @@ -118,6 +118,9 @@ void Profile::RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
prefs::kSafeBrowsingIncidentReportSent,
false,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
registry->RegisterDictionaryPref(
prefs::kSafeBrowsingIncidentsSent,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
#if defined(ENABLE_GOOGLE_NOW)
registry->RegisterBooleanPref(
prefs::kGoogleGeolocationAccessEnabled,
Expand Down
26 changes: 26 additions & 0 deletions chrome/browser/safe_browsing/incident_handler_util.cc
@@ -0,0 +1,26 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/safe_browsing/incident_handler_util.h"

#include <string>

#include "base/hash.h"
#include "base/logging.h"
#include "third_party/protobuf/src/google/protobuf/message_lite.h"

namespace safe_browsing {

// Computes a simple hash digest over the serialized form of |message|.
// |message| must be in a canonical form.
uint32_t HashMessage(const google::protobuf::MessageLite& message) {
std::string message_string;
if (!message.SerializeToString(&message_string)) {
NOTREACHED();
return 0;
}
return base::Hash(message_string);
}

} // namespace safe_browsing
27 changes: 27 additions & 0 deletions chrome/browser/safe_browsing/incident_handler_util.h
@@ -0,0 +1,27 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_SAFE_BROWSING_INCIDENT_HANDLER_UTIL_H_
#define CHROME_BROWSER_SAFE_BROWSING_INCIDENT_HANDLER_UTIL_H_

#include <stdint.h>

namespace google {
namespace protobuf {

class MessageLite;

} // namespace protobuf
} // namespace google

namespace safe_browsing {

// Computes a simple hash digest over the serialized form of |message|.
// |message| must be in a canonical form. For example, fields set to their
// default values should be cleared.
uint32_t HashMessage(const google::protobuf::MessageLite& message);

} // namespace safe_browsing

#endif // CHROME_BROWSER_SAFE_BROWSING_INCIDENT_HANDLER_UTIL_H_
188 changes: 154 additions & 34 deletions chrome/browser/safe_browsing/incident_reporting_service.cc
Expand Up @@ -11,9 +11,12 @@

#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/process/process_info.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h"
Expand All @@ -23,6 +26,7 @@
#include "chrome/browser/safe_browsing/incident_report_uploader_impl.h"
#include "chrome/browser/safe_browsing/preference_validation_delegate.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/safe_browsing/tracked_preference_incident_handlers.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/safe_browsing/csd.pb.h"
#include "content/public/browser/browser_thread.h"
Expand All @@ -33,28 +37,67 @@ namespace safe_browsing {

namespace {

// The type of an incident. Used for user metrics and for pruning of
// previously-reported incidents.
enum IncidentType {
// Start with 1 rather than zero; otherwise there won't be enough buckets for
// the histogram.
TRACKED_PREFERENCE = 1,
// Values for new incident types go here.
NUM_INCIDENT_TYPES
};

// The action taken for an incident; used for user metrics (see
// LogIncidentDataType).
enum IncidentDisposition {
DROPPED,
ACCEPTED,
};

// The state persisted for a specific instance of an incident to enable pruning
// of previously-reported incidents.
struct PersistentIncidentState {
// The type of the incident.
IncidentType type;

// The key for a specific instance of an incident.
std::string key;

// A hash digest representing a specific instance of an incident.
uint32_t digest;
};

// The amount of time the service will wait to collate incidents.
const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute

void LogIncidentDataType(
IncidentDisposition disposition,
// Returns the number of incidents contained in |incident|. The result is
// expected to be 1. Used in DCHECKs.
size_t CountIncidents(const ClientIncidentReport_IncidentData& incident) {
size_t result = 0;
if (incident.has_tracked_preference())
++result;
// Add detection for new incident types here.
return result;
}

// Returns the type of incident contained in |incident_data|.
IncidentType GetIncidentType(
const ClientIncidentReport_IncidentData& incident_data) {
IncidentType type = TRACKED_PREFERENCE;
if (incident_data.has_tracked_preference())
return TRACKED_PREFERENCE;

// Add a switch statement once other types are supported.
DCHECK(incident_data.has_tracked_preference());
// Add detection for new incident types here.
COMPILE_ASSERT(TRACKED_PREFERENCE + 1 == NUM_INCIDENT_TYPES,
add_support_for_new_types);
NOTREACHED();
return NUM_INCIDENT_TYPES;
}

// Logs the type of incident in |incident_data| to a user metrics histogram.
void LogIncidentDataType(
IncidentDisposition disposition,
const ClientIncidentReport_IncidentData& incident_data) {
IncidentType type = GetIncidentType(incident_data);
if (disposition == ACCEPTED) {
UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES);
} else {
Expand All @@ -64,6 +107,56 @@ void LogIncidentDataType(
}
}

// Computes the persistent state for an incident.
PersistentIncidentState ComputeIncidentState(
const ClientIncidentReport_IncidentData& incident) {
PersistentIncidentState state = {GetIncidentType(incident)};
switch (state.type) {
case TRACKED_PREFERENCE:
state.key = GetTrackedPreferenceIncidentKey(incident);
state.digest = GetTrackedPreferenceIncidentDigest(incident);
break;
// Add handling for new incident types here.
default:
COMPILE_ASSERT(TRACKED_PREFERENCE + 1 == NUM_INCIDENT_TYPES,
add_support_for_new_types);
NOTREACHED();
break;
}
return state;
}

// Returns true if the incident described by |state| has already been reported
// based on the bookkeeping in the |incidents_sent| preference dictionary.
bool IncidentHasBeenReported(const base::DictionaryValue* incidents_sent,
const PersistentIncidentState& state) {
const base::DictionaryValue* type_dict = NULL;
std::string digest_string;
return (incidents_sent &&
incidents_sent->GetDictionaryWithoutPathExpansion(
base::IntToString(state.type), &type_dict) &&
type_dict->GetStringWithoutPathExpansion(state.key, &digest_string) &&
digest_string == base::UintToString(state.digest));
}

// Marks the incidents described by |states| as having been reported
// in |incidents_set|.
void MarkIncidentsAsReported(const std::vector<PersistentIncidentState>& states,
base::DictionaryValue* incidents_sent) {
for (size_t i = 0; i < states.size(); ++i) {
const PersistentIncidentState& data = states[i];
base::DictionaryValue* type_dict = NULL;
const std::string type_string(base::IntToString(data.type));
if (!incidents_sent->GetDictionaryWithoutPathExpansion(type_string,
&type_dict)) {
type_dict = new base::DictionaryValue();
incidents_sent->SetWithoutPathExpansion(type_string, type_dict);
}
type_dict->SetStringWithoutPathExpansion(data.key,
base::UintToString(data.digest));
}
}

} // namespace

struct IncidentReportingService::ProfileContext {
Expand All @@ -82,6 +175,9 @@ struct IncidentReportingService::ProfileContext {

class IncidentReportingService::UploadContext {
public:
typedef std::map<Profile*, std::vector<PersistentIncidentState> >
PersistentIncidentStateCollection;

explicit UploadContext(scoped_ptr<ClientIncidentReport> report);
~UploadContext();

Expand All @@ -91,8 +187,8 @@ class IncidentReportingService::UploadContext {
// The uploader in use. This is NULL until the CSD killswitch is checked.
scoped_ptr<IncidentReportUploader> uploader;

// The set of profiles from which incidents in |report| originated.
std::vector<Profile*> profiles;
// A mapping of profiles to the data to be persisted upon successful upload.
PersistentIncidentStateCollection profiles_to_state;

private:
DISALLOW_COPY_AND_ASSIGN(UploadContext);
Expand Down Expand Up @@ -261,17 +357,9 @@ void IncidentReportingService::OnProfileDestroyed(Profile* profile) {
delete it->second;
profiles_.erase(it);

// Remove the association with this profile from any pending uploads.
for (size_t i = 0; i < uploads_.size(); ++i) {
UploadContext* upload = uploads_[i];
std::vector<Profile*>::iterator it =
std::find(upload->profiles.begin(), upload->profiles.end(), profile);
if (it != upload->profiles.end()) {
*it = upload->profiles.back();
upload->profiles.resize(upload->profiles.size() - 1);
break;
}
}
// Remove the association with this profile from all pending uploads.
for (size_t i = 0; i < uploads_.size(); ++i)
uploads_[i]->profiles_to_state.erase(profile);
}

void IncidentReportingService::AddIncident(
Expand All @@ -280,6 +368,7 @@ void IncidentReportingService::AddIncident(
DCHECK(thread_checker_.CalledOnValidThread());
// Incidents outside the context of a profile are not supported at the moment.
DCHECK(profile);
DCHECK_EQ(1U, CountIncidents(*incident_data));

ProfileContext* context = GetProfileContext(profile);
// It is forbidden to call this function with a destroyed profile.
Expand Down Expand Up @@ -516,10 +605,10 @@ void IncidentReportingService::UploadIfCollectionComplete() {
process->set_extended_consent(false);
// Collect incidents across all profiles participating in safe browsing. Drop
// incidents if the profile stopped participating before collection completed.
// Prune incidents if the profile has already submitted any incidents.
// Associate the participating profiles with the upload.
// Prune previously submitted incidents.
// Associate the profiles and their incident data with the upload.
size_t prune_count = 0;
std::vector<Profile*> profiles;
UploadContext::PersistentIncidentStateCollection profiles_to_state;
for (ProfileContextCollection::iterator scan = profiles_.begin();
scan != profiles_.end();
++scan) {
Expand All @@ -537,21 +626,48 @@ void IncidentReportingService::UploadIfCollectionComplete() {
LogIncidentDataType(DROPPED, *context->incidents[i]);
}
context->incidents.clear();
} else if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) {
// Prune all incidents.
// TODO(grt): Only prune previously submitted incidents;
// http://crbug.com/383043.
prune_count += context->incidents.size();
continue;
}
std::vector<PersistentIncidentState> states;
const base::DictionaryValue* incidents_sent =
prefs->GetDictionary(prefs::kSafeBrowsingIncidentsSent);
// Prep persistent data and prune any incidents already sent.
for (size_t i = 0; i < context->incidents.size(); ++i) {
ClientIncidentReport_IncidentData* incident = context->incidents[i];
const PersistentIncidentState state = ComputeIncidentState(*incident);
if (IncidentHasBeenReported(incidents_sent, state)) {
++prune_count;
delete context->incidents[i];
context->incidents[i] = NULL;
} else {
states.push_back(state);
}
}
if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) {
// Prune all incidents as if they had been reported, migrating to the new
// technique. TODO(grt): remove this branch after it has shipped.
for (size_t i = 0; i < context->incidents.size(); ++i) {
if (context->incidents[i])
++prune_count;
}
context->incidents.clear();
prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent);
DictionaryPrefUpdate pref_update(prefs,
prefs::kSafeBrowsingIncidentsSent);
MarkIncidentsAsReported(states, pref_update.Get());
} else {
for (size_t i = 0; i < context->incidents.size(); ++i) {
ClientIncidentReport_IncidentData* incident = context->incidents[i];
LogIncidentDataType(ACCEPTED, *incident);
// Ownership of the incident is passed to the report.
report->mutable_incident()->AddAllocated(incident);
if (incident) {
LogIncidentDataType(ACCEPTED, *incident);
// Ownership of the incident is passed to the report.
report->mutable_incident()->AddAllocated(incident);
}
}
context->incidents.weak_clear();
profiles.push_back(scan->first);
std::vector<PersistentIncidentState>& profile_states =
profiles_to_state[scan->first];
profile_states.swap(states);
}
}

Expand All @@ -573,7 +689,7 @@ void IncidentReportingService::UploadIfCollectionComplete() {
return;

scoped_ptr<UploadContext> context(new UploadContext(report.Pass()));
context->profiles.swap(profiles);
context->profiles_to_state.swap(profiles_to_state);
if (!database_manager_) {
// No database manager during testing. Take ownership of the context and
// continue processing.
Expand Down Expand Up @@ -628,9 +744,13 @@ void IncidentReportingService::OnKillSwitchResult(UploadContext* context,
}

void IncidentReportingService::HandleResponse(const UploadContext& context) {
for (size_t i = 0; i < context.profiles.size(); ++i) {
context.profiles[i]->GetPrefs()->SetBoolean(
prefs::kSafeBrowsingIncidentReportSent, true);
for (UploadContext::PersistentIncidentStateCollection::const_iterator scan =
context.profiles_to_state.begin();
scan != context.profiles_to_state.end();
++scan) {
DictionaryPrefUpdate pref_update(scan->first->GetPrefs(),
prefs::kSafeBrowsingIncidentsSent);
MarkIncidentsAsReported(scan->second, pref_update.Get());
}
}

Expand Down

0 comments on commit 93ef5ea

Please sign in to comment.