1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
// Copyright 2024 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/variations/service/limited_entropy_randomization.h"
#include <math.h>
#include <cstdint>
#include <limits>
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/version_info/version_info.h"
#include "build/build_config.h"
#include "components/variations/client_filterable_state.h"
#include "components/variations/limited_layer_entropy_cost_tracker.h"
#include "components/variations/study_filtering.h"
#include "components/variations/variations_layers.h"
#include "components/variations/variations_seed_processor.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
namespace variations {
namespace {
using LayerByIdMap = absl::flat_hash_map<uint32_t, raw_ptr<const Layer>>;
void LogSeedRejectionReason(SeedRejectionReason reason) {
base::UmaHistogramEnumeration(kSeedRejectionReasonHistogram, reason);
}
// Builds a map of layers by id from the given seed, logging the seed rejection
// reason if the seed is invalid.
std::optional<LayerByIdMap> BuildLayerByIdMap(const VariationsSeed& seed) {
LayerByIdMap layer_by_id_map;
layer_by_id_map.reserve(seed.layers_size());
for (const Layer& layer : seed.layers()) {
if (layer.id() == 0) {
LogSeedRejectionReason(SeedRejectionReason::kInvalidLayerId);
return std::nullopt;
}
if (layer.num_slots() == 0) {
LogSeedRejectionReason(SeedRejectionReason::kLayerDoesNotContainSlots);
return std::nullopt;
}
if (!VariationsLayers::AreSlotBoundsValid(layer)) {
LogSeedRejectionReason(SeedRejectionReason::kLayerHasInvalidSlotBounds);
return std::nullopt;
}
if (!layer_by_id_map.emplace(layer.id(), &layer).second) {
LogSeedRejectionReason(SeedRejectionReason::kDuplicatedLayerId);
return std::nullopt;
}
}
return layer_by_id_map;
}
// Returns true if the study references a layer.
bool HasLayerReference(const Study& study) {
return study.has_layer();
}
// Returns the layer referenced by the study, or nullptr if the layer member
// reference is invalid, logging the seed rejection reason.
//
// A layer member reference is invalid if:
//
// * The layer id of the reference is zero.
// * No layer is defined having the referenced layer id.
// * A layer member referenced by the study is not defined in the layer.
const Layer* FindLayerForStudy(const LayerByIdMap& layer_by_id_map,
const Study& study) {
const auto& ref = study.layer();
if (ref.layer_id() == 0) {
LogSeedRejectionReason(SeedRejectionReason::kInvalidLayerReference);
return nullptr;
}
const auto& layer_member_ids =
ref.layer_member_ids().empty()
? VariationsLayers::FallbackLayerMemberIds(ref)
: ref.layer_member_ids();
if (layer_member_ids.empty()) {
LogSeedRejectionReason(SeedRejectionReason::kEmptyLayerReference);
return nullptr;
}
const auto iter = layer_by_id_map.find(ref.layer_id());
if (iter == layer_by_id_map.end()) {
LogSeedRejectionReason(SeedRejectionReason::kDanglingLayerReference);
return nullptr;
}
const Layer* layer = iter->second;
for (const uint32_t member_id : layer_member_ids) {
if (!base::Contains(layer->members(), member_id, &Layer::LayerMember::id)) {
LogSeedRejectionReason(
SeedRejectionReason::kDanglingLayerMemberReference);
return nullptr;
}
}
return layer;
}
// Returns true if the layer is a limited layer.
bool IsLimitedLayer(const Layer& layer) {
return layer.entropy_mode() == Layer::LIMITED;
}
// Returns true if the study applies to the client's platform.
bool AppliesToClientPlatform(const Study& study,
const ClientFilterableState& client_state) {
return internal::CheckStudyPlatform(study.filter(), client_state.platform);
}
// Returns true if the study applies to the client's channel.
bool AppliesToClientChannel(const Study& study,
const ClientFilterableState& client_state) {
return internal::CheckStudyChannel(study.filter(), client_state.channel);
}
// Returns true if the study applies to the client's version.
bool AppliesToClientVersion(const Study& study,
const ClientFilterableState& client_state) {
return internal::CheckStudyVersion(study.filter(), client_state.version);
}
} // namespace
double GetGoogleWebEntropyLimitInBits() {
// TODO(crbug.com/422222582): Update this to platform-specific launch values.
return 1.0;
}
// TODO(crbug.com/428216544): Refactor, along with variations_layers.cc, to
// consolidate the logic for checking the layer configuration in the seed.
bool SeedHasMisconfiguredEntropy(const ClientFilterableState& client_state,
const VariationsSeed& seed,
double entropy_limit_in_bits) {
std::optional<LayerByIdMap> layer_by_id_map = BuildLayerByIdMap(seed);
if (!layer_by_id_map.has_value()) {
// Seed rejection reason already logged.
return true;
}
// We don't know which layer is the active limited layer for the client's
// platform and channel. We'll set up the active limited layer and the entropy
// tracker once we find the first relevant study.
const Layer* active_limited_layer = nullptr;
std::optional<LimitedLayerEntropyCostTracker> entropy_tracker;
for (const Study& study : seed.study()) {
if (!HasLayerReference(study)) {
continue;
}
const Layer* current_layer = FindLayerForStudy(*layer_by_id_map, study);
if (!current_layer) {
// Seed rejection reason already logged.
return true;
}
if (!IsLimitedLayer(*current_layer) ||
!AppliesToClientPlatform(study, client_state) ||
!AppliesToClientChannel(study, client_state) ||
!AppliesToClientVersion(study, client_state)) {
continue;
}
// Update the active limited layer and the entropy tracker or ensure that
// the active limited layer matches the current layer.
if (active_limited_layer == nullptr) {
active_limited_layer = current_layer;
entropy_tracker.emplace(*active_limited_layer, entropy_limit_in_bits);
if (!entropy_tracker->IsValid()) {
// The entropy tracker may have been invalidated by the layer config.
LogSeedRejectionReason(SeedRejectionReason::kInvalidLayerConfiguration);
return true;
}
} else if (active_limited_layer != current_layer) {
LogSeedRejectionReason(SeedRejectionReason::kMoreThenOneLimitedLayer);
return true;
}
if (!entropy_tracker->AddEntropyUsedByStudy(study)) {
// The entropy tracker may have been invalidated by the study config, or
// the entropy limit may have been exceeded.
LogSeedRejectionReason(
entropy_tracker->IsValid()
? SeedRejectionReason::kHighEntropyUsage
: SeedRejectionReason::kInvalidLayerConfiguration);
return true;
}
}
// No entropy or layer issues found.
return false;
}
} // namespace variations
|