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