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