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 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
|
// Copyright 2017 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/safe_browsing/content/browser/triggers/trigger_throttler.h"
#include <algorithm>
#include "base/containers/contains.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
namespace safe_browsing {
const size_t kAdSamplerTriggerDefaultQuota = 10;
const size_t kSuspiciousSiteTriggerDefaultQuota = 5;
const char kSuspiciousSiteTriggerQuotaParam[] = "suspicious_site_trigger_quota";
namespace {
const size_t kUnlimitedTriggerQuota = std::numeric_limits<size_t>::max();
constexpr base::TimeDelta kOneDayTimeDelta = base::Days(1);
void ParseTriggerTypeAndQuotaParam(
std::vector<TriggerTypeAndQuotaItem>* trigger_type_and_quota_list) {
DCHECK(trigger_type_and_quota_list);
trigger_type_and_quota_list->clear();
// First, handle the trigger-specific features.
int suspicious_site_quota = base::GetFieldTrialParamByFeatureAsInt(
kSuspiciousSiteTriggerQuotaFeature, kSuspiciousSiteTriggerQuotaParam,
kSuspiciousSiteTriggerDefaultQuota);
if (suspicious_site_quota > 0) {
trigger_type_and_quota_list->push_back(
std::make_pair(TriggerType::SUSPICIOUS_SITE, suspicious_site_quota));
}
}
// Looks in |trigger_quota_list| for |trigger_type|. If found, sets |out_quota|
// to the configured quota, and returns true. If not found, returns false.
bool TryFindQuotaForTrigger(
const TriggerType trigger_type,
const std::vector<TriggerTypeAndQuotaItem>& trigger_quota_list,
size_t* out_quota) {
const auto& trigger_quota_iter = std::ranges::find(
trigger_quota_list, trigger_type, &TriggerTypeAndQuotaItem::first);
if (trigger_quota_iter != trigger_quota_list.end()) {
*out_quota = trigger_quota_iter->second;
return true;
}
return false;
}
} // namespace
TriggerThrottler::TriggerThrottler(PrefService* local_state_prefs)
: local_state_prefs_(local_state_prefs),
clock_(base::DefaultClock::GetInstance()) {
ParseTriggerTypeAndQuotaParam(&trigger_type_and_quota_list_);
LoadTriggerEventsFromPref();
}
TriggerThrottler::~TriggerThrottler() = default;
void TriggerThrottler::SetClockForTesting(base::Clock* test_clock) {
clock_ = test_clock;
}
bool TriggerThrottler::TriggerCanFire(const TriggerType trigger_type) const {
// Lookup how many times this trigger is allowed to fire each day.
const size_t trigger_quota = GetDailyQuotaForTrigger(trigger_type);
// Some basic corner cases for triggers that always fire, or disabled
// triggers that never fire.
if (trigger_quota == kUnlimitedTriggerQuota)
return true;
if (trigger_quota == 0)
return false;
// Other triggers are capped, see how many times this trigger has already
// fired.
if (!base::Contains(trigger_events_, trigger_type))
return true;
const std::vector<base::Time>& timestamps = trigger_events_.at(trigger_type);
// More quota is available, so the trigger can fire again.
if (trigger_quota > timestamps.size())
return true;
// Otherwise, we have more events than quota, check which day they occurred
// on. Newest events are at the end of vector so we can simply look at the
// Nth-from-last entry (where N is the quota) to see if it happened within
// the current day or earlier.
base::Time min_timestamp = clock_->Now() - kOneDayTimeDelta;
const size_t pos = timestamps.size() - trigger_quota;
return timestamps[pos] < min_timestamp;
}
void TriggerThrottler::TriggerFired(const TriggerType trigger_type) {
// Lookup how many times this trigger is allowed to fire each day.
const size_t trigger_quota = GetDailyQuotaForTrigger(trigger_type);
// For triggers that always fire, don't bother tracking quota.
if (trigger_quota == kUnlimitedTriggerQuota)
return;
// Otherwise, record that the trigger fired.
std::vector<base::Time>* timestamps = &trigger_events_[trigger_type];
timestamps->push_back(clock_->Now());
// Clean up the trigger events map.
CleanupOldEvents();
// Update the pref
WriteTriggerEventsToPref();
}
void TriggerThrottler::CleanupOldEvents() {
for (const auto& map_iter : trigger_events_) {
const TriggerType trigger_type = map_iter.first;
const size_t trigger_quota = GetDailyQuotaForTrigger(trigger_type);
const std::vector<base::Time>& trigger_times = map_iter.second;
// Skip the cleanup if we have quota room, quotas should generally be small.
if (trigger_times.size() < trigger_quota)
return;
std::vector<base::Time> tmp_trigger_times;
base::Time min_timestamp = clock_->Now() - kOneDayTimeDelta;
// Go over the event times for this trigger and keep timestamps which are
// newer than |min_timestamp|. We put timestamps in a temp vector that will
// get swapped into the map in place of the existing vector.
for (const base::Time timestamp : trigger_times) {
if (timestamp > min_timestamp)
tmp_trigger_times.push_back(timestamp);
}
trigger_events_[trigger_type].swap(tmp_trigger_times);
}
}
void TriggerThrottler::LoadTriggerEventsFromPref() {
trigger_events_.clear();
if (!local_state_prefs_)
return;
const base::Value::Dict& event_dict =
local_state_prefs_->GetDict(prefs::kSafeBrowsingTriggerEventTimestamps);
for (auto trigger_pair : event_dict) {
// Check that the first item in the pair is convertible to a trigger type
// and that the second item is a list.
int trigger_type_int;
if (!base::StringToInt(trigger_pair.first, &trigger_type_int) ||
trigger_type_int < static_cast<int>(TriggerType::kMinTriggerType) ||
trigger_type_int > static_cast<int>(TriggerType::kMaxTriggerType)) {
continue;
}
if (!trigger_pair.second.is_list())
continue;
const TriggerType trigger_type = static_cast<TriggerType>(trigger_type_int);
for (const auto& timestamp : trigger_pair.second.GetList()) {
if (timestamp.is_double())
trigger_events_[trigger_type].push_back(
base::Time::FromSecondsSinceUnixEpoch(timestamp.GetDouble()));
}
}
}
void TriggerThrottler::WriteTriggerEventsToPref() {
if (!local_state_prefs_)
return;
base::Value::Dict trigger_dict;
for (const auto& trigger_item : trigger_events_) {
base::Value::List timestamps;
for (const base::Time timestamp : trigger_item.second) {
timestamps.Append(timestamp.InSecondsFSinceUnixEpoch());
}
trigger_dict.Set(base::NumberToString(static_cast<int>(trigger_item.first)),
std::move(timestamps));
}
local_state_prefs_->SetDict(prefs::kSafeBrowsingTriggerEventTimestamps,
std::move(trigger_dict));
}
size_t TriggerThrottler::GetDailyQuotaForTrigger(
const TriggerType trigger_type) const {
size_t quota = 0;
switch (trigger_type) {
case TriggerType::SECURITY_INTERSTITIAL:
case TriggerType::GAIA_PASSWORD_REUSE:
case TriggerType::APK_DOWNLOAD:
case TriggerType::PHISHY_SITE_INTERACTION:
return kUnlimitedTriggerQuota;
case TriggerType::DEPRECATED_AD_POPUP:
case TriggerType::DEPRECATED_AD_REDIRECT:
return 0;
case TriggerType::AD_SAMPLE:
// Check for non-default quota (needed for unit tests).
if (TryFindQuotaForTrigger(trigger_type, trigger_type_and_quota_list_,
"a)) {
return quota;
}
return kAdSamplerTriggerDefaultQuota;
case TriggerType::SUSPICIOUS_SITE:
// Suspicious Sites are disabled unless they are configured through Finch.
if (TryFindQuotaForTrigger(trigger_type, trigger_type_and_quota_list_,
"a)) {
return quota;
}
break;
}
// By default, unhandled or unconfigured trigger types have no quota.
return 0;
}
void TriggerThrottler::ResetPrefsForTesting(PrefService* local_state_prefs) {
local_state_prefs_ = local_state_prefs;
LoadTriggerEventsFromPref();
}
} // namespace safe_browsing
|