| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| |
| #include "base/at_exit.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_command_line.h" |
| #include "components/metrics/entropy_state.h" |
| #include "components/metrics/metrics_pref_names.h" |
| #include "components/metrics/metrics_state_manager.h" |
| #include "components/metrics/test/test_enabled_state_provider.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/variations/client_filterable_state.h" |
| #include "components/variations/entropy_provider.h" |
| #include "components/variations/fuzzers/create_trials_from_seed_test_case.pb.h" |
| #include "components/variations/proto/study.pb.h" |
| #include "components/variations/service/limited_entropy_randomization.h" |
| #include "components/variations/variations_layers.h" |
| #include "components/variations/variations_seed_processor.h" |
| #include "components/variations/variations_test_utils.h" |
| #include "testing/libfuzzer/proto/lpm_interface.h" |
| |
| namespace variations { |
| namespace { |
| |
| struct Environment { |
| Environment() : enabled_state_provider(/*consent=*/true, /*enabled=*/true) { |
| base::CommandLine::Init(0, nullptr); |
| metrics::MetricsStateManager::RegisterPrefs(pref_service.registry()); |
| } |
| |
| base::AtExitManager at_exit_manager; |
| TestingPrefServiceSimple pref_service; |
| metrics::TestEnabledStateProvider enabled_state_provider; |
| }; |
| |
| std::unique_ptr<ClientFilterableState> CreateClientFilterableState( |
| const CreateTrialsFromSeedTestCase::ClientFilterableState& spec) { |
| auto client_state = std::make_unique<ClientFilterableState>( |
| base::BindOnce([] { return false; }), |
| base::BindLambdaForTesting([spec]() { |
| return base::flat_set<uint64_t>(spec.google_groups().begin(), |
| spec.google_groups().end()); |
| })); |
| |
| if (spec.has_locale()) { |
| client_state->locale = spec.locale(); |
| } |
| if (spec.has_reference_date_seconds_since_epoch()) { |
| client_state->reference_date = |
| base::Time::UnixEpoch() + |
| base::Seconds(spec.reference_date_seconds_since_epoch()); |
| } |
| if (!spec.version().empty()) { |
| client_state->version = base::Version( |
| std::vector<uint32_t>(spec.version().begin(), spec.version().end())); |
| } else { |
| // Default constructed version is invalid as per base::Version so we use |
| // placeholder value instead. |
| client_state->version = base::Version({0, 0, 0, 0}); |
| } |
| if (!spec.os_version().empty()) { |
| client_state->os_version = base::Version(std::vector<uint32_t>( |
| spec.os_version().begin(), spec.os_version().end())); |
| } else { |
| // Default constructed version is invalid as per base::Version so we use |
| // placeholder value instead. |
| client_state->os_version = base::Version(std::vector<uint32_t>{0, 0}); |
| } |
| if (spec.has_channel()) { |
| client_state->channel = spec.channel(); |
| } |
| if (spec.has_form_factor()) { |
| client_state->form_factor = spec.form_factor(); |
| } |
| if (spec.has_cpu_architecture()) { |
| client_state->cpu_architecture = spec.cpu_architecture(); |
| } |
| if (spec.has_platform()) { |
| client_state->platform = spec.platform(); |
| } |
| if (spec.has_hardware_class()) { |
| client_state->hardware_class = spec.hardware_class(); |
| } |
| if (spec.has_is_low_end_device()) { |
| client_state->is_low_end_device = spec.is_low_end_device(); |
| } |
| if (spec.has_session_consistency_country()) { |
| client_state->session_consistency_country = |
| spec.session_consistency_country(); |
| } |
| if (spec.has_permanent_consistency_country()) { |
| client_state->permanent_consistency_country = |
| spec.permanent_consistency_country(); |
| } |
| if (spec.has_policy_restriction()) { |
| switch (spec.policy_restriction()) { |
| case CreateTrialsFromSeedTestCase_RestrictionPolicy_NO_RESTRICTIONS: |
| client_state->policy_restriction = RestrictionPolicy::NO_RESTRICTIONS; |
| break; |
| case CreateTrialsFromSeedTestCase_RestrictionPolicy_CRITICAL_ONLY: |
| client_state->policy_restriction = RestrictionPolicy::CRITICAL_ONLY; |
| break; |
| case CreateTrialsFromSeedTestCase_RestrictionPolicy_ALL: |
| client_state->policy_restriction = RestrictionPolicy::ALL; |
| break; |
| } |
| } |
| return client_state; |
| } |
| |
| std::unique_ptr<metrics::MetricsStateManager> |
| CreateMetricsStateManagerForFuzzer( |
| const CreateTrialsFromSeedTestCase::EntropyValues& entropy_values, |
| TestingPrefServiceSimple* pref_service, |
| metrics::EnabledStateProvider* enabled_state_provider) { |
| pref_service->SetInteger(metrics::prefs::kMetricsLowEntropySource, |
| entropy_values.low_entropy()); |
| pref_service->SetString( |
| metrics::prefs::kMetricsLimitedEntropyRandomizationSource, |
| entropy_values.limited_entropy_randomization_source()); |
| pref_service->SetString(metrics::prefs::kMetricsClientID, |
| entropy_values.client_id()); |
| |
| return metrics::MetricsStateManager::Create( |
| pref_service, enabled_state_provider, |
| /*backup_registry_key=*/std::wstring(), |
| /*user_data_dir=*/base::FilePath()); |
| } |
| |
| void CreateTrialsFromSeedFuzzer( |
| const variations::CreateTrialsFromSeedTestCase& test_case, |
| TestingPrefServiceSimple* pref_service, |
| metrics::EnabledStateProvider* enabled_state_provider) { |
| base::FieldTrialList field_trial_list; |
| base::FeatureList feature_list; |
| |
| auto client_state = CreateClientFilterableState( |
| test_case.has_client_filterable_state() |
| ? test_case.client_filterable_state() |
| : variations::CreateTrialsFromSeedTestCase::ClientFilterableState()); |
| |
| const auto& entropy_values = |
| test_case.has_entropy() |
| ? test_case.entropy() |
| : variations::CreateTrialsFromSeedTestCase::EntropyValues(); |
| auto state_manager = CreateMetricsStateManagerForFuzzer( |
| entropy_values, pref_service, enabled_state_provider); |
| auto entropy_providers = state_manager->CreateEntropyProviders( |
| /*enable_limited_entropy_mode=*/true); |
| |
| if (!test_case.has_seed()) { |
| return; |
| } |
| |
| base::HistogramTester histogram_tester; |
| auto seed = test_case.seed(); |
| VariationsLayers layers(seed, *entropy_providers); |
| StickyActivationManager sticky_activation_manager(/*local_state=*/nullptr); |
| VariationsSeedProcessor(sticky_activation_manager) |
| .CreateTrialsFromSeed(seed, *client_state, |
| base::BindRepeating(NoopUIStringOverrideCallback), |
| *entropy_providers, layers, &feature_list); |
| |
| // There are numerous conditions for which the seed could be rejected. They |
| // should all be caught during the initial seed validation. Post validation, |
| // when attempting to actually use the seed, there are some components which, |
| // for safety, repeat some of the validation and signal a generic "invalid |
| // configuration" rejection reason. This state should not be reachable in |
| // practice. |
| CHECK_EQ(histogram_tester.GetBucketCount( |
| kSeedRejectionReasonHistogram, |
| SeedRejectionReason::kInvalidLayerConfiguration), |
| 0); |
| } |
| |
| } // namespace |
| |
| DEFINE_PROTO_FUZZER(const variations::CreateTrialsFromSeedTestCase& test_case) { |
| static Environment env; |
| CreateTrialsFromSeedFuzzer(test_case, &env.pref_service, |
| &env.enabled_state_provider); |
| } |
| |
| } // namespace variations |