• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
SetUpTestCase()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).
CheckPrefixes(safe_browsing::PrefixSet * prefix_set,const std::vector<SBPrefix> & prefixes)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.
GetPrefixSetFile(FilePath * filenamep)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.
IncrementIntAt(FILE * fp,long offset,int inc)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.
CleanChecksum(FILE * fp)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.
ModifyAndCleanChecksum(const FilePath & filename,long offset,int inc)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.
TEST_F(PrefixSetTest,Baseline)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.
TEST_F(PrefixSetTest,Empty)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.
TEST_F(PrefixSetTest,OneElement)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.
TEST_F(PrefixSetTest,IntMinMax)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.
TEST_F(PrefixSetTest,AllBig)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|.
TEST_F(PrefixSetTest,EdgeCases)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.
TEST_F(PrefixSetTest,ReadWrite)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.
TEST_F(PrefixSetTest,CorruptionHelpers)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.
TEST_F(PrefixSetTest,CorruptionMagic)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.
TEST_F(PrefixSetTest,CorruptionVersion)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.
TEST_F(PrefixSetTest,CorruptionIndexSize)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.
TEST_F(PrefixSetTest,CorruptionDeltasSize)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).
TEST_F(PrefixSetTest,CorruptionPayload)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.
TEST_F(PrefixSetTest,CorruptionDigest)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).
TEST_F(PrefixSetTest,CorruptionExcess)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!
TEST_F(PrefixSetTest,DebuggingAccessors)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