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.
5 #include "chrome/browser/safe_browsing/prefix_set.h"
10 #include "base/file_util.h"
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/logging.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"
21 class PrefixSetTest
: public PlatformTest
{
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
);
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.
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
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
);
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
);
55 // Sort for use with PrefixSet constructor.
56 std::sort(shared_prefixes_
.begin(), shared_prefixes_
.end());
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()));
72 for (size_t i
= 0; i
< prefixes
.size(); ++i
) {
73 EXPECT_TRUE(prefix_set
.Exists(prefixes
[i
]));
75 const SBPrefix left_sibling
= prefixes
[i
] - 1;
76 if (check
.count(left_sibling
) == 0)
77 EXPECT_FALSE(prefix_set
.Exists(left_sibling
));
79 const SBPrefix right_sibling
= prefixes
[i
] + 1;
80 if (check
.count(right_sibling
) == 0)
81 EXPECT_FALSE(prefix_set
.Exists(right_sibling
));
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())
92 base::FilePath filename
= temp_dir_
.path().AppendASCII("PrefixSetTest");
94 safe_browsing::PrefixSet
prefix_set(shared_prefixes_
);
95 if (!prefix_set
.WriteFile(filename
))
98 *filenamep
= filename
;
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
105 static void IncrementIntAt(FILE* fp
, long offset
, int inc
) {
108 ASSERT_NE(-1, fseek(fp
, offset
, SEEK_SET
));
109 ASSERT_EQ(1U, fread(&value
, sizeof(value
), 1, fp
));
113 ASSERT_NE(-1, fseek(fp
, offset
, SEEK_SET
));
114 ASSERT_EQ(1U, fwrite(&value
, sizeof(value
), 1, fp
));
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
);
123 ASSERT_NE(-1, fseek(fp
, 0, SEEK_END
));
124 long file_size
= ftell(fp
);
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
) {
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
;
137 ASSERT_EQ(digested_size
, payload_size
);
138 ASSERT_EQ(static_cast<long>(digested_size
), ftell(fp
));
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
));
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
,
152 ASSERT_TRUE(base::GetFileSize(filename
, &size_64
));
154 file_util::ScopedFILE
file(base::OpenFile(filename
, "r+b"));
155 IncrementIntAt(file
.get(), offset
, inc
);
156 CleanChecksum(file
.get());
160 ASSERT_TRUE(base::GetFileSize(filename
, &new_size_64
));
161 ASSERT_EQ(new_size_64
, size_64
);
164 // Tests should not modify this shared resource.
165 static std::vector
<SBPrefix
> shared_prefixes_
;
167 base::ScopedTempDir temp_dir_
;
170 std::vector
<SBPrefix
> PrefixSetTest::shared_prefixes_
;
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_
);
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
]));
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));
195 // Check that |GetPrefixes()| returns the same set of prefixes as
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]);
203 // Edges of the 32-bit integer range.
204 TEST_F(PrefixSetTest
, IntMinMax
) {
205 std::vector
<SBPrefix
> prefixes
;
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);
218 std::sort(prefixes
.begin(), prefixes
.end());
219 safe_browsing::PrefixSet
prefix_set(prefixes
);
221 // Check that |GetPrefixes()| returns the same set of prefixes as
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()));
230 // A range with only large deltas.
231 TEST_F(PrefixSetTest
, AllBig
) {
232 std::vector
<SBPrefix
> prefixes
;
234 const SBPrefix kVeryPositive
= 1000 * 1000 * 1000;
235 const SBPrefix kVeryNegative
= -kVeryPositive
;
236 const unsigned kDelta
= 10 * 1000 * 1000;
238 for (SBPrefix prefix
= kVeryNegative
;
239 prefix
< kVeryPositive
; prefix
+= kDelta
) {
240 prefixes
.push_back(prefix
);
243 std::sort(prefixes
.begin(), prefixes
.end());
244 safe_browsing::PrefixSet
prefix_set(prefixes
);
246 // Check that |GetPrefixes()| returns the same set of prefixes as
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()));
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
262 TEST_F(PrefixSetTest
, EdgeCases
) {
263 std::vector
<SBPrefix
> prefixes
;
265 const SBPrefix kVeryPositive
= 1000 * 1000 * 1000;
266 const SBPrefix kVeryNegative
= -kVeryPositive
;
268 // Put in a very negative prefix.
269 SBPrefix prefix
= kVeryNegative
;
270 prefixes
.push_back(prefix
);
272 // Add a sequence with very large deltas.
273 unsigned delta
= 100 * 1000 * 1000;
274 for (int i
= 0; i
< 10; ++i
) {
276 prefixes
.push_back(prefix
);
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
) {
284 prefixes
.push_back(prefix
);
285 prefixes
.push_back(prefix
);
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
) {
296 prefixes
.push_back(prefix
);
300 std::sort(prefixes
.begin(), prefixes
.end());
301 safe_browsing::PrefixSet
prefix_set(prefixes
);
303 // Check that |GetPrefixes()| returns the same set of prefixes as
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()));
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));
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
319 for (size_t i
= 0; i
< prefixes
.size(); ++i
) {
320 EXPECT_TRUE(prefix_set
.Exists(prefixes
[i
]));
322 EXPECT_FALSE(prefix_set
.Exists(prefixes
[i
] - 1));
323 EXPECT_FALSE(prefix_set
.Exists(prefixes
[i
] + 1));
327 // Test writing a prefix set to disk and reading it back in.
328 TEST_F(PrefixSetTest
, ReadWrite
) {
329 base::FilePath filename
;
331 // Write the sample prefix set out, read it back in, and check all
332 // the prefixes. Leaves the path in |filename|.
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_
);
341 // Test writing and reading a very sparse set containing no deltas.
343 const SBPrefix kVeryPositive
= 1000 * 1000 * 1000;
344 const SBPrefix kVeryNegative
= -kVeryPositive
;
346 std::vector
<SBPrefix
> prefixes
;
347 prefixes
.push_back(kVeryNegative
);
348 prefixes
.push_back(kVeryPositive
);
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
);
358 // Test writing and reading an empty set.
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
);
370 // Check that |CleanChecksum()| makes an acceptable checksum.
371 TEST_F(PrefixSetTest
, CorruptionHelpers
) {
372 base::FilePath filename
;
373 ASSERT_TRUE(GetPrefixSetFile(&filename
));
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);
379 scoped_ptr
<safe_browsing::PrefixSet
>
380 prefix_set(safe_browsing::PrefixSet::LoadFile(filename
));
381 ASSERT_FALSE(prefix_set
.get());
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());
388 prefix_set
.reset(safe_browsing::PrefixSet::LoadFile(filename
));
389 ASSERT_TRUE(prefix_set
.get());
392 // Bad |index_| size is caught by the sanity check.
393 TEST_F(PrefixSetTest
, CorruptionMagic
) {
394 base::FilePath filename
;
395 ASSERT_TRUE(GetPrefixSetFile(&filename
));
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());
404 // Bad |index_| size is caught by the sanity check.
405 TEST_F(PrefixSetTest
, CorruptionVersion
) {
406 base::FilePath filename
;
407 ASSERT_TRUE(GetPrefixSetFile(&filename
));
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());
416 // Bad |index_| size is caught by the sanity check.
417 TEST_F(PrefixSetTest
, CorruptionIndexSize
) {
418 base::FilePath filename
;
419 ASSERT_TRUE(GetPrefixSetFile(&filename
));
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());
428 // Bad |deltas_| size is caught by the sanity check.
429 TEST_F(PrefixSetTest
, CorruptionDeltasSize
) {
430 base::FilePath filename
;
431 ASSERT_TRUE(GetPrefixSetFile(&filename
));
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());
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
));
446 file_util::ScopedFILE
file(base::OpenFile(filename
, "r+b"));
447 ASSERT_NO_FATAL_FAILURE(IncrementIntAt(file
.get(), 666, 1));
449 scoped_ptr
<safe_browsing::PrefixSet
>
450 prefix_set(safe_browsing::PrefixSet::LoadFile(filename
));
451 ASSERT_FALSE(prefix_set
.get());
454 // Test corruption in the digest itself.
455 TEST_F(PrefixSetTest
, CorruptionDigest
) {
456 base::FilePath filename
;
457 ASSERT_TRUE(GetPrefixSetFile(&filename
));
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));
465 scoped_ptr
<safe_browsing::PrefixSet
>
466 prefix_set(safe_browsing::PrefixSet::LoadFile(filename
));
467 ASSERT_FALSE(prefix_set
.get());
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
));
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()));
480 scoped_ptr
<safe_browsing::PrefixSet
>
481 prefix_set(safe_browsing::PrefixSet::LoadFile(filename
));
482 ASSERT_FALSE(prefix_set
.get());