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/safe_browsing_store_file.h"
6
7#include "base/bind.h"
8#include "base/files/scoped_temp_dir.h"
9#include "base/md5.h"
10#include "chrome/browser/safe_browsing/safe_browsing_store_unittest_helper.h"
11#include "testing/gtest/include/gtest/gtest.h"
12#include "testing/platform_test.h"
13
14namespace {
15
16const base::FilePath::CharType kFolderPrefix[] =
17    FILE_PATH_LITERAL("SafeBrowsingTestStoreFile");
18
19class SafeBrowsingStoreFileTest : public PlatformTest {
20 public:
21  virtual void SetUp() {
22    PlatformTest::SetUp();
23
24    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
25
26    filename_ = temp_dir_.path();
27    filename_ = filename_.AppendASCII("SafeBrowsingTestStore");
28
29    store_.reset(new SafeBrowsingStoreFile());
30    store_->Init(filename_,
31                 base::Bind(&SafeBrowsingStoreFileTest::OnCorruptionDetected,
32                            base::Unretained(this)));
33    corruption_detected_ = false;
34  }
35  virtual void TearDown() {
36    if (store_.get())
37      store_->Delete();
38    store_.reset();
39
40    PlatformTest::TearDown();
41  }
42
43  void OnCorruptionDetected() {
44    corruption_detected_ = true;
45  }
46
47  base::ScopedTempDir temp_dir_;
48  base::FilePath filename_;
49  scoped_ptr<SafeBrowsingStoreFile> store_;
50  bool corruption_detected_;
51};
52
53TEST_STORE(SafeBrowsingStoreFileTest, store_.get(), filename_);
54
55// Test that Delete() deletes the temporary store, if present.
56TEST_F(SafeBrowsingStoreFileTest, DeleteTemp) {
57  const base::FilePath temp_file =
58      SafeBrowsingStoreFile::TemporaryFileForFilename(filename_);
59
60  EXPECT_FALSE(base::PathExists(filename_));
61  EXPECT_FALSE(base::PathExists(temp_file));
62
63  // Starting a transaction creates a temporary file.
64  EXPECT_TRUE(store_->BeginUpdate());
65  EXPECT_TRUE(base::PathExists(temp_file));
66
67  // Pull the rug out from under the existing store, simulating a
68  // crash.
69  store_.reset(new SafeBrowsingStoreFile());
70  store_->Init(filename_, base::Closure());
71  EXPECT_FALSE(base::PathExists(filename_));
72  EXPECT_TRUE(base::PathExists(temp_file));
73
74  // Make sure the temporary file is deleted.
75  EXPECT_TRUE(store_->Delete());
76  EXPECT_FALSE(base::PathExists(filename_));
77  EXPECT_FALSE(base::PathExists(temp_file));
78}
79
80// Test basic corruption-handling.
81TEST_F(SafeBrowsingStoreFileTest, DetectsCorruption) {
82  // Load a store with some data.
83  SafeBrowsingStoreTestStorePrefix(store_.get());
84
85  // Can successfully open and read the store.
86  std::vector<SBAddFullHash> pending_adds;
87  std::set<SBPrefix> prefix_misses;
88  SBAddPrefixes orig_prefixes;
89  std::vector<SBAddFullHash> orig_hashes;
90  EXPECT_TRUE(store_->BeginUpdate());
91  EXPECT_TRUE(store_->FinishUpdate(pending_adds, prefix_misses,
92                                   &orig_prefixes, &orig_hashes));
93  EXPECT_GT(orig_prefixes.size(), 0U);
94  EXPECT_GT(orig_hashes.size(), 0U);
95  EXPECT_FALSE(corruption_detected_);
96
97  // Corrupt the store.
98  file_util::ScopedFILE file(file_util::OpenFile(filename_, "rb+"));
99  const long kOffset = 60;
100  EXPECT_EQ(fseek(file.get(), kOffset, SEEK_SET), 0);
101  const int32 kZero = 0;
102  int32 previous = kZero;
103  EXPECT_EQ(fread(&previous, sizeof(previous), 1, file.get()), 1U);
104  EXPECT_NE(previous, kZero);
105  EXPECT_EQ(fseek(file.get(), kOffset, SEEK_SET), 0);
106  EXPECT_EQ(fwrite(&kZero, sizeof(kZero), 1, file.get()), 1U);
107  file.reset();
108
109  // Update fails and corruption callback is called.
110  SBAddPrefixes add_prefixes;
111  std::vector<SBAddFullHash> add_hashes;
112  corruption_detected_ = false;
113  EXPECT_TRUE(store_->BeginUpdate());
114  EXPECT_FALSE(store_->FinishUpdate(pending_adds, prefix_misses,
115                                    &add_prefixes, &add_hashes));
116  EXPECT_TRUE(corruption_detected_);
117  EXPECT_EQ(add_prefixes.size(), 0U);
118  EXPECT_EQ(add_hashes.size(), 0U);
119
120  // Make it look like there is a lot of add-chunks-seen data.
121  const long kAddChunkCountOffset = 2 * sizeof(int32);
122  const int32 kLargeCount = 1000 * 1000 * 1000;
123  file.reset(file_util::OpenFile(filename_, "rb+"));
124  EXPECT_EQ(fseek(file.get(), kAddChunkCountOffset, SEEK_SET), 0);
125  EXPECT_EQ(fwrite(&kLargeCount, sizeof(kLargeCount), 1, file.get()), 1U);
126  file.reset();
127
128  // Detects corruption and fails to even begin the update.
129  corruption_detected_ = false;
130  EXPECT_FALSE(store_->BeginUpdate());
131  EXPECT_TRUE(corruption_detected_);
132}
133
134TEST_F(SafeBrowsingStoreFileTest, CheckValidity) {
135  // Empty store is valid.
136  EXPECT_FALSE(base::PathExists(filename_));
137  ASSERT_TRUE(store_->BeginUpdate());
138  EXPECT_FALSE(corruption_detected_);
139  EXPECT_TRUE(store_->CheckValidity());
140  EXPECT_FALSE(corruption_detected_);
141  EXPECT_TRUE(store_->CancelUpdate());
142
143  // A store with some data is valid.
144  EXPECT_FALSE(base::PathExists(filename_));
145  SafeBrowsingStoreTestStorePrefix(store_.get());
146  EXPECT_TRUE(base::PathExists(filename_));
147  ASSERT_TRUE(store_->BeginUpdate());
148  EXPECT_FALSE(corruption_detected_);
149  EXPECT_TRUE(store_->CheckValidity());
150  EXPECT_FALSE(corruption_detected_);
151  EXPECT_TRUE(store_->CancelUpdate());
152}
153
154// Corrupt the payload.
155TEST_F(SafeBrowsingStoreFileTest, CheckValidityPayload) {
156  SafeBrowsingStoreTestStorePrefix(store_.get());
157  EXPECT_TRUE(base::PathExists(filename_));
158
159  // 37 is the most random prime number.  It's also past the header,
160  // as corrupting the header would fail BeginUpdate() in which case
161  // CheckValidity() cannot be called.
162  const size_t kOffset = 37;
163
164  {
165    file_util::ScopedFILE file(file_util::OpenFile(filename_, "rb+"));
166    EXPECT_EQ(0, fseek(file.get(), kOffset, SEEK_SET));
167    EXPECT_GE(fputs("hello", file.get()), 0);
168  }
169  ASSERT_TRUE(store_->BeginUpdate());
170  EXPECT_FALSE(corruption_detected_);
171  EXPECT_FALSE(store_->CheckValidity());
172  EXPECT_TRUE(corruption_detected_);
173  EXPECT_TRUE(store_->CancelUpdate());
174}
175
176// Corrupt the checksum.
177TEST_F(SafeBrowsingStoreFileTest, CheckValidityChecksum) {
178  SafeBrowsingStoreTestStorePrefix(store_.get());
179  EXPECT_TRUE(base::PathExists(filename_));
180
181  // An offset from the end of the file which is in the checksum.
182  const int kOffset = -static_cast<int>(sizeof(base::MD5Digest));
183
184  {
185    file_util::ScopedFILE file(file_util::OpenFile(filename_, "rb+"));
186    EXPECT_EQ(0, fseek(file.get(), kOffset, SEEK_END));
187    EXPECT_GE(fputs("hello", file.get()), 0);
188  }
189  ASSERT_TRUE(store_->BeginUpdate());
190  EXPECT_FALSE(corruption_detected_);
191  EXPECT_FALSE(store_->CheckValidity());
192  EXPECT_TRUE(corruption_detected_);
193  EXPECT_TRUE(store_->CancelUpdate());
194}
195
196}  // namespace
197