1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/metrics/entropy_state.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/metrics/histogram_functions.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"
16 #if BUILDFLAG(IS_ANDROID)
17 #include "base/android/jni_android.h"
18 #include "components/metrics/jni_headers/LowEntropySource_jni.h"
19 #endif // BUILDFLAG(IS_ANDROID)
25 #if BUILDFLAG(IS_CHROMEOS_LACROS)
26 // Needed for a check to see if we retrieved entropy values before we have
27 // transferred them from Ash.
28 bool g_entropy_source_has_been_retrieved = false;
29 bool g_entropy_source_has_been_set = false;
32 // Generates a new non-identifying entropy source used to seed persistent
33 // activities. Make it static so that the new low entropy source value will
34 // only be generated on first access. And thus, even though we may write the
35 // new low entropy source value to prefs multiple times, it stays the same
37 int GenerateLowEntropySource() {
38 #if BUILDFLAG(IS_ANDROID)
39 // Note: As in the non-Android case below, the Java implementation also uses
40 // a static cache, so subsequent invocations will return the same value.
41 JNIEnv* env = base::android::AttachCurrentThread();
42 return Java_LowEntropySource_generateLowEntropySource(env);
44 static const int low_entropy_source =
45 base::RandInt(0, EntropyState::kMaxLowEntropySize - 1);
46 return low_entropy_source;
47 #endif // BUILDFLAG(IS_ANDROID)
50 // Generates a new non-identifying low entropy source using the same method
51 // that's used for the actual low entropy source. This one, however, is only
52 // used for statistical validation, and *not* for randomization or experiment
54 int GeneratePseudoLowEntropySource() {
55 #if BUILDFLAG(IS_ANDROID)
56 // Note: As in the non-Android case below, the Java implementation also uses
57 // a static cache, so subsequent invocations will return the same value.
58 JNIEnv* env = base::android::AttachCurrentThread();
59 return Java_LowEntropySource_generatePseudoLowEntropySource(env);
61 static const int pseudo_low_entropy_source =
62 base::RandInt(0, EntropyState::kMaxLowEntropySize - 1);
63 return pseudo_low_entropy_source;
64 #endif // BUILDFLAG(IS_ANDROID)
69 EntropyState::EntropyState(PrefService* local_state)
70 : local_state_(local_state) {}
73 constexpr int EntropyState::kLowEntropySourceNotSet;
76 void EntropyState::ClearPrefs(PrefService* local_state) {
77 #if BUILDFLAG(IS_CHROMEOS_LACROS)
78 // There are currently multiple EntropyState objects (crbug/1495576) and as
79 // Lacros does not own the entropy values anyways, it shouldn't clear them
81 LOG(WARNING) << "EntropyState::ClearPrefs ignored as set remotely.";
83 local_state->ClearPref(prefs::kMetricsLowEntropySource);
84 local_state->ClearPref(prefs::kMetricsOldLowEntropySource);
85 local_state->ClearPref(prefs::kMetricsPseudoLowEntropySource);
90 void EntropyState::RegisterPrefs(PrefRegistrySimple* registry) {
91 registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource,
92 kLowEntropySourceNotSet);
93 registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource,
94 kLowEntropySourceNotSet);
95 registry->RegisterIntegerPref(prefs::kMetricsPseudoLowEntropySource,
96 kLowEntropySourceNotSet);
99 #if BUILDFLAG(IS_CHROMEOS_LACROS)
101 void EntropyState::SetExternalPrefs(PrefService* local_state,
102 int low_entropy_source,
103 int old_low_entropy_source,
104 int pseudo_low_entropy_source) {
105 if (!g_entropy_source_has_been_set) {
106 g_entropy_source_has_been_set = true;
107 // As an |EntropyState| object has an internal state, we need to make sure
108 // that none gets read before the Ash values have been transferred.
109 // This is usually taken care of by
110 // `ChromeMetricsServicesManagerClient::GetMetricsStateManager` which first
111 // sets the Ash values and then creates the `MetricsStateManager`.
112 if (g_entropy_source_has_been_retrieved) {
113 LOG(ERROR) << "Entropy value was retrieved before they were updated";
115 DCHECK(!g_entropy_source_has_been_retrieved);
117 local_state->SetInteger(prefs::kMetricsLowEntropySource, low_entropy_source);
118 local_state->SetInteger(prefs::kMetricsOldLowEntropySource,
119 old_low_entropy_source);
120 local_state->SetInteger(prefs::kMetricsPseudoLowEntropySource,
121 pseudo_low_entropy_source);
125 std::string EntropyState::GetHighEntropySource(
126 const std::string& initial_client_id) {
127 DCHECK(!initial_client_id.empty());
128 // For metrics reporting-enabled users, we combine the client ID and low
129 // entropy source to get the final entropy source.
130 // This has two useful properties:
131 // 1) It makes the entropy source less identifiable for parties that do not
132 // know the low entropy source.
133 // 2) It makes the final entropy source resettable.
135 // If this install has an old low entropy source, continue using it, to avoid
136 // changing the group assignments of studies using high entropy. New installs
137 // only have the new low entropy source. If the number of installs with old
138 // sources ever becomes small enough (see UMA.LowEntropySourceValue), we could
139 // remove it, and just use the new source here.
140 int low_entropy_source = GetOldLowEntropySource();
141 if (low_entropy_source == kLowEntropySourceNotSet)
142 low_entropy_source = GetLowEntropySource();
144 return initial_client_id + base::NumberToString(low_entropy_source);
147 int EntropyState::GetLowEntropySource() {
148 UpdateLowEntropySources();
149 return low_entropy_source_;
152 int EntropyState::GetPseudoLowEntropySource() {
153 UpdateLowEntropySources();
154 return pseudo_low_entropy_source_;
157 int EntropyState::GetOldLowEntropySource() {
158 UpdateLowEntropySources();
159 return old_low_entropy_source_;
162 void EntropyState::UpdateLowEntropySources() {
163 #if BUILDFLAG(IS_CHROMEOS_LACROS)
164 // Coming here, someone was reading an entropy value.
165 g_entropy_source_has_been_retrieved = true;
167 // The default value for |low_entropy_source_| and the default pref value are
168 // both |kLowEntropySourceNotSet|, which indicates the value has not been set.
169 if (low_entropy_source_ != kLowEntropySourceNotSet &&
170 pseudo_low_entropy_source_ != kLowEntropySourceNotSet)
173 const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
174 // Only try to load the value from prefs if the user did not request a reset.
175 // Otherwise, skip to generating a new value. We would have already returned
176 // if both |low_entropy_source_| and |pseudo_low_entropy_source_| were set,
177 // ensuring we only do this reset on the first call to
178 // UpdateLowEntropySources().
179 if (!command_line->HasSwitch(switches::kResetVariationState)) {
180 int new_pref = local_state_->GetInteger(prefs::kMetricsLowEntropySource);
181 if (IsValidLowEntropySource(new_pref))
182 low_entropy_source_ = new_pref;
183 int old_pref = local_state_->GetInteger(prefs::kMetricsOldLowEntropySource);
184 if (IsValidLowEntropySource(old_pref))
185 old_low_entropy_source_ = old_pref;
187 local_state_->GetInteger(prefs::kMetricsPseudoLowEntropySource);
188 if (IsValidLowEntropySource(pseudo_pref))
189 pseudo_low_entropy_source_ = pseudo_pref;
192 // If the new source is missing or corrupt (or requested to be reset), then
193 // (re)create it. Don't bother recreating the old source if it's corrupt,
194 // because we only keep the old source around for consistency, and we can't
195 // maintain a consistent value if we recreate it.
196 if (low_entropy_source_ == kLowEntropySourceNotSet) {
197 low_entropy_source_ = GenerateLowEntropySource();
198 DCHECK(IsValidLowEntropySource(low_entropy_source_));
199 local_state_->SetInteger(prefs::kMetricsLowEntropySource,
200 low_entropy_source_);
203 // If the pseudo source is missing or corrupt (or requested to be reset), then
204 // (re)create it. Don't bother recreating the old source if it's corrupt,
205 // because we only keep the old source around for consistency, and we can't
206 // maintain a consistent value if we recreate it.
207 if (pseudo_low_entropy_source_ == kLowEntropySourceNotSet) {
208 pseudo_low_entropy_source_ = GeneratePseudoLowEntropySource();
209 DCHECK(IsValidLowEntropySource(pseudo_low_entropy_source_));
210 local_state_->SetInteger(prefs::kMetricsPseudoLowEntropySource,
211 pseudo_low_entropy_source_);
214 // If the old source was present but corrupt (or requested to be reset), then
215 // we'll never use it again, so delete it.
216 if (old_low_entropy_source_ == kLowEntropySourceNotSet &&
217 local_state_->HasPrefPath(prefs::kMetricsOldLowEntropySource)) {
218 local_state_->ClearPref(prefs::kMetricsOldLowEntropySource);
221 DCHECK_NE(low_entropy_source_, kLowEntropySourceNotSet);
225 bool EntropyState::IsValidLowEntropySource(int value) {
226 return value >= 0 && value < kMaxLowEntropySize;
229 } // namespace metrics