[13] [WebCore] IndexedDB hash invariant fix for -0/+0 keys
IDBKeyData의 HashMap invariant 위반을 수정한 commit입니다. add(Hasher&, ...)는 raw double을 bit pattern 기준으로 해시하는 반면, operator==는 IEEE 754 동등성을 사용했습니다. 이 불일치로 인해 -0.0과 +0.0은 서로 다른 해시 버킷에 배치되며, 이후 삭제 과정에서 잘못된 버킷을 탐색하게 됩니다. 결과적으로 open cursor가 참조 중인 live entry가 파괴되는 UAF가 IndexValueStore 항목에서 발생합니다. 이런 이유로 Severity를 High로 평가합니다.
add(Hasher&, const IDBKeyData&)에서 Number/Date payload 해시 전에 -0.0을 +0.0으로 정규화하는 처리가 추가되어 a == b ⇒ hash(a) == hash(b) 계약이 복원됩니다. MemoryIndex::transactionAborted는 rollback 전에 open cursor 전체에 무효화를 전파하며, UniqueIDBDatabase::didFinishHandlingVersionChangeTransaction은 관련 필드를 변경하기 전에 transaction 상태를 먼저 검증합니다.
Source/WebCore/Modules/indexeddb/shared/IDBKeyData.cpp
HashMap invariant 위반: IDBKeyData에 대한 bit-pattern 해시와 IEEE 754 동등성의 불일치로 in-memory index store가 손상되었으며, open cursor가 참조 중인 live record가 파괴되었습니다.
Patch Details
Number/Date 키의 해시 경로에서 -0을 +0으로 정규화합니다. MemoryIndex::transactionAborted는 rollback 전에 open cursor를 무효화하며, NetworkProcess의 UniqueIDBDatabase::didFinishHandlingVersionChangeTransaction은 상태 변경 전에 transaction 상태를 재검증합니다.
Background
IndexValueStore는 IDBKeyData를 키로 사용하는 HashMap입니다. 이 in-memory backing store는 ephemeral 세션 및 private-browsing 세션에서 사용되는 경로로, IDB 서버는 NetworkProcess에서 실행됩니다. HashMap의 기본 계약은 동일한 키는 반드시 동일하게 해시되어야 한다는 것입니다.
Analysis
패치 이전에는 Number/Date 키에 대해 add(Hasher&, const IDBKeyData&)가 raw double 값을 그대로 전달했습니다. 해시 함수는 bit pattern 기준으로 처리하므로, -0.0(sign bit가 설정된 상태)과 +0.0(sign bit가 없는 상태)은 서로 다른 해시 값을 생성했습니다. 반면 IDBKeyData::operator==는 IEEE 754 수치 동등성을 사용하며, 이 기준에서는 -0.0 == +0.0이 성립합니다.
구체적인 손상 시퀀스는 다음과 같습니다:
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
이 시점에서 HashMap은 구조적 일관성을 잃습니다. open MemoryIndexCursor가 참조 중인 entry는 이미 파괴되어 있으므로, 이후 cursor 읽기는 해제된 record를 역참조하게 됩니다. 부가적인 lifetime 버그도 있습니다. MemoryIndex::transactionAborted에서 rollback 시 live cursor가 아직 참조 중인 index record를 제거하거나 교체하는 문제입니다. 세 번째는 defense-in-depth 측면의 결함으로, didFinishHandlingVersionChangeTransaction이 transaction 종료 여부를 검증하지 않고 상태를 변경합니다.
결과적으로 IndexedDB의 in-memory store invariant가 약화됩니다. script가 보유한 cursor를 통해 MemoryIndexRecord에 대한 heap UAF primitive를 얻을 수 있습니다.
Audit directions
- hash/equality 불일치가 있는 모든
IDBKeyData필드. Number, Date, 그리고 향후 추가될 floating-point 또는 NaN을 포함하는 필드가 위험에 노출됩니다. - JS에서 접근 가능한 primitive로 구성된 HashMap 키.
Map/Setbacking storage, Cache API, 그리고 JS 값을 키로 사용하는 다른 client-side 저장소에서도 동일한 hash-equals 불일치를 점검해야 합니다. transactionAborted,transactionFinished,deleteIndex하에서의MemoryIndexcursor lifetime. 모든 변경 경로에서 cursor 무효화가 전파되는지 확인해야 합니다.