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