| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/indexed_db/indexed_db_origin_state.h" |
| |
| #include "base/bind.h" |
| #include "base/feature_list.h" |
| #include "base/rand_util.h" |
| #include "base/stl_util.h" |
| #include "content/browser/indexed_db/indexed_db_backing_store.h" |
| #include "content/browser/indexed_db/indexed_db_class_factory.h" |
| #include "content/browser/indexed_db/indexed_db_connection.h" |
| #include "content/browser/indexed_db/indexed_db_database.h" |
| #include "content/browser/indexed_db/indexed_db_factory_impl.h" |
| #include "content/browser/indexed_db/indexed_db_leveldb_coding.h" |
| #include "content/browser/indexed_db/indexed_db_leveldb_operations.h" |
| #include "content/browser/indexed_db/indexed_db_pre_close_task_queue.h" |
| #include "content/browser/indexed_db/indexed_db_tombstone_sweeper.h" |
| #include "content/browser/indexed_db/indexed_db_transaction.h" |
| #include "third_party/blink/public/platform/modules/indexeddb/web_idb_database_exception.h" |
| |
| namespace content { |
| namespace { |
| // Time after the last connection to a database is closed and when we destroy |
| // the backing store. |
| const int64_t kBackingStoreGracePeriodSeconds = 2; |
| // Total time we let pre-close tasks run. |
| const int64_t kRunningPreCloseTasksMaxRunPeriodSeconds = 60; |
| // The number of iterations for every 'round' of the tombstone sweeper. |
| const int kTombstoneSweeperRoundIterations = 1000; |
| // The maximum total iterations for the tombstone sweeper. |
| const int kTombstoneSweeperMaxIterations = 10 * 1000 * 1000; |
| |
| constexpr const base::TimeDelta kMinEarliestOriginSweepFromNow = |
| base::TimeDelta::FromDays(1); |
| static_assert(kMinEarliestOriginSweepFromNow < |
| IndexedDBOriginState::kMaxEarliestOriginSweepFromNow, |
| "Min < Max"); |
| |
| constexpr const base::TimeDelta kMinEarliestGlobalSweepFromNow = |
| base::TimeDelta::FromMinutes(10); |
| static_assert(kMinEarliestGlobalSweepFromNow < |
| IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow, |
| "Min < Max"); |
| |
| base::Time GenerateNextOriginSweepTime(base::Time now) { |
| uint64_t range = |
| IndexedDBOriginState::kMaxEarliestOriginSweepFromNow.InMilliseconds() - |
| kMinEarliestOriginSweepFromNow.InMilliseconds(); |
| int64_t rand_millis = kMinEarliestOriginSweepFromNow.InMilliseconds() + |
| static_cast<int64_t>(base::RandGenerator(range)); |
| return now + base::TimeDelta::FromMilliseconds(rand_millis); |
| } |
| |
| base::Time GenerateNextGlobalSweepTime(base::Time now) { |
| uint64_t range = |
| IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow.InMilliseconds() - |
| kMinEarliestGlobalSweepFromNow.InMilliseconds(); |
| int64_t rand_millis = kMinEarliestGlobalSweepFromNow.InMilliseconds() + |
| static_cast<int64_t>(base::RandGenerator(range)); |
| return now + base::TimeDelta::FromMilliseconds(rand_millis); |
| } |
| |
| } // namespace |
| |
| const base::Feature kIDBTombstoneStatistics{"IDBTombstoneStatistics", |
| base::FEATURE_DISABLED_BY_DEFAULT}; |
| |
| const base::Feature kIDBTombstoneDeletion{"IDBTombstoneDeletion", |
| base::FEATURE_ENABLED_BY_DEFAULT}; |
| |
| constexpr const base::TimeDelta |
| IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow; |
| constexpr const base::TimeDelta |
| IndexedDBOriginState::kMaxEarliestOriginSweepFromNow; |
| |
| IndexedDBOriginState::IndexedDBOriginState( |
| bool persist_for_incognito, |
| base::Clock* clock, |
| indexed_db::LevelDBFactory* leveldb_factory, |
| base::Time* earliest_global_sweep_time, |
| base::OnceClosure destruct_myself, |
| std::unique_ptr<IndexedDBBackingStore> backing_store) |
| : persist_for_incognito_(persist_for_incognito), |
| clock_(clock), |
| leveldb_factory_(leveldb_factory), |
| earliest_global_sweep_time_(earliest_global_sweep_time), |
| lock_manager_(kIndexedDBLockLevelCount), |
| backing_store_(std::move(backing_store)), |
| destruct_myself_(std::move(destruct_myself)) { |
| DCHECK(clock_); |
| DCHECK(earliest_global_sweep_time_); |
| if (*earliest_global_sweep_time_ == base::Time()) |
| *earliest_global_sweep_time_ = GenerateNextGlobalSweepTime(clock_->Now()); |
| } |
| |
| IndexedDBOriginState::~IndexedDBOriginState() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void IndexedDBOriginState::AbortAllTransactions(bool compact) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Because finishing all transactions could cause a database to be destructed |
| // (which would mutate the database_ map), save the keys beforehand and use |
| // those. |
| std::vector<base::string16> origins; |
| origins.reserve(databases_.size()); |
| for (const auto& pair : databases_) { |
| origins.push_back(pair.first); |
| } |
| |
| for (const base::string16& origin : origins) { |
| auto it = databases_.find(origin); |
| if (it == databases_.end()) |
| continue; |
| |
| // Calling FinishAllTransactions can destruct the IndexedDBConnection & |
| // modify the IndexedDBDatabase::connection() list. To prevent UAFs, start |
| // by taking a WeakPtr of all connections, and then iterate that list. |
| std::vector<base::WeakPtr<IndexedDBConnection>> weak_connections; |
| weak_connections.reserve(it->second->connections().size()); |
| for (IndexedDBConnection* connection : it->second->connections()) |
| weak_connections.push_back(connection->GetWeakPtr()); |
| |
| for (base::WeakPtr<IndexedDBConnection> connection : weak_connections) { |
| if (connection) { |
| connection->FinishAllTransactions(IndexedDBDatabaseError( |
| blink::kWebIDBDatabaseExceptionUnknownError, |
| "Aborting all transactions for the origin.")); |
| } |
| } |
| } |
| if (compact) |
| backing_store_->Compact(); |
| } |
| |
| void IndexedDBOriginState::ForceClose() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // To avoid re-entry, the |db_destruction_weak_factory_| is invalidated so |
| // that of the deletions closures returned by CreateDatabaseDeleteClosure will |
| // no-op. This allows force closing all of the databases without having the |
| // map mutate. Afterwards the map is manually deleted. |
| IndexedDBOriginStateHandle handle = CreateHandle(); |
| db_destruction_weak_factory_.InvalidateWeakPtrs(); |
| for (const auto& pair : databases_) { |
| pair.second->ForceClose(); |
| } |
| databases_.clear(); |
| if (has_blobs_outstanding_) { |
| backing_store_->active_blob_registry()->ForceShutdown(); |
| has_blobs_outstanding_ = false; |
| } |
| skip_closing_sequence_ = true; |
| } |
| |
| void IndexedDBOriginState::ReportOutstandingBlobs(bool blobs_outstanding) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| has_blobs_outstanding_ = blobs_outstanding; |
| MaybeStartClosing(); |
| } |
| |
| void IndexedDBOriginState::StopPersistingForIncognito() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| persist_for_incognito_ = false; |
| MaybeStartClosing(); |
| } |
| |
| IndexedDBDatabase* IndexedDBOriginState::AddDatabase( |
| const base::string16& name, |
| std::unique_ptr<IndexedDBDatabase> database) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!base::Contains(databases_, name)); |
| return databases_.emplace(name, std::move(database)).first->second.get(); |
| } |
| |
| base::OnceClosure IndexedDBOriginState::CreateDatabaseDeleteClosure( |
| const base::string16& name) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return base::BindOnce( |
| [](base::WeakPtr<IndexedDBOriginState> factory, |
| const base::string16& name) { |
| if (!factory) |
| return; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(factory->sequence_checker_); |
| size_t delete_size = factory->databases_.erase(name); |
| DCHECK(delete_size) << "Database " << name << " did not exist."; |
| }, |
| db_destruction_weak_factory_.GetWeakPtr(), name); |
| } |
| |
| IndexedDBOriginStateHandle IndexedDBOriginState::CreateHandle() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| ++open_handles_; |
| if (closing_stage_ != ClosingState::kNotClosing) { |
| closing_stage_ = ClosingState::kNotClosing; |
| close_timer_.AbandonAndStop(); |
| if (pre_close_task_queue_) { |
| pre_close_task_queue_->StopForNewConnection(); |
| pre_close_task_queue_.reset(); |
| } |
| } |
| return IndexedDBOriginStateHandle(weak_factory_.GetWeakPtr()); |
| } |
| |
| void IndexedDBOriginState::OnHandleDestruction() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_GT(open_handles_, 0ll); |
| --open_handles_; |
| MaybeStartClosing(); |
| } |
| |
| bool IndexedDBOriginState::CanCloseFactory() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_GE(open_handles_, 0); |
| return !has_blobs_outstanding_ && open_handles_ <= 0 && |
| !persist_for_incognito_; |
| } |
| |
| void IndexedDBOriginState::MaybeStartClosing() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!IsClosing() && CanCloseFactory()) |
| StartClosing(); |
| } |
| |
| void IndexedDBOriginState::StartClosing() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(CanCloseFactory()); |
| DCHECK(!IsClosing()); |
| |
| if (skip_closing_sequence_ || |
| base::CommandLine::ForCurrentProcess()->HasSwitch( |
| kIDBCloseImmediatelySwitch)) { |
| closing_stage_ = ClosingState::kClosed; |
| CloseAndDestruct(); |
| return; |
| } |
| |
| // Start a timer to close the backing store, unless something else opens it |
| // in the mean time. |
| DCHECK(!close_timer_.IsRunning()); |
| closing_stage_ = ClosingState::kPreCloseGracePeriod; |
| close_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(kBackingStoreGracePeriodSeconds), |
| base::BindOnce( |
| [](base::WeakPtr<IndexedDBOriginState> factory) { |
| if (!factory || |
| factory->closing_stage_ != ClosingState::kPreCloseGracePeriod) |
| return; |
| factory->StartPreCloseTasks(); |
| }, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void IndexedDBOriginState::StartPreCloseTasks() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(closing_stage_ == ClosingState::kPreCloseGracePeriod); |
| closing_stage_ = ClosingState::kRunningPreCloseTasks; |
| |
| // The callback will run on all early returns in this function. |
| base::ScopedClosureRunner maybe_close_backing_store_runner(base::BindOnce( |
| [](base::WeakPtr<IndexedDBOriginState> factory) { |
| if (!factory || |
| factory->closing_stage_ != ClosingState::kRunningPreCloseTasks) |
| return; |
| factory->closing_stage_ = ClosingState::kClosed; |
| factory->pre_close_task_queue_.reset(); |
| factory->CloseAndDestruct(); |
| }, |
| weak_factory_.GetWeakPtr())); |
| |
| base::Time now = clock_->Now(); |
| |
| // Check that the last sweep hasn't run too recently. |
| if (*earliest_global_sweep_time_ > now) |
| return; |
| |
| bool tombstone_stats_enabled = |
| base::FeatureList::IsEnabled(kIDBTombstoneStatistics); |
| bool tombstone_deletion_enabled = |
| base::FeatureList::IsEnabled(kIDBTombstoneDeletion); |
| |
| // After this check, exactly one of the flags must be true. |
| if (tombstone_stats_enabled == tombstone_deletion_enabled) |
| return; |
| |
| base::Time origin_earliest_sweep; |
| leveldb::Status s = indexed_db::GetEarliestSweepTime(backing_store_->db(), |
| &origin_earliest_sweep); |
| // TODO(dmurph): Log this or report to UMA. |
| if (!s.ok() && !s.IsNotFound()) |
| return; |
| |
| // This origin hasn't been swept too recently. |
| if (origin_earliest_sweep > now) |
| return; |
| |
| // A sweep will happen now, so reset the sweep timers. |
| *earliest_global_sweep_time_ = GenerateNextGlobalSweepTime(now); |
| scoped_refptr<LevelDBTransaction> txn = |
| leveldb_factory_->CreateLevelDBTransaction(backing_store_->db()); |
| indexed_db::SetEarliestSweepTime(txn.get(), GenerateNextOriginSweepTime(now)); |
| s = txn->Commit(); |
| |
| // TODO(dmurph): Log this or report to UMA. |
| if (!s.ok()) |
| return; |
| |
| std::list<std::unique_ptr<IndexedDBPreCloseTaskQueue::PreCloseTask>> tasks; |
| IndexedDBTombstoneSweeper::Mode mode = |
| tombstone_stats_enabled ? IndexedDBTombstoneSweeper::Mode::STATISTICS |
| : IndexedDBTombstoneSweeper::Mode::DELETION; |
| tasks.push_back(std::make_unique<IndexedDBTombstoneSweeper>( |
| mode, kTombstoneSweeperRoundIterations, kTombstoneSweeperMaxIterations, |
| backing_store_->db()->db())); |
| // TODO(dmurph): Add compaction task that compacts all indexes if we have |
| // more than X deletions. |
| |
| pre_close_task_queue_ = std::make_unique<IndexedDBPreCloseTaskQueue>( |
| std::move(tasks), maybe_close_backing_store_runner.Release(), |
| base::TimeDelta::FromSeconds(kRunningPreCloseTasksMaxRunPeriodSeconds), |
| std::make_unique<base::OneShotTimer>()); |
| pre_close_task_queue_->Start( |
| base::BindOnce(&IndexedDBBackingStore::GetCompleteMetadata, |
| base::Unretained(backing_store_.get()))); |
| } |
| |
| void IndexedDBOriginState::CloseAndDestruct() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(CanCloseFactory()); |
| DCHECK(closing_stage_ == ClosingState::kClosed); |
| close_timer_.AbandonAndStop(); |
| pre_close_task_queue_.reset(); |
| |
| if (backing_store_ && backing_store_->IsBlobCleanupPending()) |
| backing_store_->ForceRunBlobCleanup(); |
| |
| std::move(destruct_myself_).Run(); |
| } |
| |
| } // namespace content |