[16] [JSC] Fix data race in WaiterListManager::unregister
Severity: Medium | Component: JSC WaiterListManager | 21ab50e
패치에서는 Waiter 생성 시점에 realm pointer를 캐시하여 cancellation 판단 시 VM 경계를 넘는 읽기가 발생하지 않도록 처리했습니다. 이 점 때문에 Medium으로 평가됩니다. 패치 이전에는 한 VM의 sweep thread에서 WaiterListManager::unregister가 실행되는 도중, 다른 VM의 GC End phase가 m_dependencies를 수정하고 있었습니다. 특정 스케줄링 조건에서는 torn 상태이거나 이미 해제된 pointer를 역참조하는 문제가 발생할 수 있었습니다.
Waiter는 생성 시점(어떤 리스트에도 등록되기 전)에 realm pointer를 캐시합니다. WaiterListManager::unregister는 ticket->target()->realm() 대신 이 캐시된 pointer를 참조합니다.
Source/JavaScriptCore/runtime/WaiterListManager.cpp
- JSGlobalObject* targetRealm = waiter->ticket(listLocker)->target()->realm();
+ JSGlobalObject* targetRealm = waiter->cachedRealm();
변경 중인 storage에 대한 cross-VM 읽기: 한 VM의 sweep thread가 다른 VM 소속의 waiter를 순회하며 m_dependencies를 역참조하는 동안, 해당 VM의 GC End phase가 이를 수정하고 있었습니다.
Patch Details
Waiter에 생성 시 설정되는 JSGlobalObject* 멤버가 추가되었습니다. unregister는 cancellation 여부를 판단할 때 이 캐시된 pointer만 읽습니다. WaiterList의 구조적 변경에 대한 직렬화는 list lock이 계속 담당합니다.
Background
WaiterListManager는 SharedArrayBuffer의 Atomics.wait / Atomics.waitAsync를 지원하는 per-process registry입니다. SAB는 main window, iframe, dedicated worker 등 여러 VM에 걸쳐 공유됩니다. 이 때문에 SAB pointer를 키로 하는 단일 WaiterList에 서로 다른 VM의 Waiter가 함께 담길 수 있습니다. DeferredWorkTimer::TicketData::m_dependencies는 FixedVector<JSCell*> 타입입니다. wait 중 promise/target을 GC가 회수하지 못하도록 유지하는 역할을 담당합니다.
Analysis
패치 이전에는 WaiterListManager::unregister가 unregistering VM의 sweep thread에서 실행되며 공유 리스트의 모든 waiter를 순회했습니다. 각 waiter마다 waiter->ticket(listLocker)->target()을 호출했는데, 이 함수는 bit_cast<JSObject*>(m_dependencies.last())로 구현되어 있습니다. 이를 통해 target의 realm을 unregistering global object와 비교했습니다.
Aaa Aa Aaaaaaa Aaa Aaa Aaa Aa Aaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaa Aaaaa Aaa a Aaaaaa Aaaa Aaaaa Aaaaaaaaaaaaa Aa Aa Aaa Aa Aaa a Aaaaaa Aaa Aaaa Aaaa Aaaaaa a Aaa Aa Aaa Aaaa Aaaaa Aa Aaa Aaaaaaa Aaaaaa Aaa Aaaa Aaaaaaa a Aa Aaaaa Aaaaaaaaaaaa Aaaaa Aa Aaaaaaaaaaa Aaa a Aa Aaa Aaaaaaaaaaaa Aaa a Aaaaaaaaaaaaaaa Aaaa Aaaa
Aaa
Aa a Aaaaaaa Aa a Aaa Aaaa
Aaaaaaaaaaaaa Aaaaaaaaaaaaaa
Aaaa Aaaa Aaaaaaaaaa
Aaaa Aaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaa
Aaaaa a Aaaaaaa Aaaaaaa Aaaaa Aaaaaa
Aaa
Aa Aaa Aaaaaaaa Aa Aaa Aaaa Aaaaa Aaaaaa Aaaaaaaa Aa Aaa Aaaaa Aaaa Aaaa Aaaaa Aaa Aaaaaaa Aa Aaa Aa Aaa Aa Aaaa
a Aaa Aaa Aaaaaa Aaaaaaaaaa Aa a Aaaaaaa Aaaa Aa Aaaaaa Aa Aaaaaaaaaa Aa Aaa Aa Aaaa Aa Aaa Aaa Aaaaaa
🔒What can really happen when two VMs share a single SAB waiter list and one of them tears down its realm while the other GC-frees its ticket — the cross-VM lifetime and exploitation reasoning is unpacked in depth.
더 확인하려면 구독해 주세요
Audit directions
a Aaa Aaa Aaaaaaa Aaaa Aaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaa a Aaa Aa Aa Aaa Aaa a Aa Aaa Aaaaaa
a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaa a Aaa Aaaaaaaaaa Aa Aaa Aaa Aaaaaa
a Aaaa Aaaaaaaaa Aa Aaa Aaaaa Aaaaaaaaa a Aa Aaaaa Aa Aaaaa Aaaaaaaaaaaaaa Aaaaa Aaaaaa
🔒Four reusable audit patterns identified, covering shared per-process registries, ticket-target dereferences, cached-identity-at-construction, and racy interior-pointer reads of vector backing stores.
더 확인하려면 구독해 주세요