1 // Copyright 2019 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 "content/browser/indexed_db/indexed_db_origin_state.h"
6
7 #include <list>
8 #include <utility>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/feature_list.h"
14 #include "base/rand_util.h"
15 #include "base/stl_util.h"
16 #include "base/synchronization/waitable_event.h"
17 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.h"
18 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_factory.h"
19 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction.h"
20 #include "content/browser/indexed_db/indexed_db_active_blob_registry.h"
21 #include "content/browser/indexed_db/indexed_db_backing_store.h"
22 #include "content/browser/indexed_db/indexed_db_class_factory.h"
23 #include "content/browser/indexed_db/indexed_db_compaction_task.h"
24 #include "content/browser/indexed_db/indexed_db_connection.h"
25 #include "content/browser/indexed_db/indexed_db_database.h"
26 #include "content/browser/indexed_db/indexed_db_factory_impl.h"
27 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
28 #include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
29 #include "content/browser/indexed_db/indexed_db_pre_close_task_queue.h"
30 #include "content/browser/indexed_db/indexed_db_tombstone_sweeper.h"
31 #include "content/browser/indexed_db/indexed_db_transaction.h"
32 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
33
34 namespace content {
35 namespace {
36 // Time after the last connection to a database is closed and when we destroy
37 // the backing store.
38 const int64_t kBackingStoreGracePeriodSeconds = 2;
39 // Total time we let pre-close tasks run.
40 const int64_t kRunningPreCloseTasksMaxRunPeriodSeconds = 60;
41 // The number of iterations for every 'round' of the tombstone sweeper.
42 const int kTombstoneSweeperRoundIterations = 1000;
43 // The maximum total iterations for the tombstone sweeper.
44 const int kTombstoneSweeperMaxIterations = 10 * 1000 * 1000;
45
46 constexpr const base::TimeDelta kMinEarliestOriginSweepFromNow =
47 base::TimeDelta::FromDays(1);
48 static_assert(kMinEarliestOriginSweepFromNow <
49 IndexedDBOriginState::kMaxEarliestOriginSweepFromNow,
50 "Min < Max");
51
52 constexpr const base::TimeDelta kMinEarliestGlobalSweepFromNow =
53 base::TimeDelta::FromMinutes(5);
54 static_assert(kMinEarliestGlobalSweepFromNow <
55 IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow,
56 "Min < Max");
57
GenerateNextOriginSweepTime(base::Time now)58 base::Time GenerateNextOriginSweepTime(base::Time now) {
59 uint64_t range =
60 IndexedDBOriginState::kMaxEarliestOriginSweepFromNow.InMilliseconds() -
61 kMinEarliestOriginSweepFromNow.InMilliseconds();
62 int64_t rand_millis = kMinEarliestOriginSweepFromNow.InMilliseconds() +
63 static_cast<int64_t>(base::RandGenerator(range));
64 return now + base::TimeDelta::FromMilliseconds(rand_millis);
65 }
66
GenerateNextGlobalSweepTime(base::Time now)67 base::Time GenerateNextGlobalSweepTime(base::Time now) {
68 uint64_t range =
69 IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow.InMilliseconds() -
70 kMinEarliestGlobalSweepFromNow.InMilliseconds();
71 int64_t rand_millis = kMinEarliestGlobalSweepFromNow.InMilliseconds() +
72 static_cast<int64_t>(base::RandGenerator(range));
73 return now + base::TimeDelta::FromMilliseconds(rand_millis);
74 }
75
76 } // namespace
77
78 const base::Feature kCompactIDBOnClose{"CompactIndexedDBOnClose",
79 base::FEATURE_ENABLED_BY_DEFAULT};
80
81 constexpr const base::TimeDelta
82 IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow;
83 constexpr const base::TimeDelta
84 IndexedDBOriginState::kMaxEarliestOriginSweepFromNow;
85
IndexedDBOriginState(url::Origin origin,bool persist_for_incognito,base::Clock * clock,TransactionalLevelDBFactory * transactional_leveldb_factory,base::Time * earliest_global_sweep_time,std::unique_ptr<DisjointRangeLockManager> lock_manager,TasksAvailableCallback notify_tasks_callback,TearDownCallback tear_down_callback,std::unique_ptr<IndexedDBBackingStore> backing_store)86 IndexedDBOriginState::IndexedDBOriginState(
87 url::Origin origin,
88 bool persist_for_incognito,
89 base::Clock* clock,
90 TransactionalLevelDBFactory* transactional_leveldb_factory,
91 base::Time* earliest_global_sweep_time,
92 std::unique_ptr<DisjointRangeLockManager> lock_manager,
93 TasksAvailableCallback notify_tasks_callback,
94 TearDownCallback tear_down_callback,
95 std::unique_ptr<IndexedDBBackingStore> backing_store)
96 : origin_(std::move(origin)),
97 persist_for_incognito_(persist_for_incognito),
98 clock_(clock),
99 transactional_leveldb_factory_(transactional_leveldb_factory),
100 earliest_global_sweep_time_(earliest_global_sweep_time),
101 lock_manager_(std::move(lock_manager)),
102 backing_store_(std::move(backing_store)),
103 notify_tasks_callback_(std::move(notify_tasks_callback)),
104 tear_down_callback_(std::move(tear_down_callback)) {
105 DCHECK(clock_);
106 DCHECK(earliest_global_sweep_time_);
107 if (*earliest_global_sweep_time_ == base::Time())
108 *earliest_global_sweep_time_ = GenerateNextGlobalSweepTime(clock_->Now());
109 }
110
~IndexedDBOriginState()111 IndexedDBOriginState::~IndexedDBOriginState() {
112 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
113 if (!backing_store_)
114 return;
115 if (backing_store_->IsBlobCleanupPending())
116 backing_store_->ForceRunBlobCleanup();
117
118 base::WaitableEvent leveldb_destruct_event;
119 backing_store_->db()->leveldb_state()->RequestDestruction(
120 &leveldb_destruct_event);
121 backing_store_.reset();
122 leveldb_destruct_event.Wait();
123 }
124
AbortAllTransactions(bool compact)125 void IndexedDBOriginState::AbortAllTransactions(bool compact) {
126 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
127
128 // Because finishing all transactions could cause a database to be destructed
129 // (which would mutate the database_ map), save the keys beforehand and use
130 // those.
131 std::vector<base::string16> origins;
132 origins.reserve(databases_.size());
133 for (const auto& pair : databases_) {
134 origins.push_back(pair.first);
135 }
136
137 base::WeakPtr<IndexedDBOriginState> weak_ptr = AsWeakPtr();
138 for (const base::string16& origin : origins) {
139 auto it = databases_.find(origin);
140 if (it == databases_.end())
141 continue;
142
143 // Calling FinishAllTransactions can destruct the IndexedDBConnection &
144 // modify the IndexedDBDatabase::connection() list. To prevent UAFs, start
145 // by taking a WeakPtr of all connections, and then iterate that list.
146 std::vector<base::WeakPtr<IndexedDBConnection>> weak_connections;
147 weak_connections.reserve(it->second->connections().size());
148 for (IndexedDBConnection* connection : it->second->connections())
149 weak_connections.push_back(connection->GetWeakPtr());
150
151 for (base::WeakPtr<IndexedDBConnection> connection : weak_connections) {
152 if (connection) {
153 leveldb::Status status =
154 connection->AbortAllTransactions(IndexedDBDatabaseError(
155 blink::mojom::IDBException::kUnknownError,
156 "Aborting all transactions for the origin."));
157 if (!status.ok()) {
158 // This call should delete this object.
159 tear_down_callback().Run(status);
160 return;
161 }
162 }
163 }
164 }
165
166 if (compact)
167 backing_store_->Compact();
168 }
169
ForceClose()170 void IndexedDBOriginState::ForceClose() {
171 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
172 // To avoid re-entry, the |db_destruction_weak_factory_| is invalidated so
173 // that of the deletions closures returned by CreateDatabaseDeleteClosure will
174 // no-op. This allows force closing all of the databases without having the
175 // map mutate. Afterwards the map is manually deleted.
176 IndexedDBOriginStateHandle handle = CreateHandle();
177 for (const auto& pair : databases_) {
178 // Note: We purposefully ignore the result here as force close needs to
179 // continue tearing things down anyways.
180 pair.second->ForceCloseAndRunTasks();
181 }
182 databases_.clear();
183 if (has_blobs_outstanding_) {
184 backing_store_->active_blob_registry()->ForceShutdown();
185 has_blobs_outstanding_ = false;
186 }
187 skip_closing_sequence_ = true;
188 }
189
ReportOutstandingBlobs(bool blobs_outstanding)190 void IndexedDBOriginState::ReportOutstandingBlobs(bool blobs_outstanding) {
191 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
192 has_blobs_outstanding_ = blobs_outstanding;
193 MaybeStartClosing();
194 }
195
StopPersistingForIncognito()196 void IndexedDBOriginState::StopPersistingForIncognito() {
197 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
198 persist_for_incognito_ = false;
199 MaybeStartClosing();
200 }
201
202 std::tuple<IndexedDBOriginState::RunTasksResult, leveldb::Status>
RunTasks()203 IndexedDBOriginState::RunTasks() {
204 task_run_scheduled_ = false;
205 running_tasks_ = true;
206 leveldb::Status status;
207 for (auto db_it = databases_.begin(); db_it != databases_.end();) {
208 IndexedDBDatabase& db = *db_it->second;
209
210 IndexedDBDatabase::RunTasksResult tasks_result;
211 std::tie(tasks_result, status) = db.RunTasks();
212 switch (tasks_result) {
213 case IndexedDBDatabase::RunTasksResult::kDone:
214 ++db_it;
215 continue;
216 case IndexedDBDatabase::RunTasksResult::kError:
217 running_tasks_ = false;
218 return std::make_tuple(RunTasksResult::kError, status);
219 case IndexedDBDatabase::RunTasksResult::kCanBeDestroyed:
220 db_it = databases_.erase(db_it);
221 break;
222 }
223 }
224 running_tasks_ = false;
225 if (CanCloseFactory() && closing_stage_ == ClosingState::kClosed)
226 return std::make_tuple(RunTasksResult::kCanBeDestroyed, leveldb::Status::OK());
227 return std::make_tuple(RunTasksResult::kDone, leveldb::Status::OK());
228 }
229
AddDatabase(const base::string16 & name,std::unique_ptr<IndexedDBDatabase> database)230 IndexedDBDatabase* IndexedDBOriginState::AddDatabase(
231 const base::string16& name,
232 std::unique_ptr<IndexedDBDatabase> database) {
233 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
234 DCHECK(!base::Contains(databases_, name));
235 return databases_.emplace(name, std::move(database)).first->second.get();
236 }
237
CreateHandle()238 IndexedDBOriginStateHandle IndexedDBOriginState::CreateHandle() {
239 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
240 ++open_handles_;
241 if (closing_stage_ != ClosingState::kNotClosing) {
242 closing_stage_ = ClosingState::kNotClosing;
243 close_timer_.AbandonAndStop();
244 if (pre_close_task_queue_) {
245 pre_close_task_queue_->StopForNewConnection();
246 pre_close_task_queue_.reset();
247 }
248 }
249 return IndexedDBOriginStateHandle(weak_factory_.GetWeakPtr());
250 }
251
OnHandleDestruction()252 void IndexedDBOriginState::OnHandleDestruction() {
253 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
254 DCHECK_GT(open_handles_, 0ll);
255 --open_handles_;
256 MaybeStartClosing();
257 }
258
CanCloseFactory()259 bool IndexedDBOriginState::CanCloseFactory() {
260 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
261 DCHECK_GE(open_handles_, 0);
262 return !has_blobs_outstanding_ && open_handles_ <= 0 &&
263 !persist_for_incognito_;
264 }
265
MaybeStartClosing()266 void IndexedDBOriginState::MaybeStartClosing() {
267 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
268 if (!IsClosing() && CanCloseFactory())
269 StartClosing();
270 }
271
StartClosing()272 void IndexedDBOriginState::StartClosing() {
273 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
274 DCHECK(CanCloseFactory());
275 DCHECK(!IsClosing());
276
277 if (skip_closing_sequence_ ||
278 base::CommandLine::ForCurrentProcess()->HasSwitch(
279 kIDBCloseImmediatelySwitch)) {
280 closing_stage_ = ClosingState::kClosed;
281 close_timer_.AbandonAndStop();
282 pre_close_task_queue_.reset();
283 notify_tasks_callback_.Run();
284 return;
285 }
286
287 // Start a timer to close the backing store, unless something else opens it
288 // in the mean time.
289 DCHECK(!close_timer_.IsRunning());
290 closing_stage_ = ClosingState::kPreCloseGracePeriod;
291 close_timer_.Start(
292 FROM_HERE, base::TimeDelta::FromSeconds(kBackingStoreGracePeriodSeconds),
293 base::BindOnce(
294 [](base::WeakPtr<IndexedDBOriginState> factory) {
295 if (!factory ||
296 factory->closing_stage_ != ClosingState::kPreCloseGracePeriod)
297 return;
298 factory->StartPreCloseTasks();
299 },
300 weak_factory_.GetWeakPtr()));
301 }
302
StartPreCloseTasks()303 void IndexedDBOriginState::StartPreCloseTasks() {
304 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
305 DCHECK(closing_stage_ == ClosingState::kPreCloseGracePeriod);
306 closing_stage_ = ClosingState::kRunningPreCloseTasks;
307
308 // The callback will run on all early returns in this function.
309 base::ScopedClosureRunner maybe_close_backing_store_runner(base::BindOnce(
310 [](base::WeakPtr<IndexedDBOriginState> factory) {
311 if (!factory ||
312 factory->closing_stage_ != ClosingState::kRunningPreCloseTasks)
313 return;
314 factory->closing_stage_ = ClosingState::kClosed;
315 factory->pre_close_task_queue_.reset();
316 factory->close_timer_.AbandonAndStop();
317 factory->notify_tasks_callback_.Run();
318 },
319 weak_factory_.GetWeakPtr()));
320
321 std::list<std::unique_ptr<IndexedDBPreCloseTaskQueue::PreCloseTask>> tasks;
322
323 if (ShouldRunTombstoneSweeper()) {
324 tasks.push_back(std::make_unique<IndexedDBTombstoneSweeper>(
325 kTombstoneSweeperRoundIterations, kTombstoneSweeperMaxIterations,
326 backing_store_->db()->db()));
327 }
328
329 if (ShouldRunCompaction()) {
330 tasks.push_back(
331 std::make_unique<IndexedDBCompactionTask>(backing_store_->db()->db()));
332 }
333
334 if (!tasks.empty()) {
335 pre_close_task_queue_ = std::make_unique<IndexedDBPreCloseTaskQueue>(
336 std::move(tasks), maybe_close_backing_store_runner.Release(),
337 base::TimeDelta::FromSeconds(kRunningPreCloseTasksMaxRunPeriodSeconds),
338 std::make_unique<base::OneShotTimer>());
339 pre_close_task_queue_->Start(
340 base::BindOnce(&IndexedDBBackingStore::GetCompleteMetadata,
341 base::Unretained(backing_store_.get())));
342 }
343 }
344
ShouldRunTombstoneSweeper()345 bool IndexedDBOriginState::ShouldRunTombstoneSweeper() {
346 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
347 base::Time now = clock_->Now();
348 // Check that the last sweep hasn't run too recently.
349 if (*earliest_global_sweep_time_ > now)
350 return false;
351
352 base::Time origin_earliest_sweep;
353 leveldb::Status s = indexed_db::GetEarliestSweepTime(backing_store_->db(),
354 &origin_earliest_sweep);
355 // TODO(dmurph): Log this or report to UMA.
356 if (!s.ok() && !s.IsNotFound())
357 return false;
358
359 // This origin hasn't been swept too recently.
360 if (origin_earliest_sweep > now)
361 return false;
362
363 // A sweep will happen now, so reset the sweep timers.
364 *earliest_global_sweep_time_ = GenerateNextGlobalSweepTime(now);
365 std::unique_ptr<LevelDBDirectTransaction> txn =
366 transactional_leveldb_factory_->CreateLevelDBDirectTransaction(
367 backing_store_->db());
368 s = indexed_db::SetEarliestSweepTime(txn.get(),
369 GenerateNextOriginSweepTime(now));
370 // TODO(dmurph): Log this or report to UMA.
371 if (!s.ok())
372 return false;
373 s = txn->Commit();
374
375 // TODO(dmurph): Log this or report to UMA.
376 if (!s.ok())
377 return false;
378 return true;
379 }
380
ShouldRunCompaction()381 bool IndexedDBOriginState::ShouldRunCompaction() {
382 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
383 return base::FeatureList::IsEnabled(kCompactIDBOnClose);
384 }
385
386 } // namespace content
387