| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 "base/base64.h" | 5 #include "base/base64.h" |
| 6 #include "base/bind.h" | 6 #include "base/bind.h" |
| 7 #include "base/files/file_util.h" | 7 #include "base/files/file_util.h" |
| 8 #include "base/metrics/histogram_macros.h" | 8 #include "base/metrics/histogram_macros.h" |
| 9 #include "base/metrics/sparse_histogram.h" | 9 #include "base/metrics/sparse_histogram.h" |
| 10 #include "base/strings/stringprintf.h" | 10 #include "base/strings/stringprintf.h" |
| 11 #include "components/safe_browsing_db/v4_store.h" | 11 #include "components/safe_browsing_db/v4_store.h" |
| 12 #include "components/safe_browsing_db/v4_store.pb.h" | 12 #include "components/safe_browsing_db/v4_store.pb.h" |
| 13 | 13 |
| 14 namespace safe_browsing { | 14 namespace safe_browsing { |
| 15 | 15 |
| 16 namespace { | 16 namespace { |
| 17 const uint32_t kFileMagic = 0x600D71FE; | 17 const uint32_t kFileMagic = 0x600D71FE; |
| 18 | 18 |
| 19 const uint32_t kFileVersion = 9; | 19 const uint32_t kFileVersion = 9; |
| 20 | 20 |
| 21 // The minimum expected size (in bytes) of a hash-prefix. |
| 22 const uint32_t kMinHashPrefixLength = 4; |
| 23 |
| 24 // The maximum expected size (in bytes) of a hash-prefix. This represents the |
| 25 // length of a SHA256 hash. |
| 26 const uint32_t kMaxHashPrefixLength = 32; |
| 27 |
| 21 void RecordStoreReadResult(StoreReadResult result) { | 28 void RecordStoreReadResult(StoreReadResult result) { |
| 22 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreReadResult", result, | 29 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreReadResult", result, |
| 23 STORE_READ_RESULT_MAX); | 30 STORE_READ_RESULT_MAX); |
| 24 } | 31 } |
| 25 | 32 |
| 26 void RecordStoreWriteResult(StoreWriteResult result) { | 33 void RecordStoreWriteResult(StoreWriteResult result) { |
| 27 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreWriteResult", result, | 34 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreWriteResult", result, |
| 28 STORE_WRITE_RESULT_MAX); | 35 STORE_WRITE_RESULT_MAX); |
| 29 } | 36 } |
| 30 | 37 |
| 38 void RecordApplyUpdateResult(ApplyUpdateResult result) { |
| 39 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4ApplyUpdateResult", result, |
| 40 APPLY_UPDATE_RESULT_MAX); |
| 41 } |
| 42 |
| 31 // Returns the name of the temporary file used to buffer data for | 43 // Returns the name of the temporary file used to buffer data for |
| 32 // |filename|. Exported for unit tests. | 44 // |filename|. Exported for unit tests. |
| 33 const base::FilePath TemporaryFileForFilename(const base::FilePath& filename) { | 45 const base::FilePath TemporaryFileForFilename(const base::FilePath& filename) { |
| 34 return base::FilePath(filename.value() + FILE_PATH_LITERAL("_new")); | 46 return base::FilePath(filename.value() + FILE_PATH_LITERAL("_new")); |
| 35 } | 47 } |
| 36 | 48 |
| 37 } // namespace | 49 } // namespace |
| 38 | 50 |
| 39 std::ostream& operator<<(std::ostream& os, const V4Store& store) { | 51 std::ostream& operator<<(std::ostream& os, const V4Store& store) { |
| 40 os << store.DebugString(); | 52 os << store.DebugString(); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 76 state_ = ""; | 88 state_ = ""; |
| 77 return true; | 89 return true; |
| 78 } | 90 } |
| 79 | 91 |
| 80 void V4Store::ApplyUpdate( | 92 void V4Store::ApplyUpdate( |
| 81 std::unique_ptr<ListUpdateResponse> response, | 93 std::unique_ptr<ListUpdateResponse> response, |
| 82 const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner, | 94 const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner, |
| 83 UpdatedStoreReadyCallback callback) { | 95 UpdatedStoreReadyCallback callback) { |
| 84 std::unique_ptr<V4Store> new_store( | 96 std::unique_ptr<V4Store> new_store( |
| 85 new V4Store(this->task_runner_, this->store_path_)); | 97 new V4Store(this->task_runner_, this->store_path_)); |
| 86 | |
| 87 new_store->state_ = response->new_client_state(); | 98 new_store->state_ = response->new_client_state(); |
| 88 // TODO(vakh): | 99 // TODO(vakh): |
| 89 // 1. Merge the old store and the new update in new_store. | 100 // 1. Merge the old store and the new update in new_store. |
| 90 // 2. Create a ListUpdateResponse containing RICE encoded hash-prefixes and | 101 // 2. Create a ListUpdateResponse containing RICE encoded hash-prefixes and |
| 91 // response_type as FULL_UPDATE, and write that to disk. | 102 // response_type as FULL_UPDATE, and write that to disk. |
| 92 // 3. Remove this if condition after completing 1. and 2. | 103 // 3. Remove this if condition after completing 1. and 2. |
| 93 if (response->has_response_type() && | 104 HashPrefixMap hash_prefix_map; |
| 94 response->response_type() == ListUpdateResponse::FULL_UPDATE) { | 105 ApplyUpdateResult apply_update_result; |
| 95 StoreWriteResult result = new_store->WriteToDisk(std::move(response)); | 106 if (response->response_type() == ListUpdateResponse::PARTIAL_UPDATE) { |
| 96 RecordStoreWriteResult(result); | 107 for (const auto& removal : response->removals()) { |
| 97 } | 108 // TODO(vakh): Allow other compression types. |
| 98 | 109 // See: https://bugs.chromium.org/p/chromium/issues/detail?id=624567 |
| 99 // new_store is done updating, pass it to the callback. | 110 DCHECK_EQ(RAW, removal.compression_type()); |
| 100 callback_task_runner->PostTask( | 111 } |
| 101 FROM_HERE, base::Bind(callback, base::Passed(&new_store))); | 112 |
| 113 apply_update_result = UpdateHashPrefixMapFromAdditions( |
| 114 response->additions(), &hash_prefix_map); |
| 115 if (apply_update_result == APPLY_UPDATE_SUCCESS) { |
| 116 apply_update_result = |
| 117 new_store->MergeUpdate(hash_prefix_map_, hash_prefix_map); |
| 118 } |
| 119 // TODO(vakh): Generate the updated ListUpdateResponse to write to disk. |
| 120 } else if (response->response_type() == ListUpdateResponse::FULL_UPDATE) { |
| 121 apply_update_result = UpdateHashPrefixMapFromAdditions( |
| 122 response->additions(), &hash_prefix_map); |
| 123 if (apply_update_result == APPLY_UPDATE_SUCCESS) { |
| 124 new_store->hash_prefix_map_ = hash_prefix_map; |
| 125 RecordStoreWriteResult(new_store->WriteToDisk(std::move(response))); |
| 126 } |
| 127 } else { |
| 128 apply_update_result = UNEXPECTED_RESPONSE_TYPE_FAILURE; |
| 129 NOTREACHED() << "Unexpected response type: " << response->response_type(); |
| 130 } |
| 131 |
| 132 if (apply_update_result == APPLY_UPDATE_SUCCESS) { |
| 133 // new_store is done updating, pass it to the callback. |
| 134 callback_task_runner->PostTask( |
| 135 FROM_HERE, base::Bind(callback, base::Passed(&new_store))); |
| 136 } else { |
| 137 // new_store failed updating. Pass a nullptr to the callback. |
| 138 callback_task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr)); |
| 139 } |
| 140 |
| 141 RecordApplyUpdateResult(apply_update_result); |
| 142 } |
| 143 |
| 144 // static |
| 145 ApplyUpdateResult V4Store::UpdateHashPrefixMapFromAdditions( |
| 146 const ::google::protobuf::RepeatedPtrField<ThreatEntrySet>& additions, |
| 147 HashPrefixMap* additions_map) { |
| 148 for (const auto& addition : additions) { |
| 149 // TODO(vakh): Allow other compression types. |
| 150 // See: https://bugs.chromium.org/p/chromium/issues/detail?id=624567 |
| 151 DCHECK_EQ(RAW, addition.compression_type()); |
| 152 |
| 153 DCHECK(addition.has_raw_hashes()); |
| 154 DCHECK(addition.raw_hashes().has_raw_hashes()); |
| 155 |
| 156 PrefixSize prefix_size = addition.raw_hashes().prefix_size(); |
| 157 ApplyUpdateResult result = AddUnlumpedHashes( |
| 158 prefix_size, addition.raw_hashes().raw_hashes(), additions_map); |
| 159 if (result != APPLY_UPDATE_SUCCESS) { |
| 160 // If there was an error in updating the map, discard the update entirely. |
| 161 return result; |
| 162 } |
| 163 } |
| 164 return APPLY_UPDATE_SUCCESS; |
| 165 } |
| 166 |
| 167 // static |
| 168 ApplyUpdateResult V4Store::AddUnlumpedHashes(PrefixSize prefix_size, |
| 169 const std::string& lumped_hashes, |
| 170 HashPrefixMap* additions_map) { |
| 171 if (prefix_size < kMinHashPrefixLength) { |
| 172 NOTREACHED(); |
| 173 return PREFIX_SIZE_TOO_SMALL_FAILURE; |
| 174 } |
| 175 if (prefix_size > kMaxHashPrefixLength) { |
| 176 NOTREACHED(); |
| 177 return PREFIX_SIZE_TOO_LARGE_FAILURE; |
| 178 } |
| 179 if (lumped_hashes.size() % prefix_size != 0) { |
| 180 return ADDITIONS_SIZE_UNEXPECTED_FAILURE; |
| 181 } |
| 182 // TODO(vakh): Figure out a way to avoid the following copy operation. |
| 183 (*additions_map)[prefix_size] = lumped_hashes; |
| 184 return APPLY_UPDATE_SUCCESS; |
| 185 } |
| 186 |
| 187 // static |
| 188 bool V4Store::GetNextSmallestPrefixSize(const HashPrefixMap& hash_prefix_map, |
| 189 const CounterMap& counter_map, |
| 190 PrefixSize* smallest_prefix_size) { |
| 191 HashPrefix smallest_prefix, current_prefix; |
| 192 for (const auto& counter_pair : counter_map) { |
| 193 PrefixSize prefix_size = counter_pair.first; |
| 194 size_t index = counter_pair.second; |
| 195 size_t sized_index = prefix_size * index; |
| 196 |
| 197 const HashPrefixes& hash_prefixes = hash_prefix_map.at(prefix_size); |
| 198 if (sized_index < hash_prefixes.size()) { |
| 199 current_prefix = hash_prefixes.substr(sized_index, prefix_size); |
| 200 if (smallest_prefix.empty() || smallest_prefix > current_prefix) { |
| 201 smallest_prefix = current_prefix; |
| 202 *smallest_prefix_size = prefix_size; |
| 203 } |
| 204 } |
| 205 } |
| 206 return !smallest_prefix.empty(); |
| 207 } |
| 208 |
| 209 // static |
| 210 void V4Store::InitializeCounterMap(const HashPrefixMap& hash_prefix_map, |
| 211 CounterMap* counter_map) { |
| 212 for (const auto& map_pair : hash_prefix_map) { |
| 213 (*counter_map)[map_pair.first] = 0; |
| 214 } |
| 215 } |
| 216 |
| 217 // static |
| 218 HashPrefix V4Store::GetNextUnmergedPrefixForSize( |
| 219 PrefixSize prefix_size, |
| 220 const HashPrefixMap& hash_prefix_map, |
| 221 const CounterMap& counter_map) { |
| 222 const HashPrefixes& hash_prefixes = hash_prefix_map.at(prefix_size); |
| 223 size_t index_within_list = counter_map.at(prefix_size); |
| 224 size_t sized_index = prefix_size * index_within_list; |
| 225 return hash_prefixes.substr(sized_index, prefix_size); |
| 226 } |
| 227 |
| 228 // static |
| 229 void V4Store::ReserveSpaceInPrefixMap(const HashPrefixMap& other_prefixes_map, |
| 230 HashPrefixMap* prefix_map_to_update) { |
| 231 for (const auto& pair : other_prefixes_map) { |
| 232 PrefixSize prefix_size = pair.first; |
| 233 size_t prefix_length_to_add = pair.second.length(); |
| 234 |
| 235 const HashPrefixes& existing_prefixes = |
| 236 (*prefix_map_to_update)[prefix_size]; |
| 237 size_t existing_capacity = existing_prefixes.capacity(); |
| 238 |
| 239 (*prefix_map_to_update)[prefix_size].reserve(existing_capacity + |
| 240 prefix_length_to_add); |
| 241 } |
| 242 } |
| 243 |
| 244 ApplyUpdateResult V4Store::MergeUpdate(const HashPrefixMap& old_prefixes_map, |
| 245 const HashPrefixMap& additions_map) { |
| 246 DCHECK(hash_prefix_map_.empty()); |
| 247 hash_prefix_map_.clear(); |
| 248 ReserveSpaceInPrefixMap(old_prefixes_map, &hash_prefix_map_); |
| 249 ReserveSpaceInPrefixMap(additions_map, &hash_prefix_map_); |
| 250 |
| 251 PrefixSize next_size_for_old; |
| 252 CounterMap old_counter_map; |
| 253 InitializeCounterMap(old_prefixes_map, &old_counter_map); |
| 254 bool old_has_unmerged = GetNextSmallestPrefixSize( |
| 255 old_prefixes_map, old_counter_map, &next_size_for_old); |
| 256 |
| 257 PrefixSize next_size_for_additions; |
| 258 CounterMap additions_counter_map; |
| 259 InitializeCounterMap(additions_map, &additions_counter_map); |
| 260 bool additions_has_unmerged = GetNextSmallestPrefixSize( |
| 261 additions_map, additions_counter_map, &next_size_for_additions); |
| 262 |
| 263 // Classical merge sort. |
| 264 // The two constructs to merge are maps: old_prefixes_map, additions_map. |
| 265 |
| 266 HashPrefix next_smallest_prefix_old, next_smallest_prefix_additions; |
| 267 // At least one of the maps still has elements that need to be merged into the |
| 268 // new store. |
| 269 while (old_has_unmerged || additions_has_unmerged) { |
| 270 // Old map still has elements that need to be merged. |
| 271 if (old_has_unmerged) { |
| 272 // Get the lexographically smallest hash prefix from the old store. |
| 273 next_smallest_prefix_old = GetNextUnmergedPrefixForSize( |
| 274 next_size_for_old, old_prefixes_map, old_counter_map); |
| 275 } |
| 276 |
| 277 // Additions map still has elements that need to be merged. |
| 278 if (additions_has_unmerged) { |
| 279 // Get the lexographically smallest hash prefix from the additions in the |
| 280 // latest update from the server. |
| 281 next_smallest_prefix_additions = GetNextUnmergedPrefixForSize( |
| 282 next_size_for_additions, additions_map, additions_counter_map); |
| 283 } |
| 284 |
| 285 // If the same hash prefix appears in the existing store and the additions |
| 286 // list, something is clearly wrong. Discard the update. |
| 287 if (old_has_unmerged && additions_has_unmerged && |
| 288 next_smallest_prefix_old == next_smallest_prefix_additions) { |
| 289 return ADDITIONS_HAS_EXISTING_PREFIX_FAILURE; |
| 290 } |
| 291 |
| 292 // Select which map to pick the next hash prefix from to keep the result in |
| 293 // lexographically sorted order. |
| 294 bool pick_from_old = |
| 295 old_has_unmerged && |
| 296 (!additions_has_unmerged || |
| 297 (next_smallest_prefix_old < next_smallest_prefix_additions)); |
| 298 |
| 299 if (pick_from_old) { |
| 300 hash_prefix_map_[next_size_for_old] += next_smallest_prefix_old; |
| 301 |
| 302 // Update the counter map, which means that we have merged one hash |
| 303 // prefix of size |next_size_for_old| from the old store. |
| 304 old_counter_map[next_size_for_old]++; |
| 305 |
| 306 // Find the next smallest unmerged element in the old store's map. |
| 307 old_has_unmerged = GetNextSmallestPrefixSize( |
| 308 old_prefixes_map, old_counter_map, &next_size_for_old); |
| 309 } else { |
| 310 hash_prefix_map_[next_size_for_additions] += |
| 311 next_smallest_prefix_additions; |
| 312 |
| 313 // Update the counter map, which means that we have merged one hash |
| 314 // prefix of size |next_size_for_additions| from the update. |
| 315 additions_counter_map[next_size_for_additions]++; |
| 316 |
| 317 // Find the next smallest unmerged element in the additions map. |
| 318 additions_has_unmerged = GetNextSmallestPrefixSize( |
| 319 additions_map, additions_counter_map, &next_size_for_additions); |
| 320 } |
| 321 } |
| 322 |
| 323 return APPLY_UPDATE_SUCCESS; |
| 102 } | 324 } |
| 103 | 325 |
| 104 StoreReadResult V4Store::ReadFromDisk() { | 326 StoreReadResult V4Store::ReadFromDisk() { |
| 105 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | 327 DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| 106 | 328 |
| 107 std::string contents; | 329 std::string contents; |
| 108 bool read_success = base::ReadFileToString(store_path_, &contents); | 330 bool read_success = base::ReadFileToString(store_path_, &contents); |
| 109 if (!read_success) { | 331 if (!read_success) { |
| 110 return FILE_UNREADABLE_FAILURE; | 332 return FILE_UNREADABLE_FAILURE; |
| 111 } | 333 } |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 174 | 396 |
| 175 if (!base::Move(new_filename, store_path_)) { | 397 if (!base::Move(new_filename, store_path_)) { |
| 176 DVLOG(1) << "store_path_: " << store_path_.value(); | 398 DVLOG(1) << "store_path_: " << store_path_.value(); |
| 177 return UNABLE_TO_RENAME_FAILURE; | 399 return UNABLE_TO_RENAME_FAILURE; |
| 178 } | 400 } |
| 179 | 401 |
| 180 return WRITE_SUCCESS; | 402 return WRITE_SUCCESS; |
| 181 } | 403 } |
| 182 | 404 |
| 183 } // namespace safe_browsing | 405 } // namespace safe_browsing |
| OLD | NEW |