OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/metrics/variations/variations_seed_store.h" | 5 #include "chrome/browser/metrics/variations/variations_seed_store.h" |
6 | 6 |
7 #include "base/base64.h" | 7 #include "base/base64.h" |
8 #include "base/metrics/histogram_macros.h" | 8 #include "base/metrics/histogram_macros.h" |
| 9 #include "base/numerics/safe_math.h" |
9 #include "base/prefs/pref_registry_simple.h" | 10 #include "base/prefs/pref_registry_simple.h" |
10 #include "base/prefs/pref_service.h" | 11 #include "base/prefs/pref_service.h" |
11 #include "base/sha1.h" | 12 #include "base/sha1.h" |
12 #include "base/strings/string_number_conversions.h" | 13 #include "base/strings/string_number_conversions.h" |
13 #include "chrome/common/pref_names.h" | 14 #include "chrome/common/pref_names.h" |
14 #include "components/metrics/compression_utils.h" | 15 #include "components/metrics/compression_utils.h" |
15 #include "components/variations/proto/variations_seed.pb.h" | 16 #include "components/variations/proto/variations_seed.pb.h" |
16 #include "crypto/signature_verifier.h" | 17 #include "crypto/signature_verifier.h" |
| 18 #include "third_party/protobuf/src/google/protobuf/io/coded_stream.h" |
17 | 19 |
18 namespace chrome_variations { | 20 namespace chrome_variations { |
19 | 21 |
20 namespace { | 22 namespace { |
21 | 23 |
22 // Signature verification is disabled on mobile platforms for now, since it | 24 // Signature verification is disabled on mobile platforms for now, since it |
23 // adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop). | 25 // adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop). |
24 bool SignatureVerificationEnabled() { | 26 bool SignatureVerificationEnabled() { |
25 #if defined(OS_IOS) || defined(OS_ANDROID) | 27 #if defined(OS_IOS) || defined(OS_ANDROID) |
26 return false; | 28 return false; |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
75 UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state, | 77 UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state, |
76 VARIATIONS_SEED_EMPTY_ENUM_SIZE); | 78 VARIATIONS_SEED_EMPTY_ENUM_SIZE); |
77 } | 79 } |
78 | 80 |
79 enum VariationsSeedStoreResult { | 81 enum VariationsSeedStoreResult { |
80 VARIATIONS_SEED_STORE_SUCCESS, | 82 VARIATIONS_SEED_STORE_SUCCESS, |
81 VARIATIONS_SEED_STORE_FAILED_EMPTY, | 83 VARIATIONS_SEED_STORE_FAILED_EMPTY, |
82 VARIATIONS_SEED_STORE_FAILED_PARSE, | 84 VARIATIONS_SEED_STORE_FAILED_PARSE, |
83 VARIATIONS_SEED_STORE_FAILED_SIGNATURE, | 85 VARIATIONS_SEED_STORE_FAILED_SIGNATURE, |
84 VARIATIONS_SEED_STORE_FAILED_GZIP, | 86 VARIATIONS_SEED_STORE_FAILED_GZIP, |
| 87 // DELTA_COUNT is not so much a result of the seed store, but rather counting |
| 88 // the number of delta-compressed seeds the SeedStore() function saw. Kept in |
| 89 // the same histogram for convenience of comparing against the other values. |
| 90 VARIATIONS_SEED_STORE_DELTA_COUNT, |
| 91 VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED, |
| 92 VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY, |
| 93 VARIATIONS_SEED_STORE_FAILED_DELTA_STORE, |
85 VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE, | 94 VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE, |
86 }; | 95 }; |
87 | 96 |
88 void RecordVariationsSeedStoreHistogram(VariationsSeedStoreResult result) { | 97 void RecordSeedStoreHistogram(VariationsSeedStoreResult result) { |
89 UMA_HISTOGRAM_ENUMERATION("Variations.SeedStoreResult", result, | 98 UMA_HISTOGRAM_ENUMERATION("Variations.SeedStoreResult", result, |
90 VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE); | 99 VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE); |
91 } | 100 } |
92 | 101 |
93 // Note: UMA histogram enum - don't re-order or remove entries. | 102 // Note: UMA histogram enum - don't re-order or remove entries. |
94 enum VariationsSeedDateChangeState { | 103 enum VariationsSeedDateChangeState { |
95 SEED_DATE_NO_OLD_DATE, | 104 SEED_DATE_NO_OLD_DATE, |
96 SEED_DATE_NEW_DATE_OLDER, | 105 SEED_DATE_NEW_DATE_OLDER, |
97 SEED_DATE_SAME_DAY, | 106 SEED_DATE_SAME_DAY, |
98 SEED_DATE_NEW_DAY, | 107 SEED_DATE_NEW_DAY, |
(...skipping 25 matching lines...) Expand all Loading... |
124 // The server date is earlier than the stored date, and they are from | 133 // The server date is earlier than the stored date, and they are from |
125 // different UTC days, so |server_seed_date| is a valid new day. | 134 // different UTC days, so |server_seed_date| is a valid new day. |
126 return SEED_DATE_NEW_DAY; | 135 return SEED_DATE_NEW_DAY; |
127 } | 136 } |
128 return SEED_DATE_SAME_DAY; | 137 return SEED_DATE_SAME_DAY; |
129 } | 138 } |
130 | 139 |
131 } // namespace | 140 } // namespace |
132 | 141 |
133 VariationsSeedStore::VariationsSeedStore(PrefService* local_state) | 142 VariationsSeedStore::VariationsSeedStore(PrefService* local_state) |
134 : local_state_(local_state) { | 143 : local_state_(local_state), seed_has_country_code_(false) { |
135 } | 144 } |
136 | 145 |
137 VariationsSeedStore::~VariationsSeedStore() { | 146 VariationsSeedStore::~VariationsSeedStore() { |
138 } | 147 } |
139 | 148 |
140 bool VariationsSeedStore::LoadSeed(variations::VariationsSeed* seed) { | 149 bool VariationsSeedStore::LoadSeed(variations::VariationsSeed* seed) { |
141 invalid_base64_signature_.clear(); | 150 invalid_base64_signature_.clear(); |
142 | 151 |
143 std::string seed_data; | 152 std::string seed_data; |
144 if (!ReadSeedData(&seed_data)) | 153 if (!ReadSeedData(&seed_data)) |
(...skipping 22 matching lines...) Expand all Loading... |
167 } | 176 } |
168 | 177 |
169 // Migrate any existing country code from the seed to the pref, if the pref is | 178 // Migrate any existing country code from the seed to the pref, if the pref is |
170 // empty. TODO(asvitkine): Clean up the code in M50+ when sufficient number | 179 // empty. TODO(asvitkine): Clean up the code in M50+ when sufficient number |
171 // of clients have migrated. | 180 // of clients have migrated. |
172 if (seed->has_country_code() && | 181 if (seed->has_country_code() && |
173 local_state_->GetString(prefs::kVariationsCountry).empty()) { | 182 local_state_->GetString(prefs::kVariationsCountry).empty()) { |
174 local_state_->SetString(prefs::kVariationsCountry, seed->country_code()); | 183 local_state_->SetString(prefs::kVariationsCountry, seed->country_code()); |
175 } | 184 } |
176 variations_serial_number_ = seed->serial_number(); | 185 variations_serial_number_ = seed->serial_number(); |
| 186 seed_has_country_code_ = seed->has_country_code(); |
177 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY); | 187 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY); |
178 return true; | 188 return true; |
179 } | 189 } |
180 | 190 |
181 bool VariationsSeedStore::StoreSeedData( | 191 bool VariationsSeedStore::StoreSeedData( |
| 192 const std::string& data, |
| 193 const std::string& base64_seed_signature, |
| 194 const std::string& country_code, |
| 195 const base::Time& date_fetched, |
| 196 bool is_delta_compressed, |
| 197 variations::VariationsSeed* parsed_seed) { |
| 198 if (!is_delta_compressed) { |
| 199 const bool result = |
| 200 StoreSeedDataNoDelta(data, base64_seed_signature, country_code, |
| 201 date_fetched, parsed_seed); |
| 202 if (result) { |
| 203 UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.Size", |
| 204 data.length() / 1024); |
| 205 } |
| 206 return result; |
| 207 } |
| 208 |
| 209 // If the data is delta compressed, first decode it. |
| 210 DCHECK(invalid_base64_signature_.empty()); |
| 211 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_DELTA_COUNT); |
| 212 |
| 213 std::string existing_seed_data; |
| 214 std::string updated_seed_data; |
| 215 if (!ReadSeedData(&existing_seed_data)) { |
| 216 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED); |
| 217 return false; |
| 218 } |
| 219 if (!ApplyDeltaPatch(existing_seed_data, data, &updated_seed_data)) { |
| 220 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY); |
| 221 return false; |
| 222 } |
| 223 |
| 224 const bool result = |
| 225 StoreSeedDataNoDelta(updated_seed_data, base64_seed_signature, |
| 226 country_code, date_fetched, parsed_seed); |
| 227 if (result) { |
| 228 // Note: |updated_seed_data.length()| is guaranteed to be non-zero, else |
| 229 // result would be false. |
| 230 int size_reduction = updated_seed_data.length() - data.length(); |
| 231 UMA_HISTOGRAM_PERCENTAGE("Variations.StoreSeed.DeltaSize.ReductionPercent", |
| 232 100 * size_reduction / updated_seed_data.length()); |
| 233 UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.DeltaSize", |
| 234 data.length() / 1024); |
| 235 } else { |
| 236 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_STORE); |
| 237 } |
| 238 return result; |
| 239 } |
| 240 |
| 241 // TODO(asvitkine): Move this method down to match declaration order in a |
| 242 // follow-up CL. |
| 243 bool VariationsSeedStore::StoreSeedDataNoDelta( |
182 const std::string& seed_data, | 244 const std::string& seed_data, |
183 const std::string& base64_seed_signature, | 245 const std::string& base64_seed_signature, |
| 246 const std::string& country_code, |
184 const base::Time& date_fetched, | 247 const base::Time& date_fetched, |
185 variations::VariationsSeed* parsed_seed) { | 248 variations::VariationsSeed* parsed_seed) { |
186 if (seed_data.empty()) { | 249 if (seed_data.empty()) { |
187 RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_EMPTY); | 250 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_EMPTY); |
188 return false; | 251 return false; |
189 } | 252 } |
190 | 253 |
191 // Only store the seed data if it parses correctly. | 254 // Only store the seed data if it parses correctly. |
192 variations::VariationsSeed seed; | 255 variations::VariationsSeed seed; |
193 if (!seed.ParseFromString(seed_data)) { | 256 if (!seed.ParseFromString(seed_data)) { |
194 RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_PARSE); | 257 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_PARSE); |
195 return false; | 258 return false; |
196 } | 259 } |
197 | 260 |
198 const VerifySignatureResult result = | 261 const VerifySignatureResult result = |
199 VerifySeedSignature(seed_data, base64_seed_signature); | 262 VerifySeedSignature(seed_data, base64_seed_signature); |
200 if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) { | 263 if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) { |
201 UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result, | 264 UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result, |
202 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE); | 265 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE); |
203 if (result != VARIATIONS_SEED_SIGNATURE_VALID) { | 266 if (result != VARIATIONS_SEED_SIGNATURE_VALID) { |
204 RecordVariationsSeedStoreHistogram( | 267 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_SIGNATURE); |
205 VARIATIONS_SEED_STORE_FAILED_SIGNATURE); | |
206 return false; | 268 return false; |
207 } | 269 } |
208 } | 270 } |
209 | 271 |
210 // Compress the seed before base64-encoding and storing. | 272 // Compress the seed before base64-encoding and storing. |
211 std::string compressed_seed_data; | 273 std::string compressed_seed_data; |
212 if (!metrics::GzipCompress(seed_data, &compressed_seed_data)) { | 274 if (!metrics::GzipCompress(seed_data, &compressed_seed_data)) { |
213 RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_GZIP); | 275 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_GZIP); |
214 return false; | 276 return false; |
215 } | 277 } |
216 | 278 |
217 std::string base64_seed_data; | 279 std::string base64_seed_data; |
218 base::Base64Encode(compressed_seed_data, &base64_seed_data); | 280 base::Base64Encode(compressed_seed_data, &base64_seed_data); |
219 | 281 |
220 // TODO(asvitkine): This pref is no longer being used. Remove it completely | 282 // TODO(asvitkine): This pref is no longer being used. Remove it completely |
221 // in M45+. | 283 // in M45+. |
222 local_state_->ClearPref(prefs::kVariationsSeed); | 284 local_state_->ClearPref(prefs::kVariationsSeed); |
223 | 285 |
224 // Update the saved country code only if one was returned from the server. | 286 // Update the saved country code only if one was returned from the server. |
225 if (seed.has_country_code()) | 287 // Prefer the country code that was transmitted in the header over the one in |
| 288 // the seed (which is deprecated). |
| 289 if (!country_code.empty()) |
| 290 local_state_->SetString(prefs::kVariationsCountry, country_code); |
| 291 else if (seed.has_country_code()) |
226 local_state_->SetString(prefs::kVariationsCountry, seed.country_code()); | 292 local_state_->SetString(prefs::kVariationsCountry, seed.country_code()); |
227 | 293 |
228 local_state_->SetString(prefs::kVariationsCompressedSeed, base64_seed_data); | 294 local_state_->SetString(prefs::kVariationsCompressedSeed, base64_seed_data); |
229 UpdateSeedDateAndLogDayChange(date_fetched); | 295 UpdateSeedDateAndLogDayChange(date_fetched); |
230 local_state_->SetString(prefs::kVariationsSeedSignature, | 296 local_state_->SetString(prefs::kVariationsSeedSignature, |
231 base64_seed_signature); | 297 base64_seed_signature); |
232 variations_serial_number_ = seed.serial_number(); | 298 variations_serial_number_ = seed.serial_number(); |
233 if (parsed_seed) | 299 if (parsed_seed) |
234 seed.Swap(parsed_seed); | 300 seed.Swap(parsed_seed); |
235 | 301 |
236 RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_SUCCESS); | 302 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_SUCCESS); |
237 return true; | 303 return true; |
238 } | 304 } |
239 | 305 |
240 void VariationsSeedStore::UpdateSeedDateAndLogDayChange( | 306 void VariationsSeedStore::UpdateSeedDateAndLogDayChange( |
241 const base::Time& server_date_fetched) { | 307 const base::Time& server_date_fetched) { |
242 VariationsSeedDateChangeState date_change = SEED_DATE_NO_OLD_DATE; | 308 VariationsSeedDateChangeState date_change = SEED_DATE_NO_OLD_DATE; |
243 | 309 |
244 if (local_state_->HasPrefPath(prefs::kVariationsSeedDate)) { | 310 if (local_state_->HasPrefPath(prefs::kVariationsSeedDate)) { |
245 const int64 stored_date_value = | 311 const int64 stored_date_value = |
246 local_state_->GetInt64(prefs::kVariationsSeedDate); | 312 local_state_->GetInt64(prefs::kVariationsSeedDate); |
247 const base::Time stored_date = | 313 const base::Time stored_date = |
248 base::Time::FromInternalValue(stored_date_value); | 314 base::Time::FromInternalValue(stored_date_value); |
249 | 315 |
250 date_change = GetSeedDateChangeState(server_date_fetched, stored_date); | 316 date_change = GetSeedDateChangeState(server_date_fetched, stored_date); |
251 } | 317 } |
252 | 318 |
253 UMA_HISTOGRAM_ENUMERATION("Variations.SeedDateChange", date_change, | 319 UMA_HISTOGRAM_ENUMERATION("Variations.SeedDateChange", date_change, |
254 SEED_DATE_ENUM_SIZE); | 320 SEED_DATE_ENUM_SIZE); |
255 | 321 |
256 local_state_->SetInt64(prefs::kVariationsSeedDate, | 322 local_state_->SetInt64(prefs::kVariationsSeedDate, |
257 server_date_fetched.ToInternalValue()); | 323 server_date_fetched.ToInternalValue()); |
258 } | 324 } |
259 | 325 |
| 326 |
| 327 std::string VariationsSeedStore::GetInvalidSignature() const { |
| 328 return invalid_base64_signature_; |
| 329 } |
| 330 |
260 // static | 331 // static |
261 void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) { | 332 void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) { |
262 registry->RegisterStringPref(prefs::kVariationsCompressedSeed, std::string()); | 333 registry->RegisterStringPref(prefs::kVariationsCompressedSeed, std::string()); |
263 registry->RegisterStringPref(prefs::kVariationsSeed, std::string()); | 334 registry->RegisterStringPref(prefs::kVariationsSeed, std::string()); |
264 registry->RegisterInt64Pref(prefs::kVariationsSeedDate, | 335 registry->RegisterInt64Pref(prefs::kVariationsSeedDate, |
265 base::Time().ToInternalValue()); | 336 base::Time().ToInternalValue()); |
266 registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string()); | 337 registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string()); |
267 registry->RegisterStringPref(prefs::kVariationsCountry, std::string()); | 338 registry->RegisterStringPref(prefs::kVariationsCountry, std::string()); |
268 } | 339 } |
269 | 340 |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
328 return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE; | 399 return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE; |
329 } | 400 } |
330 | 401 |
331 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(seed_bytes.data()), | 402 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(seed_bytes.data()), |
332 seed_bytes.size()); | 403 seed_bytes.size()); |
333 if (verifier.VerifyFinal()) | 404 if (verifier.VerifyFinal()) |
334 return VARIATIONS_SEED_SIGNATURE_VALID; | 405 return VARIATIONS_SEED_SIGNATURE_VALID; |
335 return VARIATIONS_SEED_SIGNATURE_INVALID_SEED; | 406 return VARIATIONS_SEED_SIGNATURE_INVALID_SEED; |
336 } | 407 } |
337 | 408 |
338 std::string VariationsSeedStore::GetInvalidSignature() const { | 409 // static |
339 return invalid_base64_signature_; | 410 bool VariationsSeedStore::ApplyDeltaPatch(const std::string& existing_data, |
| 411 const std::string& patch, |
| 412 std::string* output) { |
| 413 output->clear(); |
| 414 |
| 415 google::protobuf::io::CodedInputStream in( |
| 416 reinterpret_cast<const uint8*>(patch.data()), patch.length()); |
| 417 // Temporary string declared outside the loop so it can be re-used between |
| 418 // different iterations (rather than allocating new ones). |
| 419 std::string temp; |
| 420 |
| 421 const uint32 existing_data_size = static_cast<uint32>(existing_data.size()); |
| 422 while (in.CurrentPosition() != static_cast<int>(patch.length())) { |
| 423 uint32 value; |
| 424 if (!in.ReadVarint32(&value)) |
| 425 return false; |
| 426 |
| 427 if (value != 0) { |
| 428 // A non-zero value indicates the number of bytes to copy from the patch |
| 429 // stream to the output. |
| 430 |
| 431 // No need to guard against bad data (i.e. very large |value|) because the |
| 432 // call below will fail if |value| is greater than the size of the patch. |
| 433 if (!in.ReadString(&temp, value)) |
| 434 return false; |
| 435 output->append(temp); |
| 436 } else { |
| 437 // Otherwise, when it's zero, it indicates that it's followed by a pair of |
| 438 // numbers - |offset| and |length| that specify a range of data to copy |
| 439 // from |existing_data|. |
| 440 uint32 offset; |
| 441 uint32 length; |
| 442 if (!in.ReadVarint32(&offset) || !in.ReadVarint32(&length)) |
| 443 return false; |
| 444 |
| 445 // Check for |offset + length| being out of range and for overflow. |
| 446 base::CheckedNumeric<uint32> end_offset(offset); |
| 447 end_offset += length; |
| 448 if (!end_offset.IsValid() || end_offset.ValueOrDie() > existing_data_size) |
| 449 return false; |
| 450 output->append(existing_data, offset, length); |
| 451 } |
| 452 } |
| 453 return true; |
340 } | 454 } |
341 | 455 |
342 } // namespace chrome_variations | 456 } // namespace chrome_variations |
OLD | NEW |