xref: /aosp_15_r20/external/cronet/components/metrics/entropy_state.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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.
4 
5 #include "components/metrics/entropy_state.h"
6 
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 "base/token.h"
13 #include "base/unguessable_token.h"
14 #include "components/metrics/metrics_pref_names.h"
15 #include "components/metrics/metrics_switches.h"
16 #include "components/prefs/pref_service.h"
17 
18 #if BUILDFLAG(IS_ANDROID)
19 #include "base/android/jni_android.h"
20 #include "components/metrics/jni_headers/LowEntropySource_jni.h"
21 #endif  // BUILDFLAG(IS_ANDROID)
22 
23 namespace metrics {
24 
25 namespace {
26 
27 #if BUILDFLAG(IS_CHROMEOS_LACROS)
28 // Needed for a check to see if we retrieved entropy values before we have
29 // transferred them from Ash.
30 bool g_entropy_source_has_been_retrieved = false;
31 bool g_entropy_source_has_been_set = false;
32 #endif
33 
34 // Generates a new non-identifying entropy source used to seed persistent
35 // activities. Make it static so that the new low entropy source value will
36 // only be generated on first access. And thus, even though we may write the
37 // new low entropy source value to prefs multiple times, it stays the same
38 // value.
GenerateLowEntropySource()39 int GenerateLowEntropySource() {
40 #if BUILDFLAG(IS_ANDROID)
41   // Note: As in the non-Android case below, the Java implementation also uses
42   // a static cache, so subsequent invocations will return the same value.
43   JNIEnv* env = base::android::AttachCurrentThread();
44   return Java_LowEntropySource_generateLowEntropySource(env);
45 #else
46   static const int low_entropy_source =
47       base::RandInt(0, EntropyState::kMaxLowEntropySize - 1);
48   return low_entropy_source;
49 #endif  // BUILDFLAG(IS_ANDROID)
50 }
51 
52 // Generates a new non-identifying low entropy source using the same method
53 // that's used for the actual low entropy source. This one, however, is only
54 // used for statistical validation, and *not* for randomization or experiment
55 // assignment.
GeneratePseudoLowEntropySource()56 int GeneratePseudoLowEntropySource() {
57 #if BUILDFLAG(IS_ANDROID)
58   // Note: As in the non-Android case below, the Java implementation also uses
59   // a static cache, so subsequent invocations will return the same value.
60   JNIEnv* env = base::android::AttachCurrentThread();
61   return Java_LowEntropySource_generatePseudoLowEntropySource(env);
62 #else
63   static const int pseudo_low_entropy_source =
64       base::RandInt(0, EntropyState::kMaxLowEntropySize - 1);
65   return pseudo_low_entropy_source;
66 #endif  // BUILDFLAG(IS_ANDROID)
67 }
68 
69 }  // namespace
70 
EntropyState(PrefService * local_state)71 EntropyState::EntropyState(PrefService* local_state)
72     : local_state_(local_state) {}
73 
74 // static
75 constexpr int EntropyState::kLowEntropySourceNotSet;
76 
77 // static
ClearPrefs(PrefService * local_state)78 void EntropyState::ClearPrefs(PrefService* local_state) {
79 #if BUILDFLAG(IS_CHROMEOS_LACROS)
80   // There are currently multiple EntropyState objects (crbug/1495576) and as
81   // Lacros does not own the entropy values anyways, it shouldn't clear them
82   // either.
83   LOG(WARNING) << "EntropyState::ClearPrefs ignored as set remotely.";
84 #else
85   local_state->ClearPref(prefs::kMetricsLowEntropySource);
86   local_state->ClearPref(prefs::kMetricsOldLowEntropySource);
87   local_state->ClearPref(prefs::kMetricsPseudoLowEntropySource);
88   local_state->ClearPref(prefs::kMetricsLimitedEntropyRandomizationSource);
89 #endif
90 }
91 
92 // static
RegisterPrefs(PrefRegistrySimple * registry)93 void EntropyState::RegisterPrefs(PrefRegistrySimple* registry) {
94   registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource,
95                                 kLowEntropySourceNotSet);
96   registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource,
97                                 kLowEntropySourceNotSet);
98   registry->RegisterIntegerPref(prefs::kMetricsPseudoLowEntropySource,
99                                 kLowEntropySourceNotSet);
100   registry->RegisterStringPref(prefs::kMetricsLimitedEntropyRandomizationSource,
101                                std::string());
102 }
103 
104 #if BUILDFLAG(IS_CHROMEOS_LACROS)
105 // static
SetExternalPrefs(PrefService * local_state,int low_entropy_source,int old_low_entropy_source,int pseudo_low_entropy_source,std::string_view limited_entropy_randomization_source)106 void EntropyState::SetExternalPrefs(
107     PrefService* local_state,
108     int low_entropy_source,
109     int old_low_entropy_source,
110     int pseudo_low_entropy_source,
111     std::string_view limited_entropy_randomization_source) {
112   if (!g_entropy_source_has_been_set) {
113     g_entropy_source_has_been_set = true;
114     // As an |EntropyState| object has an internal state, we need to make sure
115     // that none gets read before the Ash values have been transferred.
116     // This is usually taken care of by
117     // `ChromeMetricsServicesManagerClient::GetMetricsStateManager` which first
118     // sets the Ash values and then creates the `MetricsStateManager`.
119     if (g_entropy_source_has_been_retrieved) {
120       LOG(ERROR) << "Entropy value was retrieved before they were updated";
121     }
122     DCHECK(!g_entropy_source_has_been_retrieved);
123   }
124   local_state->SetInteger(prefs::kMetricsLowEntropySource, low_entropy_source);
125   local_state->SetInteger(prefs::kMetricsOldLowEntropySource,
126                           old_low_entropy_source);
127   local_state->SetInteger(prefs::kMetricsPseudoLowEntropySource,
128                           pseudo_low_entropy_source);
129   if (IsValidLimitedEntropyRandomizationSource(
130           limited_entropy_randomization_source)) {
131     local_state->SetString(prefs::kMetricsLimitedEntropyRandomizationSource,
132                            limited_entropy_randomization_source);
133   }
134 }
135 #endif
136 
GetHighEntropySource(const std::string & initial_client_id)137 std::string EntropyState::GetHighEntropySource(
138     const std::string& initial_client_id) {
139   DCHECK(!initial_client_id.empty());
140   // For metrics reporting-enabled users, we combine the client ID and low
141   // entropy source to get the final entropy source.
142   // This has two useful properties:
143   //  1) It makes the entropy source less identifiable for parties that do not
144   //     know the low entropy source.
145   //  2) It makes the final entropy source resettable.
146 
147   // If this install has an old low entropy source, continue using it, to avoid
148   // changing the group assignments of studies using high entropy. New installs
149   // only have the new low entropy source. If the number of installs with old
150   // sources ever becomes small enough (see UMA.LowEntropySourceValue), we could
151   // remove it, and just use the new source here.
152   int low_entropy_source = GetOldLowEntropySource();
153   if (low_entropy_source == kLowEntropySourceNotSet)
154     low_entropy_source = GetLowEntropySource();
155 
156   return initial_client_id + base::NumberToString(low_entropy_source);
157 }
158 
GetLowEntropySource()159 int EntropyState::GetLowEntropySource() {
160   UpdateLowEntropySources();
161   return low_entropy_source_;
162 }
163 
GetPseudoLowEntropySource()164 int EntropyState::GetPseudoLowEntropySource() {
165   UpdateLowEntropySources();
166   return pseudo_low_entropy_source_;
167 }
168 
GetOldLowEntropySource()169 int EntropyState::GetOldLowEntropySource() {
170   UpdateLowEntropySources();
171   return old_low_entropy_source_;
172 }
173 
GenerateLimitedEntropyRandomizationSource()174 std::string EntropyState::GenerateLimitedEntropyRandomizationSource() {
175   // Uses a cryptographically strong random source to generate a random 128 bit
176   // value. The value cannot be all zeros.
177   auto token = base::UnguessableToken::Create().ToString();
178   DCHECK(IsValidLimitedEntropyRandomizationSource(token));
179   return token;
180 }
181 
GetLimitedEntropyRandomizationSource()182 std::string_view EntropyState::GetLimitedEntropyRandomizationSource() {
183   UpdateLimitedEntropyRandomizationSource();
184   return limited_entropy_randomization_source_;
185 }
186 
UpdateLimitedEntropyRandomizationSource()187 void EntropyState::UpdateLimitedEntropyRandomizationSource() {
188   // The default value for limited entropy randomization source is an empty
189   // string. If it's not empty, it must have been set during this session and an
190   // update is not needed.
191   if (!limited_entropy_randomization_source_.empty()) {
192     return;
193   }
194 
195   auto* pref_name = prefs::kMetricsLimitedEntropyRandomizationSource;
196   const auto* command_line = base::CommandLine::ForCurrentProcess();
197   // Load the previously set value from prefs, unless the reset variations state
198   // command line flag is given.
199   if (!command_line->HasSwitch(switches::kResetVariationState)) {
200     auto pref_value = local_state_->GetString(pref_name);
201     if (IsValidLimitedEntropyRandomizationSource(pref_value)) {
202       limited_entropy_randomization_source_ = pref_value;
203     }
204   }
205 
206   // If a previously set value is not found, or if the the reset variations
207   // state command line flag is given, generate a new value and store it into
208   // prefs.
209   if (limited_entropy_randomization_source_.empty()) {
210     limited_entropy_randomization_source_ =
211         GenerateLimitedEntropyRandomizationSource();
212     local_state_->SetString(pref_name, limited_entropy_randomization_source_);
213   }
214 
215   CHECK(!limited_entropy_randomization_source_.empty());
216 }
217 
UpdateLowEntropySources()218 void EntropyState::UpdateLowEntropySources() {
219 #if BUILDFLAG(IS_CHROMEOS_LACROS)
220   // Coming here, someone was reading an entropy value.
221   g_entropy_source_has_been_retrieved = true;
222 #endif
223   // The default value for |low_entropy_source_| and the default pref value are
224   // both |kLowEntropySourceNotSet|, which indicates the value has not been set.
225   if (low_entropy_source_ != kLowEntropySourceNotSet &&
226       pseudo_low_entropy_source_ != kLowEntropySourceNotSet)
227     return;
228 
229   const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
230   // Only try to load the value from prefs if the user did not request a reset.
231   // Otherwise, skip to generating a new value. We would have already returned
232   // if both |low_entropy_source_| and |pseudo_low_entropy_source_| were set,
233   // ensuring we only do this reset on the first call to
234   // UpdateLowEntropySources().
235   if (!command_line->HasSwitch(switches::kResetVariationState)) {
236     int new_pref = local_state_->GetInteger(prefs::kMetricsLowEntropySource);
237     if (IsValidLowEntropySource(new_pref))
238       low_entropy_source_ = new_pref;
239     int old_pref = local_state_->GetInteger(prefs::kMetricsOldLowEntropySource);
240     if (IsValidLowEntropySource(old_pref))
241       old_low_entropy_source_ = old_pref;
242     int pseudo_pref =
243         local_state_->GetInteger(prefs::kMetricsPseudoLowEntropySource);
244     if (IsValidLowEntropySource(pseudo_pref))
245       pseudo_low_entropy_source_ = pseudo_pref;
246   }
247 
248   // If the new source is missing or corrupt (or requested to be reset), then
249   // (re)create it. Don't bother recreating the old source if it's corrupt,
250   // because we only keep the old source around for consistency, and we can't
251   // maintain a consistent value if we recreate it.
252   if (low_entropy_source_ == kLowEntropySourceNotSet) {
253     low_entropy_source_ = GenerateLowEntropySource();
254     DCHECK(IsValidLowEntropySource(low_entropy_source_));
255     local_state_->SetInteger(prefs::kMetricsLowEntropySource,
256                              low_entropy_source_);
257   }
258 
259   // If the pseudo source is missing or corrupt (or requested to be reset), then
260   // (re)create it. Don't bother recreating the old source if it's corrupt,
261   // because we only keep the old source around for consistency, and we can't
262   // maintain a consistent value if we recreate it.
263   if (pseudo_low_entropy_source_ == kLowEntropySourceNotSet) {
264     pseudo_low_entropy_source_ = GeneratePseudoLowEntropySource();
265     DCHECK(IsValidLowEntropySource(pseudo_low_entropy_source_));
266     local_state_->SetInteger(prefs::kMetricsPseudoLowEntropySource,
267                              pseudo_low_entropy_source_);
268   }
269 
270   // If the old source was present but corrupt (or requested to be reset), then
271   // we'll never use it again, so delete it.
272   if (old_low_entropy_source_ == kLowEntropySourceNotSet &&
273       local_state_->HasPrefPath(prefs::kMetricsOldLowEntropySource)) {
274     local_state_->ClearPref(prefs::kMetricsOldLowEntropySource);
275   }
276 
277   DCHECK_NE(low_entropy_source_, kLowEntropySourceNotSet);
278 }
279 
280 // static
IsValidLowEntropySource(int value)281 bool EntropyState::IsValidLowEntropySource(int value) {
282   return value >= 0 && value < kMaxLowEntropySize;
283 }
284 
285 // static
IsValidLimitedEntropyRandomizationSource(std::string_view value)286 bool EntropyState::IsValidLimitedEntropyRandomizationSource(
287     std::string_view value) {
288   if (value.empty()) {
289     return false;
290   }
291   // Use Token::FromString() to check whether the given value is a valid
292   // `base::UnguessableToken`.
293   auto token = base::Token::FromString(value);
294   return token.has_value() && !token.value().is_zero();
295 }
296 
297 }  // namespace metrics
298