← All issues

[14] SWServer dereferences unchecked HashMap end iterator

Severity: Low | Component: WebCore Service Worker Server | 3bce213

두 client map이 race condition으로 동기화가 어긋날 때만 도달 가능한 HashMap end-iterator 역참조를 수정합니다. commit message에는 재현 방법이 알려져 있지 않다고 명시됩니다. 영향은 service-worker bookkeeping 경로에서의 crash에 국한되며, 공격자가 메모리 읽기/쓰기를 제어할 수 있다는 근거는 없습니다.

m_clientIdentifiersPerOriginm_clientsById가 동기화 상태를 잃으면, topLevelServiceWorkerClientFromPageIdentifier()에서 crash가 발생합니다. 이 함수는 m_clientIdentifiersPerOrigin에서 client identifier를 순회하며 각각에 대해 m_clientsById.find()를 호출합니다. 그러나 역참조 전에 반환값이 m_clientsById.end()와 같은지 전혀 확인하지 않습니다.

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;
}

serviceWorkerClientWithOriginByID()에서는 기존의 ASSERT(clientIterator != m_clientsById.end())가 실제 end-iterator 검사로 변경되었습니다. end 조건에 해당하면 release 빌드에서 std::nullopt를 반환합니다. topLevelServiceWorkerClientFromPageIdentifier()에서는 iterator->value.identifiers를 순회하며 m_clientsById.find(clientIdentifier)를 호출하던 루프가, 기존에는 결과를 무조건 역참조했습니다. 이제 end-iterator 검사와 함께 ASSERT_NOT_REACHED()continue가 추가되어, 해당 identifier가 없는 항목은 건너뜁니다.

비동기 lifecycle 경로에서 paired-map 불변 조건이 어긋날 수 있는 환경에서, end-check 없이 HashMap iterator를 역참조하는 패턴.

SWServer는 세션의 service-worker 상태를 process 경계를 넘어 관리하는 소유자입니다. m_clientIdentifiersPerOriginHashMap<ClientOrigin, ...> 타입으로, 각 origin에 client로 등록된 모든 ScriptExecutionContextIdentifieridentifiers 집합으로 관리합니다. m_clientsById는 동일한 identifier를 키로 사용하는 별도의 HashMap<ScriptExecutionContextIdentifier, RefPtr<ServiceWorkerClientData>>입니다. 두 map의 불변 조건은, per-origin identifiers 집합에 등장하는 모든 identifier가 반드시 m_clientsById에도 존재해야 한다는 전제에 기반합니다. HashMap::find()는 키가 없을 때 end()와 같은 iterator를 반환하는데, 이 end iterator를 역참조하면 undefined behaviour가 됩니다. WebCore의 ASSERT(...)는 release 빌드에서 컴파일 시 제거되므로, ASSERT만으로는 런타임 보호 효과를 기대할 수 없습니다.

topLevelServiceWorkerClientFromPageIdentifier()m_clientIdentifiersPerOrigin의 per-origin 집합에서 client identifier를 가져온 뒤, m_clientsById.find(clientIdentifier)의 반환값이 m_clientsById.end()와 같은지 확인하지 않고 clientIterator->value를 역참조했습니다. 두 map은 서로 다른 code path를 통해 채워지고 해제됩니다. commit message에서는 이를, 두 map의 동기화가 어긋날 수 있는 race로 설명합니다.

🔒

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.

더 확인하려면 구독해 주세요

🔒

Multiple reusable audit patterns identified across the service-worker subsystem, with concrete grep targets for finding sibling instances of the same defensive gap.

더 확인하려면 구독해 주세요