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
188 // Don't run the preclosing tasks after a ForceClose, whether or not we've
189 // started them. Compaction in particular can run long and cannot be
190 // interrupted, so it can cause shutdown hangs.
191 close_timer_.AbandonAndStop();
192 if (pre_close_task_queue_) {
193 pre_close_task_queue_->Stop(
194 IndexedDBPreCloseTaskQueue::StopReason::FORCE_CLOSE);
195 pre_close_task_queue_.reset();
196 }
197 skip_closing_sequence_ = true;
198 }
199
ReportOutstandingBlobs(bool blobs_outstanding)200 void IndexedDBOriginState::ReportOutstandingBlobs(bool blobs_outstanding) {
201 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
202 has_blobs_outstanding_ = blobs_outstanding;
203 MaybeStartClosing();
204 }
205
StopPersistingForIncognito()206 void IndexedDBOriginState::StopPersistingForIncognito() {
207 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
208 persist_for_incognito_ = false;
209 MaybeStartClosing();
210 }
211
212 std::tuple<IndexedDBOriginState::RunTasksResult, leveldb::Status>
RunTasks()213 IndexedDBOriginState::RunTasks() {
214 task_run_scheduled_ = false;
215 running_tasks_ = true;
216 leveldb::Status status;
217 for (auto db_it = databases_.begin(); db_it != databases_.end();) {
218 IndexedDBDatabase& db = *db_it->second;
219
220 IndexedDBDatabase::RunTasksResult tasks_result;
221 std::tie(tasks_result, status) = db.RunTasks();
222 switch (tasks_result) {
223 case IndexedDBDatabase::RunTasksResult::kDone:
224 ++db_it;
225 continue;
226 case IndexedDBDatabase::RunTasksResult::kError:
227 running_tasks_ = false;
228 return {RunTasksResult::kError, status};
229 case IndexedDBDatabase::RunTasksResult::kCanBeDestroyed:
230 db_it = databases_.erase(db_it);
231 break;
232 }
233 }
234 running_tasks_ = false;
235 if (CanCloseFactory() && closing_stage_ == ClosingState::kClosed)
236 return {RunTasksResult::kCanBeDestroyed, leveldb::Status::OK()};
237 return {RunTasksResult::kDone, leveldb::Status::OK()};
238 }
239
AddDatabase(const base::string16 & name,std::unique_ptr<IndexedDBDatabase> database)240 IndexedDBDatabase* IndexedDBOriginState::AddDatabase(
241 const base::string16& name,
242 std::unique_ptr<IndexedDBDatabase> database) {
243 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
244 DCHECK(!base::Contains(databases_, name));
245 return databases_.emplace(name, std::move(database)).first->second.get();
246 }
247
CreateHandle()248 IndexedDBOriginStateHandle IndexedDBOriginState::CreateHandle() {
249 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
250 ++open_handles_;
251 if (closing_stage_ != ClosingState::kNotClosing) {
252 closing_stage_ = ClosingState::kNotClosing;
253 close_timer_.AbandonAndStop();
254 if (pre_close_task_queue_) {
255 pre_close_task_queue_->Stop(
256 IndexedDBPreCloseTaskQueue::StopReason::NEW_CONNECTION);
257 pre_close_task_queue_.reset();
258 }
259 }
260 return IndexedDBOriginStateHandle(weak_factory_.GetWeakPtr());
261 }
262
OnHandleDestruction()263 void IndexedDBOriginState::OnHandleDestruction() {
264 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
265 DCHECK_GT(open_handles_, 0ll);
266 --open_handles_;
267 MaybeStartClosing();
268 }
269
CanCloseFactory()270 bool IndexedDBOriginState::CanCloseFactory() {
271 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
272 DCHECK_GE(open_handles_, 0);
273 return !has_blobs_outstanding_ && open_handles_ <= 0 &&
274 !persist_for_incognito_;
275 }
276
MaybeStartClosing()277 void IndexedDBOriginState::MaybeStartClosing() {
278 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
279 if (!IsClosing() && CanCloseFactory())
280 StartClosing();
281 }
282
StartClosing()283 void IndexedDBOriginState::StartClosing() {
284 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
285 DCHECK(CanCloseFactory());
286 DCHECK(!IsClosing());
287
288 if (skip_closing_sequence_ ||
289 base::CommandLine::ForCurrentProcess()->HasSwitch(
290 kIDBCloseImmediatelySwitch)) {
291 closing_stage_ = ClosingState::kClosed;
292 close_timer_.AbandonAndStop();
293 pre_close_task_queue_.reset();
294 notify_tasks_callback_.Run();
295 return;
296 }
297
298 // Start a timer to close the backing store, unless something else opens it
299 // in the mean time.
300 DCHECK(!close_timer_.IsRunning());
301 closing_stage_ = ClosingState::kPreCloseGracePeriod;
302 close_timer_.Start(
303 FROM_HERE, base::TimeDelta::FromSeconds(kBackingStoreGracePeriodSeconds),
304 base::BindOnce(
305 [](base::WeakPtr<IndexedDBOriginState> factory) {
306 if (!factory ||
307 factory->closing_stage_ != ClosingState::kPreCloseGracePeriod)
308 return;
309 factory->StartPreCloseTasks();
310 },
311 weak_factory_.GetWeakPtr()));
312 }
313
StartPreCloseTasks()314 void IndexedDBOriginState::StartPreCloseTasks() {
315 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
316 DCHECK(closing_stage_ == ClosingState::kPreCloseGracePeriod);
317 closing_stage_ = ClosingState::kRunningPreCloseTasks;
318
319 // The callback will run on all early returns in this function.
320 base::ScopedClosureRunner maybe_close_backing_store_runner(base::BindOnce(
321 [](base::WeakPtr<IndexedDBOriginState> factory) {
322 if (!factory ||
323 factory->closing_stage_ != ClosingState::kRunningPreCloseTasks)
324 return;
325 factory->closing_stage_ = ClosingState::kClosed;
326 factory->pre_close_task_queue_.reset();
327 factory->close_timer_.AbandonAndStop();
328 factory->notify_tasks_callback_.Run();
329 },
330 weak_factory_.GetWeakPtr()));
331
332 std::list<std::unique_ptr<IndexedDBPreCloseTaskQueue::PreCloseTask>> tasks;
333
334 if (ShouldRunTombstoneSweeper()) {
335 tasks.push_back(std::make_unique<IndexedDBTombstoneSweeper>(
336 kTombstoneSweeperRoundIterations, kTombstoneSweeperMaxIterations,
337 backing_store_->db()->db()));
338 }
339
340 if (ShouldRunCompaction()) {
341 tasks.push_back(
342 std::make_unique<IndexedDBCompactionTask>(backing_store_->db()->db()));
343 }
344
345 if (!tasks.empty()) {
346 pre_close_task_queue_ = std::make_unique<IndexedDBPreCloseTaskQueue>(
347 std::move(tasks), maybe_close_backing_store_runner.Release(),
348 base::TimeDelta::FromSeconds(kRunningPreCloseTasksMaxRunPeriodSeconds),
349 std::make_unique<base::OneShotTimer>());
350 pre_close_task_queue_->Start(
351 base::BindOnce(&IndexedDBBackingStore::GetCompleteMetadata,
352 base::Unretained(backing_store_.get())));
353 }
354 }
355
ShouldRunTombstoneSweeper()356 bool IndexedDBOriginState::ShouldRunTombstoneSweeper() {
357 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
358 base::Time now = clock_->Now();
359 // Check that the last sweep hasn't run too recently.
360 if (*earliest_global_sweep_time_ > now)
361 return false;
362
363 base::Time origin_earliest_sweep;
364 leveldb::Status s = indexed_db::GetEarliestSweepTime(backing_store_->db(),
365 &origin_earliest_sweep);
366 // TODO(dmurph): Log this or report to UMA.
367 if (!s.ok() && !s.IsNotFound())
368 return false;
369
370 // This origin hasn't been swept too recently.
371 if (origin_earliest_sweep > now)
372 return false;
373
374 // A sweep will happen now, so reset the sweep timers.
375 *earliest_global_sweep_time_ = GenerateNextGlobalSweepTime(now);
376 std::unique_ptr<LevelDBDirectTransaction> txn =
377 transactional_leveldb_factory_->CreateLevelDBDirectTransaction(
378 backing_store_->db());
379 s = indexed_db::SetEarliestSweepTime(txn.get(),
380 GenerateNextOriginSweepTime(now));
381 // TODO(dmurph): Log this or report to UMA.
382 if (!s.ok())
383 return false;
384 s = txn->Commit();
385
386 // TODO(dmurph): Log this or report to UMA.
387 if (!s.ok())
388 return false;
389 return true;
390 }
391
ShouldRunCompaction()392 bool IndexedDBOriginState::ShouldRunCompaction() {
393 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
394 return base::FeatureList::IsEnabled(kCompactIDBOnClose);
395 }
396
397 } // namespace content
398