[16] [JSC] JSLock m_hasOwnerThread has concurrency issue
JSLockownership state의 publication symmetry가 복원되는 패치이므로 Medium으로 평가됩니다. 이 race는 lock을 보유하지 않은 thread에서currentThreadIsHoldingLock()이 true를 반환하게 만들어, 두 thread가 VM에 동시에 진입하는 상황을 유발할 수 있습니다. 다만 racy observation에 도달하려면, stalem_ownerThread가 우연히 현재 thread와 일치하는 타이밍이 필요합니다.
JSLock::lock은 m_ownerThread를 기록한 뒤 m_hasOwnerThread = true를 설정했으며, 두 쓰기 사이에는 writer 측의 storeStoreFence()가 있었습니다. 그러나 reader는 두 필드를 plain non-atomic load로 읽었고, 이에 대응하는 acquire barrier가 없었습니다. 결과적으로 weak ordering을 허용하는 하드웨어에서는 m_hasOwnerThread == true를 관찰하면서 동시에 stale m_ownerThread를 읽는 상황이 가능했습니다.
Source/JavaScriptCore/runtime/JSLock.cpp / JSLock.h
Patch Details
m_hasOwnerThread가 std::atomic<bool>로 변경되었습니다. Writer는 memory_order_release를 사용하고, ownerThread(), ownerThreadUID(), currentThreadIsHoldingLock()의 reader는 memory_order_acquire를 사용합니다.
비대칭 memory fencing — writer에는 release 순서가 적용되었지만 reader가 unordered non-atomic load를 사용하는 구조에서는, 두 필드 사이의 publication invariant가 깨집니다.
Background
JSLock은 재귀적으로 동작합니다. lock()은 currentThreadIsHoldingLock()을 확인하여 true이면 underlying mutex를 획득하는 대신 m_lockCount를 증가시킵니다. (m_hasOwnerThread, m_ownerThread) 쌍은 m_lock을 보유하지 않은 상태에서 SamplingProfiler, MachineThreads, Web Thread interop이 접근하는 racy-readable view로 공개됩니다. 동일한 atomic에 대한 release-acquire 쌍은 이전 write를 이후 read와 동기화합니다.
stale m_ownerThread가 우연히 현재 thread와 일치하는 경우, 재귀 fast path는 underlying m_lock.lock()을 건너뛰고 진행합니다. 그 결과 두 thread가 VM에 동시에 진입하게 되며, 이에 수반되는 Structure/IndexingType corruption이 발생할 수 있습니다.
이 vulnerability는 JSC threading model의 근간이 되는 mutual-exclusion invariant를 약화시킵니다. WTF::storeStoreFence()는 writer 측만을 제약합니다. 이런 방식으로 공개된 flag를 다른 thread가 읽는다면, 대응하는 reader 측 순서 보장이 반드시 필요합니다.
Audit directions
- Writer가
storeStoreFence()를 사용하지만 reader는 plain load를 사용하는 단방향 fencing.Source/에서storeStoreFence를 검색하고, 각 결과에 대해 공개된 flag에 reader 측loadLoadFence()또는 acquire-atomic이 적용되어 있는지 확인해야 합니다. - Racy-readable ownership view (hasOwner-bool, owner-handle 쌍).
Lock,RecursiveLock, sampling profiler ownership flag,MachineStackMarkerthread suspension flag를 점검해야 합니다. - Underlying mutex 없이 owner state를 읽는 recursive-lock 재진입 fast path.
JSLock::currentThreadIsHoldingLock,ownerThread,ownerThreadUID의 모든 호출 지점을 살펴봐야 합니다. - "thread 간 읽기가 안전하다"고 문서화된 plain
bool멤버. WebKit 전체에서"racy","across threads","unlocked read"문자열을 포함하는 멤버 주석을 검색해야 합니다.