[13] [WebCore] IndexedDB hash invariant fix for -0/+0 keys
Rated High because the diff fixes a HashMap invariant violation in IDBKeyData that produces a UAF on IndexValueStore entries: add(Hasher&, ...) hashed the raw double by bit pattern while operator== used IEEE 754 equality, so -0.0 and +0.0 collided unequally and a later removal walked the wrong bucket and destroyed a live entry observed by an open cursor.
add(Hasher&, const IDBKeyData&) normalises -0.0 to +0.0 before hashing the Number/Date payload, restoring the a == b ⇒ hash(a) == hash(b) contract. MemoryIndex::transactionAborted now broadcasts cursor invalidation, and UniqueIDBDatabase::didFinishHandlingVersionChangeTransaction validates the transaction state before mutating m_versionChangeTransaction-related fields.
Source/WebCore/Modules/indexeddb/shared/IDBKeyData.cpp
HashMap invariant violation: bit-pattern hash plus IEEE 754 equality on IDBKeyData corrupted the in-memory index store, destroying a live record observed by open cursors.
Patch Details
The hashing path for Number/Date keys normalizes -0 to +0. MemoryIndex::transactionAborted invalidates open cursors before rolling back. The NetworkProcess UniqueIDBDatabase::didFinishHandlingVersionChangeTransaction re-checks transaction state before acting on it.
Background
IndexValueStore is a HashMap keyed on IDBKeyData. The in-memory backing store is the path used for ephemeral / private-browsing sessions; the IDB server runs in the NetworkProcess. The HashMap contract is that equal keys hash equally.
Analysis
Pre-fix, add(Hasher&, const IDBKeyData&) forwarded the raw double for Number/Date keys; the hasher hashed by bit pattern, so -0.0 (sign bit set) and +0.0 (sign bit clear) produced different hashes. IDBKeyData::operator== used IEEE 754 numeric equality, where -0.0 == +0.0.
Concrete corruption sequence:
insert {v: -0} → hash(-0) → bucket A, slot for -0
insert {v: +0} → unique-constraint check via operator== matches -0,
ConstraintError reported correctly
remove key +0 → hash(+0) → bucket B, walk chain comparing with ==,
matches and destroys the -0 entry in bucket A
The map is now structurally inconsistent: an entry observed by an open MemoryIndexCursor has been destroyed. Subsequent cursor reads dereference freed records. A secondary lifetime bug exists in MemoryIndex::transactionAborted: rollback destroyed/replaced index records while live cursors still observed them. A third defense-in-depth defect: didFinishHandlingVersionChangeTransaction mutated state without verifying the transaction was finishing.
This weakens the IndexedDB in-memory store invariants; the exploit primitive is heap UAF on MemoryIndexRecord reachable through a script-held cursor.
Audit directions
- Every
IDBKeyDatafield with a hash/equality mismatch. Number, Date, and any future floating-point or NaN-bearing field is at risk. - HashMap keys built from JS-visible primitives.
Map/Setbacking storage, Cache API, and other client-side stores keyed on JS values should be audited for the same hash-equals divergence. MemoryIndexcursor lifetime under transactionAborted, transactionFinished, deleteIndex. Verify every mutation path broadcasts cursor invalidation.