blob: 2885703250788b5daebf3bc22d97beeeab9272a9 [file] [log] [blame]
// 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