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