1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/safe_browsing/prefix_set.h" 6 7 #include <algorithm> 8 #include <iterator> 9 10 #include "base/file_util.h" 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/logging.h" 13 #include "base/md5.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "base/rand_util.h" 16 #include "testing/gtest/include/gtest/gtest.h" 17 #include "testing/platform_test.h" 18 19 namespace { 20 21 class PrefixSetTest : public PlatformTest { 22 protected: 23 // Constants for the v1 format. 24 static const size_t kMagicOffset = 0 * sizeof(uint32); 25 static const size_t kVersionOffset = 1 * sizeof(uint32); 26 static const size_t kIndexSizeOffset = 2 * sizeof(uint32); 27 static const size_t kDeltasSizeOffset = 3 * sizeof(uint32); 28 static const size_t kPayloadOffset = 4 * sizeof(uint32); 29 30 // Generate a set of random prefixes to share between tests. For 31 // most tests this generation was a large fraction of the test time. 32 static void SetUpTestCase() { 33 for (size_t i = 0; i < 50000; ++i) { 34 const SBPrefix prefix = static_cast<SBPrefix>(base::RandUint64()); 35 shared_prefixes_.push_back(prefix); 36 } 37 38 // Sort for use with PrefixSet constructor. 39 std::sort(shared_prefixes_.begin(), shared_prefixes_.end()); 40 } 41 42 // Check that all elements of |prefixes| are in |prefix_set|, and 43 // that nearby elements are not (for lack of a more sensible set of 44 // items to check for absence). 45 static void CheckPrefixes(const safe_browsing::PrefixSet& prefix_set, 46 const std::vector<SBPrefix> &prefixes) { 47 // The set can generate the prefixes it believes it has, so that's 48 // a good starting point. 49 std::set<SBPrefix> check(prefixes.begin(), prefixes.end()); 50 std::vector<SBPrefix> prefixes_copy; 51 prefix_set.GetPrefixes(&prefixes_copy); 52 EXPECT_EQ(prefixes_copy.size(), check.size()); 53 EXPECT_TRUE(std::equal(check.begin(), check.end(), prefixes_copy.begin())); 54 55 for (size_t i = 0; i < prefixes.size(); ++i) { 56 EXPECT_TRUE(prefix_set.Exists(prefixes[i])); 57 58 const SBPrefix left_sibling = prefixes[i] - 1; 59 if (check.count(left_sibling) == 0) 60 EXPECT_FALSE(prefix_set.Exists(left_sibling)); 61 62 const SBPrefix right_sibling = prefixes[i] + 1; 63 if (check.count(right_sibling) == 0) 64 EXPECT_FALSE(prefix_set.Exists(right_sibling)); 65 } 66 } 67 68 // Generate a |PrefixSet| file from |shared_prefixes_|, store it in 69 // a temporary file, and return the filename in |filenamep|. 70 // Returns |true| on success. 71 bool GetPrefixSetFile(base::FilePath* filenamep) { 72 if (!temp_dir_.IsValid() && !temp_dir_.CreateUniqueTempDir()) 73 return false; 74 75 base::FilePath filename = temp_dir_.path().AppendASCII("PrefixSetTest"); 76 77 safe_browsing::PrefixSet prefix_set(shared_prefixes_); 78 if (!prefix_set.WriteFile(filename)) 79 return false; 80 81 *filenamep = filename; 82 return true; 83 } 84 85 // Helper function to read the int32 value at |offset|, increment it 86 // by |inc|, and write it back in place. |fp| should be opened in 87 // r+ mode. 88 static void IncrementIntAt(FILE* fp, long offset, int inc) { 89 int32 value = 0; 90 91 ASSERT_NE(-1, fseek(fp, offset, SEEK_SET)); 92 ASSERT_EQ(1U, fread(&value, sizeof(value), 1, fp)); 93 94 value += inc; 95 96 ASSERT_NE(-1, fseek(fp, offset, SEEK_SET)); 97 ASSERT_EQ(1U, fwrite(&value, sizeof(value), 1, fp)); 98 } 99 100 // Helper function to re-generated |fp|'s checksum to be correct for 101 // the file's contents. |fp| should be opened in r+ mode. 102 static void CleanChecksum(FILE* fp) { 103 base::MD5Context context; 104 base::MD5Init(&context); 105 106 ASSERT_NE(-1, fseek(fp, 0, SEEK_END)); 107 long file_size = ftell(fp); 108 109 using base::MD5Digest; 110 size_t payload_size = static_cast<size_t>(file_size) - sizeof(MD5Digest); 111 size_t digested_size = 0; 112 ASSERT_NE(-1, fseek(fp, 0, SEEK_SET)); 113 while (digested_size < payload_size) { 114 char buf[1024]; 115 size_t nitems = std::min(payload_size - digested_size, sizeof(buf)); 116 ASSERT_EQ(nitems, fread(buf, 1, nitems, fp)); 117 base::MD5Update(&context, base::StringPiece(buf, nitems)); 118 digested_size += nitems; 119 } 120 ASSERT_EQ(digested_size, payload_size); 121 ASSERT_EQ(static_cast<long>(digested_size), ftell(fp)); 122 123 base::MD5Digest new_digest; 124 base::MD5Final(&new_digest, &context); 125 ASSERT_NE(-1, fseek(fp, digested_size, SEEK_SET)); 126 ASSERT_EQ(1U, fwrite(&new_digest, sizeof(new_digest), 1, fp)); 127 ASSERT_EQ(file_size, ftell(fp)); 128 } 129 130 // Open |filename| and increment the int32 at |offset| by |inc|. 131 // Then re-generate the checksum to account for the new contents. 132 void ModifyAndCleanChecksum(const base::FilePath& filename, long offset, 133 int inc) { 134 int64 size_64; 135 ASSERT_TRUE(file_util::GetFileSize(filename, &size_64)); 136 137 file_util::ScopedFILE file(file_util::OpenFile(filename, "r+b")); 138 IncrementIntAt(file.get(), offset, inc); 139 CleanChecksum(file.get()); 140 file.reset(); 141 142 int64 new_size_64; 143 ASSERT_TRUE(file_util::GetFileSize(filename, &new_size_64)); 144 ASSERT_EQ(new_size_64, size_64); 145 } 146 147 // Tests should not modify this shared resource. 148 static std::vector<SBPrefix> shared_prefixes_; 149 150 base::ScopedTempDir temp_dir_; 151 }; 152 153 std::vector<SBPrefix> PrefixSetTest::shared_prefixes_; 154 155 // Test that a small sparse random input works. 156 TEST_F(PrefixSetTest, Baseline) { 157 safe_browsing::PrefixSet prefix_set(shared_prefixes_); 158 CheckPrefixes(prefix_set, shared_prefixes_); 159 } 160 161 // Test that the empty set doesn't appear to have anything in it. 162 TEST_F(PrefixSetTest, Empty) { 163 const std::vector<SBPrefix> empty; 164 safe_browsing::PrefixSet prefix_set(empty); 165 for (size_t i = 0; i < shared_prefixes_.size(); ++i) { 166 EXPECT_FALSE(prefix_set.Exists(shared_prefixes_[i])); 167 } 168 } 169 170 // Single-element set should work fine. 171 TEST_F(PrefixSetTest, OneElement) { 172 const std::vector<SBPrefix> prefixes(100, 0); 173 safe_browsing::PrefixSet prefix_set(prefixes); 174 EXPECT_FALSE(prefix_set.Exists(-1)); 175 EXPECT_TRUE(prefix_set.Exists(prefixes[0])); 176 EXPECT_FALSE(prefix_set.Exists(1)); 177 178 // Check that |GetPrefixes()| returns the same set of prefixes as 179 // was passed in. 180 std::vector<SBPrefix> prefixes_copy; 181 prefix_set.GetPrefixes(&prefixes_copy); 182 EXPECT_EQ(1U, prefixes_copy.size()); 183 EXPECT_EQ(prefixes[0], prefixes_copy[0]); 184 } 185 186 // Edges of the 32-bit integer range. 187 TEST_F(PrefixSetTest, IntMinMax) { 188 std::vector<SBPrefix> prefixes; 189 190 // Using bit patterns rather than portable constants because this 191 // really is testing how the entire 32-bit integer range is handled. 192 prefixes.push_back(0x00000000); 193 prefixes.push_back(0x0000FFFF); 194 prefixes.push_back(0x7FFF0000); 195 prefixes.push_back(0x7FFFFFFF); 196 prefixes.push_back(0x80000000); 197 prefixes.push_back(0x8000FFFF); 198 prefixes.push_back(0xFFFF0000); 199 prefixes.push_back(0xFFFFFFFF); 200 201 std::sort(prefixes.begin(), prefixes.end()); 202 safe_browsing::PrefixSet prefix_set(prefixes); 203 204 // Check that |GetPrefixes()| returns the same set of prefixes as 205 // was passed in. 206 std::vector<SBPrefix> prefixes_copy; 207 prefix_set.GetPrefixes(&prefixes_copy); 208 ASSERT_EQ(prefixes_copy.size(), prefixes.size()); 209 EXPECT_TRUE(std::equal(prefixes.begin(), prefixes.end(), 210 prefixes_copy.begin())); 211 } 212 213 // A range with only large deltas. 214 TEST_F(PrefixSetTest, AllBig) { 215 std::vector<SBPrefix> prefixes; 216 217 const SBPrefix kVeryPositive = 1000 * 1000 * 1000; 218 const SBPrefix kVeryNegative = -kVeryPositive; 219 const unsigned kDelta = 10 * 1000 * 1000; 220 221 for (SBPrefix prefix = kVeryNegative; 222 prefix < kVeryPositive; prefix += kDelta) { 223 prefixes.push_back(prefix); 224 } 225 226 std::sort(prefixes.begin(), prefixes.end()); 227 safe_browsing::PrefixSet prefix_set(prefixes); 228 229 // Check that |GetPrefixes()| returns the same set of prefixes as 230 // was passed in. 231 std::vector<SBPrefix> prefixes_copy; 232 prefix_set.GetPrefixes(&prefixes_copy); 233 prefixes.erase(std::unique(prefixes.begin(), prefixes.end()), prefixes.end()); 234 EXPECT_EQ(prefixes_copy.size(), prefixes.size()); 235 EXPECT_TRUE(std::equal(prefixes.begin(), prefixes.end(), 236 prefixes_copy.begin())); 237 } 238 239 // Use artificial inputs to test various edge cases in Exists(). 240 // Items before the lowest item aren't present. Items after the 241 // largest item aren't present. Create a sequence of items with 242 // deltas above and below 2^16, and make sure they're all present. 243 // Create a very long sequence with deltas below 2^16 to test crossing 244 // |kMaxRun|. 245 TEST_F(PrefixSetTest, EdgeCases) { 246 std::vector<SBPrefix> prefixes; 247 248 const SBPrefix kVeryPositive = 1000 * 1000 * 1000; 249 const SBPrefix kVeryNegative = -kVeryPositive; 250 251 // Put in a very negative prefix. 252 SBPrefix prefix = kVeryNegative; 253 prefixes.push_back(prefix); 254 255 // Add a sequence with very large deltas. 256 unsigned delta = 100 * 1000 * 1000; 257 for (int i = 0; i < 10; ++i) { 258 prefix += delta; 259 prefixes.push_back(prefix); 260 } 261 262 // Add a sequence with deltas that start out smaller than the 263 // maximum delta, and end up larger. Also include some duplicates. 264 delta = 256 * 256 - 100; 265 for (int i = 0; i < 200; ++i) { 266 prefix += delta; 267 prefixes.push_back(prefix); 268 prefixes.push_back(prefix); 269 delta++; 270 } 271 272 // Add a long sequence with deltas smaller than the maximum delta, 273 // so a new index item will be injected. 274 delta = 256 * 256 - 1; 275 prefix = kVeryPositive - delta * 1000; 276 prefixes.push_back(prefix); 277 for (int i = 0; i < 1000; ++i) { 278 prefix += delta; 279 prefixes.push_back(prefix); 280 delta--; 281 } 282 283 std::sort(prefixes.begin(), prefixes.end()); 284 safe_browsing::PrefixSet prefix_set(prefixes); 285 286 // Check that |GetPrefixes()| returns the same set of prefixes as 287 // was passed in. 288 std::vector<SBPrefix> prefixes_copy; 289 prefix_set.GetPrefixes(&prefixes_copy); 290 prefixes.erase(std::unique(prefixes.begin(), prefixes.end()), prefixes.end()); 291 EXPECT_EQ(prefixes_copy.size(), prefixes.size()); 292 EXPECT_TRUE(std::equal(prefixes.begin(), prefixes.end(), 293 prefixes_copy.begin())); 294 295 // Items before and after the set are not present, and don't crash. 296 EXPECT_FALSE(prefix_set.Exists(kVeryNegative - 100)); 297 EXPECT_FALSE(prefix_set.Exists(kVeryPositive + 100)); 298 299 // Check that the set correctly flags all of the inputs, and also 300 // check items just above and below the inputs to make sure they 301 // aren't present. 302 for (size_t i = 0; i < prefixes.size(); ++i) { 303 EXPECT_TRUE(prefix_set.Exists(prefixes[i])); 304 305 EXPECT_FALSE(prefix_set.Exists(prefixes[i] - 1)); 306 EXPECT_FALSE(prefix_set.Exists(prefixes[i] + 1)); 307 } 308 } 309 310 // Test writing a prefix set to disk and reading it back in. 311 TEST_F(PrefixSetTest, ReadWrite) { 312 base::FilePath filename; 313 314 // Write the sample prefix set out, read it back in, and check all 315 // the prefixes. Leaves the path in |filename|. 316 { 317 ASSERT_TRUE(GetPrefixSetFile(&filename)); 318 scoped_ptr<safe_browsing::PrefixSet> 319 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 320 ASSERT_TRUE(prefix_set.get()); 321 CheckPrefixes(*prefix_set, shared_prefixes_); 322 } 323 324 // Test writing and reading a very sparse set containing no deltas. 325 { 326 const SBPrefix kVeryPositive = 1000 * 1000 * 1000; 327 const SBPrefix kVeryNegative = -kVeryPositive; 328 329 std::vector<SBPrefix> prefixes; 330 prefixes.push_back(kVeryNegative); 331 prefixes.push_back(kVeryPositive); 332 333 safe_browsing::PrefixSet prefix_set_to_write(prefixes); 334 ASSERT_TRUE(prefix_set_to_write.WriteFile(filename)); 335 scoped_ptr<safe_browsing::PrefixSet> 336 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 337 ASSERT_TRUE(prefix_set.get()); 338 CheckPrefixes(*prefix_set, prefixes); 339 } 340 341 // Test writing and reading an empty set. 342 { 343 std::vector<SBPrefix> prefixes; 344 safe_browsing::PrefixSet prefix_set_to_write(prefixes); 345 ASSERT_TRUE(prefix_set_to_write.WriteFile(filename)); 346 scoped_ptr<safe_browsing::PrefixSet> 347 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 348 ASSERT_TRUE(prefix_set.get()); 349 CheckPrefixes(*prefix_set, prefixes); 350 } 351 } 352 353 // Check that |CleanChecksum()| makes an acceptable checksum. 354 TEST_F(PrefixSetTest, CorruptionHelpers) { 355 base::FilePath filename; 356 ASSERT_TRUE(GetPrefixSetFile(&filename)); 357 358 // This will modify data in |index_|, which will fail the digest check. 359 file_util::ScopedFILE file(file_util::OpenFile(filename, "r+b")); 360 IncrementIntAt(file.get(), kPayloadOffset, 1); 361 file.reset(); 362 scoped_ptr<safe_browsing::PrefixSet> 363 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 364 ASSERT_FALSE(prefix_set.get()); 365 366 // Fix up the checksum and it will read successfully (though the 367 // data will be wrong). 368 file.reset(file_util::OpenFile(filename, "r+b")); 369 CleanChecksum(file.get()); 370 file.reset(); 371 prefix_set.reset(safe_browsing::PrefixSet::LoadFile(filename)); 372 ASSERT_TRUE(prefix_set.get()); 373 } 374 375 // Bad |index_| size is caught by the sanity check. 376 TEST_F(PrefixSetTest, CorruptionMagic) { 377 base::FilePath filename; 378 ASSERT_TRUE(GetPrefixSetFile(&filename)); 379 380 ASSERT_NO_FATAL_FAILURE( 381 ModifyAndCleanChecksum(filename, kMagicOffset, 1)); 382 scoped_ptr<safe_browsing::PrefixSet> 383 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 384 ASSERT_FALSE(prefix_set.get()); 385 } 386 387 // Bad |index_| size is caught by the sanity check. 388 TEST_F(PrefixSetTest, CorruptionVersion) { 389 base::FilePath filename; 390 ASSERT_TRUE(GetPrefixSetFile(&filename)); 391 392 ASSERT_NO_FATAL_FAILURE( 393 ModifyAndCleanChecksum(filename, kVersionOffset, 1)); 394 scoped_ptr<safe_browsing::PrefixSet> 395 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 396 ASSERT_FALSE(prefix_set.get()); 397 } 398 399 // Bad |index_| size is caught by the sanity check. 400 TEST_F(PrefixSetTest, CorruptionIndexSize) { 401 base::FilePath filename; 402 ASSERT_TRUE(GetPrefixSetFile(&filename)); 403 404 ASSERT_NO_FATAL_FAILURE( 405 ModifyAndCleanChecksum(filename, kIndexSizeOffset, 1)); 406 scoped_ptr<safe_browsing::PrefixSet> 407 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 408 ASSERT_FALSE(prefix_set.get()); 409 } 410 411 // Bad |deltas_| size is caught by the sanity check. 412 TEST_F(PrefixSetTest, CorruptionDeltasSize) { 413 base::FilePath filename; 414 ASSERT_TRUE(GetPrefixSetFile(&filename)); 415 416 ASSERT_NO_FATAL_FAILURE( 417 ModifyAndCleanChecksum(filename, kDeltasSizeOffset, 1)); 418 scoped_ptr<safe_browsing::PrefixSet> 419 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 420 ASSERT_FALSE(prefix_set.get()); 421 } 422 423 // Test that the digest catches corruption in the middle of the file 424 // (in the payload between the header and the digest). 425 TEST_F(PrefixSetTest, CorruptionPayload) { 426 base::FilePath filename; 427 ASSERT_TRUE(GetPrefixSetFile(&filename)); 428 429 file_util::ScopedFILE file(file_util::OpenFile(filename, "r+b")); 430 ASSERT_NO_FATAL_FAILURE(IncrementIntAt(file.get(), 666, 1)); 431 file.reset(); 432 scoped_ptr<safe_browsing::PrefixSet> 433 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 434 ASSERT_FALSE(prefix_set.get()); 435 } 436 437 // Test corruption in the digest itself. 438 TEST_F(PrefixSetTest, CorruptionDigest) { 439 base::FilePath filename; 440 ASSERT_TRUE(GetPrefixSetFile(&filename)); 441 442 int64 size_64; 443 ASSERT_TRUE(file_util::GetFileSize(filename, &size_64)); 444 file_util::ScopedFILE file(file_util::OpenFile(filename, "r+b")); 445 long digest_offset = static_cast<long>(size_64 - sizeof(base::MD5Digest)); 446 ASSERT_NO_FATAL_FAILURE(IncrementIntAt(file.get(), digest_offset, 1)); 447 file.reset(); 448 scoped_ptr<safe_browsing::PrefixSet> 449 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 450 ASSERT_FALSE(prefix_set.get()); 451 } 452 453 // Test excess data after the digest (fails the size test). 454 TEST_F(PrefixSetTest, CorruptionExcess) { 455 base::FilePath filename; 456 ASSERT_TRUE(GetPrefixSetFile(&filename)); 457 458 // Add some junk to the trunk. 459 file_util::ScopedFILE file(file_util::OpenFile(filename, "ab")); 460 const char buf[] = "im in ur base, killing ur d00dz."; 461 ASSERT_EQ(strlen(buf), fwrite(buf, 1, strlen(buf), file.get())); 462 file.reset(); 463 scoped_ptr<safe_browsing::PrefixSet> 464 prefix_set(safe_browsing::PrefixSet::LoadFile(filename)); 465 ASSERT_FALSE(prefix_set.get()); 466 } 467 468 } // namespace 469