| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/metrics/entropy_state.h" |
| |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/token.h" |
| #include "base/unguessable_token.h" |
| #include "components/metrics/metrics_pref_names.h" |
| #include "components/metrics/metrics_switches.h" |
| #include "components/prefs/pref_service.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "base/android/jni_android.h" |
| #include "components/metrics/jni_headers/LowEntropySource_jni.h" |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| namespace metrics { |
| |
| namespace { |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| // Needed for a check to see if we retrieved entropy values before we have |
| // transferred them from Ash. |
| bool g_entropy_source_has_been_retrieved = false; |
| bool g_entropy_source_has_been_set = false; |
| #endif |
| |
| // Generates a new non-identifying entropy source used to seed persistent |
| // activities. Make it static so that the new low entropy source value will |
| // only be generated on first access. And thus, even though we may write the |
| // new low entropy source value to prefs multiple times, it stays the same |
| // value. |
| int GenerateLowEntropySource() { |
| #if BUILDFLAG(IS_ANDROID) |
| // Note: As in the non-Android case below, the Java implementation also uses |
| // a static cache, so subsequent invocations will return the same value. |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_LowEntropySource_generateLowEntropySource(env); |
| #else |
| static const int low_entropy_source = |
| base::RandInt(0, EntropyState::kMaxLowEntropySize - 1); |
| return low_entropy_source; |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| // Generates a new non-identifying low entropy source using the same method |
| // that's used for the actual low entropy source. This one, however, is only |
| // used for statistical validation, and *not* for randomization or experiment |
| // assignment. |
| int GeneratePseudoLowEntropySource() { |
| #if BUILDFLAG(IS_ANDROID) |
| // Note: As in the non-Android case below, the Java implementation also uses |
| // a static cache, so subsequent invocations will return the same value. |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| return Java_LowEntropySource_generatePseudoLowEntropySource(env); |
| #else |
| static const int pseudo_low_entropy_source = |
| base::RandInt(0, EntropyState::kMaxLowEntropySize - 1); |
| return pseudo_low_entropy_source; |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| } // namespace |
| |
| EntropyState::EntropyState(PrefService* local_state) |
| : local_state_(local_state) {} |
| |
| // static |
| constexpr int EntropyState::kLowEntropySourceNotSet; |
| |
| // static |
| void EntropyState::ClearPrefs(PrefService* local_state) { |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| // There are currently multiple EntropyState objects (crbug/1495576) and as |
| // Lacros does not own the entropy values anyways, it shouldn't clear them |
| // either. |
| LOG(WARNING) << "EntropyState::ClearPrefs ignored as set remotely."; |
| #else |
| local_state->ClearPref(prefs::kMetricsLowEntropySource); |
| local_state->ClearPref(prefs::kMetricsOldLowEntropySource); |
| local_state->ClearPref(prefs::kMetricsPseudoLowEntropySource); |
| local_state->ClearPref(prefs::kMetricsLimitedEntropyRandomizationSource); |
| #endif |
| } |
| |
| // static |
| void EntropyState::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource, |
| kLowEntropySourceNotSet); |
| registry->RegisterIntegerPref(prefs::kMetricsOldLowEntropySource, |
| kLowEntropySourceNotSet); |
| registry->RegisterIntegerPref(prefs::kMetricsPseudoLowEntropySource, |
| kLowEntropySourceNotSet); |
| registry->RegisterStringPref(prefs::kMetricsLimitedEntropyRandomizationSource, |
| std::string()); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| // static |
| void EntropyState::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) { |
| if (!g_entropy_source_has_been_set) { |
| g_entropy_source_has_been_set = true; |
| // As an |EntropyState| object has an internal state, we need to make sure |
| // that none gets read before the Ash values have been transferred. |
| // This is usually taken care of by |
| // `ChromeMetricsServicesManagerClient::GetMetricsStateManager` which first |
| // sets the Ash values and then creates the `MetricsStateManager`. |
| if (g_entropy_source_has_been_retrieved) { |
| LOG(ERROR) << "Entropy value was retrieved before they were updated"; |
| } |
| DCHECK(!g_entropy_source_has_been_retrieved); |
| } |
| local_state->SetInteger(prefs::kMetricsLowEntropySource, low_entropy_source); |
| local_state->SetInteger(prefs::kMetricsOldLowEntropySource, |
| old_low_entropy_source); |
| local_state->SetInteger(prefs::kMetricsPseudoLowEntropySource, |
| pseudo_low_entropy_source); |
| if (IsValidLimitedEntropyRandomizationSource( |
| limited_entropy_randomization_source)) { |
| local_state->SetString(prefs::kMetricsLimitedEntropyRandomizationSource, |
| limited_entropy_randomization_source); |
| } |
| } |
| #endif |
| |
| std::string EntropyState::GetHighEntropySource( |
| const std::string& initial_client_id) { |
| DCHECK(!initial_client_id.empty()); |
| // For metrics reporting-enabled users, we combine the client ID and low |
| // entropy source to get the final entropy source. |
| // This has two useful properties: |
| // 1) It makes the entropy source less identifiable for parties that do not |
| // know the low entropy source. |
| // 2) It makes the final entropy source resettable. |
| |
| // If this install has an old low entropy source, continue using it, to avoid |
| // changing the group assignments of studies using high entropy. New installs |
| // only have the new low entropy source. If the number of installs with old |
| // sources ever becomes small enough (see UMA.LowEntropySourceValue), we could |
| // remove it, and just use the new source here. |
| int low_entropy_source = GetOldLowEntropySource(); |
| if (low_entropy_source == kLowEntropySourceNotSet) |
| low_entropy_source = GetLowEntropySource(); |
| |
| return initial_client_id + base::NumberToString(low_entropy_source); |
| } |
| |
| int EntropyState::GetLowEntropySource() { |
| UpdateLowEntropySources(); |
| return low_entropy_source_; |
| } |
| |
| int EntropyState::GetPseudoLowEntropySource() { |
| UpdateLowEntropySources(); |
| return pseudo_low_entropy_source_; |
| } |
| |
| int EntropyState::GetOldLowEntropySource() { |
| UpdateLowEntropySources(); |
| return old_low_entropy_source_; |
| } |
| |
| std::string EntropyState::GenerateLimitedEntropyRandomizationSource() { |
| // Uses a cryptographically strong random source to generate a random 128 bit |
| // value. The value cannot be all zeros. |
| auto token = base::UnguessableToken::Create().ToString(); |
| DCHECK(IsValidLimitedEntropyRandomizationSource(token)); |
| return token; |
| } |
| |
| std::string_view EntropyState::GetLimitedEntropyRandomizationSource() { |
| UpdateLimitedEntropyRandomizationSource(); |
| return limited_entropy_randomization_source_; |
| } |
| |
| void EntropyState::UpdateLimitedEntropyRandomizationSource() { |
| // The default value for limited entropy randomization source is an empty |
| // string. If it's not empty, it must have been set during this session and an |
| // update is not needed. |
| if (!limited_entropy_randomization_source_.empty()) { |
| return; |
| } |
| |
| auto* pref_name = prefs::kMetricsLimitedEntropyRandomizationSource; |
| const auto* command_line = base::CommandLine::ForCurrentProcess(); |
| // Load the previously set value from prefs, unless the reset variations state |
| // command line flag is given. |
| if (!command_line->HasSwitch(switches::kResetVariationState)) { |
| auto pref_value = local_state_->GetString(pref_name); |
| if (IsValidLimitedEntropyRandomizationSource(pref_value)) { |
| limited_entropy_randomization_source_ = pref_value; |
| } |
| } |
| |
| // If a previously set value is not found, or if the the reset variations |
| // state command line flag is given, generate a new value and store it into |
| // prefs. |
| if (limited_entropy_randomization_source_.empty()) { |
| limited_entropy_randomization_source_ = |
| GenerateLimitedEntropyRandomizationSource(); |
| local_state_->SetString(pref_name, limited_entropy_randomization_source_); |
| } |
| |
| CHECK(!limited_entropy_randomization_source_.empty()); |
| } |
| |
| void EntropyState::UpdateLowEntropySources() { |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| // Coming here, someone was reading an entropy value. |
| g_entropy_source_has_been_retrieved = true; |
| #endif |
| // The default value for |low_entropy_source_| and the default pref value are |
| // both |kLowEntropySourceNotSet|, which indicates the value has not been set. |
| if (low_entropy_source_ != kLowEntropySourceNotSet && |
| pseudo_low_entropy_source_ != kLowEntropySourceNotSet) |
| return; |
| |
| const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess()); |
| // Only try to load the value from prefs if the user did not request a reset. |
| // Otherwise, skip to generating a new value. We would have already returned |
| // if both |low_entropy_source_| and |pseudo_low_entropy_source_| were set, |
| // ensuring we only do this reset on the first call to |
| // UpdateLowEntropySources(). |
| if (!command_line->HasSwitch(switches::kResetVariationState)) { |
| int new_pref = local_state_->GetInteger(prefs::kMetricsLowEntropySource); |
| if (IsValidLowEntropySource(new_pref)) |
| low_entropy_source_ = new_pref; |
| int old_pref = local_state_->GetInteger(prefs::kMetricsOldLowEntropySource); |
| if (IsValidLowEntropySource(old_pref)) |
| old_low_entropy_source_ = old_pref; |
| int pseudo_pref = |
| local_state_->GetInteger(prefs::kMetricsPseudoLowEntropySource); |
| if (IsValidLowEntropySource(pseudo_pref)) |
| pseudo_low_entropy_source_ = pseudo_pref; |
| } |
| |
| // If the new source is missing or corrupt (or requested to be reset), then |
| // (re)create it. Don't bother recreating the old source if it's corrupt, |
| // because we only keep the old source around for consistency, and we can't |
| // maintain a consistent value if we recreate it. |
| if (low_entropy_source_ == kLowEntropySourceNotSet) { |
| low_entropy_source_ = GenerateLowEntropySource(); |
| DCHECK(IsValidLowEntropySource(low_entropy_source_)); |
| local_state_->SetInteger(prefs::kMetricsLowEntropySource, |
| low_entropy_source_); |
| } |
| |
| // If the pseudo source is missing or corrupt (or requested to be reset), then |
| // (re)create it. Don't bother recreating the old source if it's corrupt, |
| // because we only keep the old source around for consistency, and we can't |
| // maintain a consistent value if we recreate it. |
| if (pseudo_low_entropy_source_ == kLowEntropySourceNotSet) { |
| pseudo_low_entropy_source_ = GeneratePseudoLowEntropySource(); |
| DCHECK(IsValidLowEntropySource(pseudo_low_entropy_source_)); |
| local_state_->SetInteger(prefs::kMetricsPseudoLowEntropySource, |
| pseudo_low_entropy_source_); |
| } |
| |
| // If the old source was present but corrupt (or requested to be reset), then |
| // we'll never use it again, so delete it. |
| if (old_low_entropy_source_ == kLowEntropySourceNotSet && |
| local_state_->HasPrefPath(prefs::kMetricsOldLowEntropySource)) { |
| local_state_->ClearPref(prefs::kMetricsOldLowEntropySource); |
| } |
| |
| DCHECK_NE(low_entropy_source_, kLowEntropySourceNotSet); |
| } |
| |
| // static |
| bool EntropyState::IsValidLowEntropySource(int value) { |
| return value >= 0 && value < kMaxLowEntropySize; |
| } |
| |
| // static |
| bool EntropyState::IsValidLimitedEntropyRandomizationSource( |
| std::string_view value) { |
| if (value.empty()) { |
| return false; |
| } |
| // Use Token::FromString() to check whether the given value is a valid |
| // `base::UnguessableToken`. |
| auto token = base::Token::FromString(value); |
| return token.has_value() && !token.value().is_zero(); |
| } |
| |
| } // namespace metrics |