[14] SWServer dereferences unchecked HashMap end iterator
Severity: Low | Component: WebCore Service Worker Server | 3bce213
Rated Low because the diff fixes a HashMap end-iterator dereference reachable only when paired client maps desynchronise across a race the commit message states has no known reproducer; impact is bounded to a crash in the service-worker bookkeeping path, with no evidence of attacker-controlled read/write.
A crash occurs in topLevelServiceWorkerClientFromPageIdentifier() when the Service Worker maps m_clientIdentifiersPerOrigin and m_clientsById become out-of-sync. The function iterates over client identifiers from m_clientIdentifiersPerOrigin and calls m_clientsById.find() for each one, but never checks whether the result equals m_clientsById.end() before dereferencing.
Source/WebCore/workers/service/server/SWServer.cpp
auto clientIterator = m_clientsById.find(clientIdentifier);
-ASSERT(clientIterator != m_clientsById.end());
+if (clientIterator == m_clientsById.end()) [[unlikely]] {
+ ASSERT_NOT_REACHED();
+ return std::nullopt;
+}
return clientIterator->value;
...
for (auto clientIdentifier : iterator->value.identifiers) {
auto clientIterator = m_clientsById.find(clientIdentifier);
+ if (clientIterator == m_clientsById.end()) {
+ ASSERT_NOT_REACHED();
+ continue;
+ }
if (clientIterator->value->frameType == ServiceWorkerClientFrameType::TopLevel && clientIterator->value->pageIdentifier == pageIdentifier)
return clientIterator->value;
}
Patch Details
In serviceWorkerClientWithOriginByID(), the previous ASSERT(clientIterator != m_clientsById.end()) is replaced with a real end-iterator check that returns std::nullopt in release builds. In topLevelServiceWorkerClientFromPageIdentifier(), the loop that iterates over iterator->value.identifiers and calls m_clientsById.find(clientIdentifier) previously dereferenced unconditionally; an end-iterator check with ASSERT_NOT_REACHED() and continue is added so missing entries are skipped.
Dereferencing a HashMap iterator without an end-check when paired-map invariants can desynchronize across asynchronous lifecycle paths.
Background
SWServer is the cross-process owner of service-worker state for a session. m_clientIdentifiersPerOrigin is a HashMap<ClientOrigin, ...> whose value type carries an identifiers set listing every ScriptExecutionContextIdentifier that has registered as a client of that origin. m_clientsById is a separate HashMap<ScriptExecutionContextIdentifier, RefPtr<ServiceWorkerClientData>> keyed by the same identifier. The intended invariant is that every identifier appearing in any per-origin identifiers set has a corresponding entry in m_clientsById. HashMap::find() returns an iterator equal to end() when the key is not present, and dereferencing that end iterator is undefined behaviour. ASSERT(...) in WebCore is compiled out in release builds, so an ASSERT-only guard provides no runtime protection.
Analysis
topLevelServiceWorkerClientFromPageIdentifier() obtained a client identifier from the per-origin set in m_clientIdentifiersPerOrigin and then performed m_clientsById.find(clientIdentifier) without checking whether the returned iterator equals m_clientsById.end(). The function then dereferenced clientIterator->value. The two maps are populated and torn down through separate code paths — the commit message describes this as a race that can leave them out-of-sync.
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaa Aaa Aaaa Aaaa Aa Aaaaaaaa a Aaaa Aa Aaaaa Aaaaaaa Aaaaaaa Aa Aaaaaaaa Aa Aaaaaaa Aaaaaa Aaaaaaaaaaaa Aaa Aaa Aaaaaaaa Aaaaaaaaaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaaaaa Aa Aaa Aaaaaaa Aaaaaaa Aaaa Aaaaa Aaaaaaaaaaa Aaa Aaaaaaa Aaaa Aaaaaaa a Aaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaa Aaa Aaa Aaaaaa Aaaa Aaaaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaaaaa Aa a Aaaaa Aa Aaa Aaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaa Aa Aaaaaaaa Aa Aaa Aaaa Aa Aaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaaaaa Aaaaa Aaaaaaa Aa Aaaaa Aaa Aaaaaaaa Aaaaaaaa Aaaaa Aaa Aaa Aaaa Aaaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaa Aaaa Aaaaa Aaaa Aa Aaaaaaaa Aaaaaaaaa Aaaaaaaa Aaaa Aa Aaaaaaaaa Aa a Aaaaaaaaa Aaaaaa Aaaaaaa a Aaa Aaaaaa Aaaaaaa Aaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaaaa Aaaaaaaaaa Aaa Aaa Aa Aaaa Aaaaaaa Aaaaaaa Aaaaaaaa Aaaa Aaa
🔒The lifetime and synchronization implications between two parallel service-worker bookkeeping maps are explored, along with the realistic ceiling on what this crash can be escalated to.
Subscribe to read more
Audit directions
a Aaaaaaaa Aaaaaaaa Aaaa Aa Aaaaaaaa Aaaaaa Aaa Aa Aaa a Aaaaaaa Aa Aaa Aa Aaaaaaaaaa Aaaaaaa Aaaa Aa Aaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaaaaaaaaaa Aaaaa Aaa Aaaaa Aaaaaaaaa Aaaaa Aa Aaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaa Aa Aaaaaaaaaa
a Aaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaa Aaaaaaaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaa Aaaaa Aa a Aaaaaaaaaaa Aa Aaa Aaaa Aaaaaaaaa Aaaa Aaa Aa a Aaaaaaaaa Aaa Aaa Aaaa Aaaaaaaaaaaaaaaaaa
a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaa Aaaaa Aaaaaaaa Aaaaa Aaa Aaaaa Aaaaa Aaaaaaa Aaa Aaaaaa Aaaaaa Aaaaaaa Aaaaaa Aaa Aaa Aa Aaaaaaa Aaaaaaaaaaaa Aaaa Aaa Aaaaaaaa Aaaaa Aaa Aaaaa Aa Aaaaa Aaaaaaaaa
a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaa Aa a Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaa Aa Aaaa Aaaa Aaaa Aaa Aaaaaaaa Aa Aaaaaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaaaaa Aaaa Aaaaaa a Aaaa Aaaaa Aaaaaaaaaaa Aaaaaa Aaaaaaaaaaaa Aa Aaaaaaaa Aa a Aaaaaaaaaaaaaaaaaaaaaaaaaa
🔒Multiple reusable audit patterns identified across the service-worker subsystem, with concrete grep targets for finding sibling instances of the same defensive gap.
Subscribe to read more