9f00fd0c8fd7b48eca3068d38a8dc1f9c823706b [indexeddb] Fix aborted connection issue
The bug at a glance
This fixes a liveness bug: an aborted upgrade left a dangling shared connection in the dedup cache so subsequent indexedDB.open() calls never resolved, a per-origin hang/DoS of IndexedDB rather than memory corruption, and it sits behind the kIndexedDBConnectionDeduplication feature flag.
A page could wedge its own IndexedDB (aborting a versionchange upgrade, closing, then reopening) so that later opens hang forever; the defender’s concern is a self-inflicted, flag-gated denial of service, with no shown cross-origin or memory-safety impact.
Root cause
With connection deduplication enabled, IDBOpenDBRequest created and registered a SharedIDBDatabaseConnection in the factory cache at the point the upgrade/connection was established (CreateAndRegisterSharedConnection, plus an eager OnRequestComplete). If OnUpgradeNeeded was followed by an abort, that connection was registered but never successfully opened, leaving a dangling/uncommitted shared connection in the cache; queued shared requests then attached to this dead connection and their opens never resolved. The fix splits creation from registration: it binds the connection immediately but defers factory RegisterSharedConnection until OnOpenDBSuccess, and adds destructor CHECKs that no shared_connection_target_ or shared_requests_ linger.
Key code
RegisterSharedConnection is now deferred to OnOpenDBSuccess instead of at connection creation
if (base::FeatureList::IsEnabled(
features::kIndexedDBConnectionDeduplication)) {
CHECK(shared_connection_target_);
RegisterSharedConnection(shared_connection_target_, metadata.name);
}
Patch walkthrough
third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc— Replaces CreateAndRegisterSharedConnection (which created the shared connection and immediately registered it in the factory cache) with a two-step flow: create + BindToConnection on upgrade-needed, and register the shared connection in the factory only on OnOpenDBSuccess; also removes the premature OnRequestComplete so an aborted upgrade no longer leaves a dangling cached connection that hangs later opens.third_party/blink/renderer/modules/indexeddb/idb_open_db_request.h— Changes the private API from CreateAndRegisterSharedConnection to RegisterSharedConnection(connection, name), reflecting that creation and cache registration are now separate phases.content/browser/indexed_db/indexed_db_browsertest.cc + content/test/data/indexeddb/aborted_upgrade_reopen.{html,js}— Adds a regression browser test with 5 concurrent callers that abort an upgrade, close, and reopen, asserting the opens complete instead of hanging.
Background
IndexedDB connection deduplication — A (flag-gated) optimization where multiple same-origin open requests for one database share a single SharedIDBDatabaseConnection cached in the factory.
versionchange / onupgradeneeded abort — An open with a higher version runs an upgrade transaction that the page may abort, which rolls back and should tear down the nascent connection.
SharedIDBDatabaseConnection — A garbage-collected wrapper that fronts the actual mojo IDBDatabase remote and is reused by queued shared open requests.
Vulnerability window
- Reachability — Reachable from ordinary web content only when the kIndexedDBConnectionDeduplication feature is enabled for the build/experiment.
- Trigger — A page opens a database without a version, aborts the upgrade in onupgradeneeded and closes, while concurrent callers queue as shared requests, then reopens with an explicit version.
- Primitive — The aborted-but-registered shared connection stays in the factory cache; queued and subsequent opens attach to the dead connection and their promises never resolve (hang/DoS).
- Escalation — No memory-corruption or cross-origin escalation is shown; impact is limited to the origin’s own IndexedDB becoming unusable (inferred).
Proof of concept
Conceptually: launch several concurrent indexedDB.open(name) probes, abort the versionchange transaction in onupgradeneeded and close the connection, then reopen with an explicit version; before the fix the reopen never resolves because a dangling shared connection remains cached. No exploit is provided.
Exploitation
- Trigger — Script the abort-and-reopen dance on a flag-enabled build to wedge the origin’s IndexedDB (inferred).
- Impact — Subsequent opens for that database hang, denying the origin its own IndexedDB until teardown (inferred).
Detection & hunting
For defenders and detection engineers:
- grep for RegisterSharedConnection / factory cache inserts that run before OnOpenDBSuccess — Flag dedup caches populated at connection-creation time rather than on confirmed success, which can cache failed/aborted connections.
- Destructor CHECK(!shared_connection_target_) / CHECK(shared_requests_.empty()) firing in crash reports — These added invariants surface lingering shared state and can be used to hunt residual leaks.
Audit directions
- third_party/blink/renderer/modules/indexeddb/ — Audit all paths that register a shared/cached connection to ensure registration only happens on success and abort/error paths fully unregister.
- Other connection-deduplication / shared-handle caches — Look for caches keyed by name that are populated before the underlying operation is confirmed, which can strand later waiters.