1 // Copyright 2020 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 "components/metrics/entropy_state.h"
6 
7 #include "base/command_line.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/no_destructor.h"
10 #include "base/rand_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "components/metrics/metrics_pref_names.h"
13 #include "components/metrics/metrics_switches.h"
14 #include "components/prefs/pref_service.h"
15 
16 namespace metrics {
17 
18 namespace {
19 
20 // The argument used to generate a non-identifying entropy source. We want no
21 // more than 13 bits of entropy, so use this max to return a number in the range
22 // [0, 7999] as the entropy source (12.97 bits of entropy).
23 const int kMaxLowEntropySize = 8000;
24 
25 // Generates a new non-identifying entropy source used to seed persistent
26 // activities. Using a NoDestructor so that the new low entropy source value
27 // will only be generated on first access. And thus, even though we may write
28 // the new low entropy source value to prefs multiple times, it stays the same
29 // value.
GenerateLowEntropySource()30 int GenerateLowEntropySource() {
31   static const base::NoDestructor<int> low_entropy_source(
32       [] { return base::RandInt(0, kMaxLowEntropySize - 1); }());
33   return *low_entropy_source;
34 }
35 
36 }  // namespace
37 
EntropyState(PrefService * local_state)38 EntropyState::EntropyState(PrefService* local_state)
39     : local_state_(local_state) {}
40 
41 // static
42 constexpr int EntropyState::kLowEntropySourceNotSet;
43 
44 // static
ClearPrefs(PrefService * local_state)45 void EntropyState::ClearPrefs(PrefService* local_state) {
46   local_state->ClearPref(prefs::kMetricsLowEntropySource);
47   local_state->ClearPref(prefs::kMetricsOldLowEntropySource);
48 }
49 
50 // static
RegisterPrefs(PrefRegistrySimple * registry)51 void EntropyState::RegisterPrefs(PrefRegistrySimple* registry) {
52   registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource,
53                                 kLowEntropySourceNotSet);
54   registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource,
55                                 kLowEntropySourceNotSet);
56 }
57 
GetHighEntropySource(const std::string & initial_client_id)58 std::string EntropyState::GetHighEntropySource(
59     const std::string& initial_client_id) {
60   DCHECK(!initial_client_id.empty());
61   // For metrics reporting-enabled users, we combine the client ID and low
62   // entropy source to get the final entropy source.
63   // This has two useful properties:
64   //  1) It makes the entropy source less identifiable for parties that do not
65   //     know the low entropy source.
66   //  2) It makes the final entropy source resettable.
67 
68   // If this install has an old low entropy source, continue using it, to avoid
69   // changing the group assignments of studies using high entropy. New installs
70   // only have the new low entropy source. If the number of installs with old
71   // sources ever becomes small enough (see UMA.LowEntropySourceValue), we could
72   // remove it, and just use the new source here.
73   int low_entropy_source = GetOldLowEntropySource();
74   if (low_entropy_source == kLowEntropySourceNotSet)
75     low_entropy_source = GetLowEntropySource();
76 
77   return initial_client_id + base::NumberToString(low_entropy_source);
78 }
79 
GetLowEntropySource()80 int EntropyState::GetLowEntropySource() {
81   UpdateLowEntropySources();
82   return low_entropy_source_;
83 }
84 
GetOldLowEntropySource()85 int EntropyState::GetOldLowEntropySource() {
86   UpdateLowEntropySources();
87   return old_low_entropy_source_;
88 }
89 
UpdateLowEntropySources()90 void EntropyState::UpdateLowEntropySources() {
91   // The default value for |low_entropy_source_| and the default pref value are
92   // both |kLowEntropySourceNotSet|, which indicates the value has not been set.
93   if (low_entropy_source_ != kLowEntropySourceNotSet)
94     return;
95 
96   const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
97   // Only try to load the value from prefs if the user did not request a reset.
98   // Otherwise, skip to generating a new value. We would have already returned
99   // if |low_entropy_source_| were set, ensuring we only do this reset on the
100   // first call to UpdateLowEntropySources().
101   if (!command_line->HasSwitch(switches::kResetVariationState)) {
102     int new_pref = local_state_->GetInteger(prefs::kMetricsLowEntropySource);
103     if (IsValidLowEntropySource(new_pref))
104       low_entropy_source_ = new_pref;
105     int old_pref = local_state_->GetInteger(prefs::kMetricsOldLowEntropySource);
106     if (IsValidLowEntropySource(old_pref))
107       old_low_entropy_source_ = old_pref;
108   }
109 
110   // If the new source is missing or corrupt (or requested to be reset), then
111   // (re)create it. Don't bother recreating the old source if it's corrupt,
112   // because we only keep the old source around for consistency, and we can't
113   // maintain a consistent value if we recreate it.
114   if (low_entropy_source_ == kLowEntropySourceNotSet) {
115     low_entropy_source_ = GenerateLowEntropySource();
116     DCHECK(IsValidLowEntropySource(low_entropy_source_));
117     local_state_->SetInteger(prefs::kMetricsLowEntropySource,
118                              low_entropy_source_);
119   }
120 
121   // If the old source was present but corrupt (or requested to be reset), then
122   // we'll never use it again, so delete it.
123   if (old_low_entropy_source_ == kLowEntropySourceNotSet &&
124       local_state_->HasPrefPath(prefs::kMetricsOldLowEntropySource)) {
125     local_state_->ClearPref(prefs::kMetricsOldLowEntropySource);
126   }
127 
128   DCHECK_NE(low_entropy_source_, kLowEntropySourceNotSet);
129   // TODO(crbug/1041710): Currently, these metrics might be recorded multiple
130   // times but that shouldn't matter because we can workaround it by using count
131   // unique user mode. Also, once we verify that we can persist
132   // low_entropy_source to our system profile proto, These two metrics are
133   // longer needed and should be removed.
134   base::UmaHistogramSparse("UMA.LowEntropySource3Value", low_entropy_source_);
135   if (old_low_entropy_source_ != kLowEntropySourceNotSet) {
136     base::UmaHistogramSparse("UMA.LowEntropySourceValue",
137                              old_low_entropy_source_);
138   }
139 }
140 
141 // static
IsValidLowEntropySource(int value)142 bool EntropyState::IsValidLowEntropySource(int value) {
143   return value >= 0 && value < kMaxLowEntropySize;
144 }
145 
146 }  // namespace metrics
147