[JSC] ScopedArgumentsTable ScopeOffset buffer allocates from fastMalloc
Component: JSC runtime | 72272dc
Source/JavaScriptCore/runtime/ScopedArgumentsTable.h
Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp
Gigacage::Primitive는 JSC가 typed array와 ArrayBuffer의 backing store를 담아두는 대형 격리 가상 메모리 영역입니다. JS가 제어하는 typed array에서 OOB write가 발생하더라도 무관한 heap 객체로 쉽게 번지지 않도록 막아주는 구조로, cage 경계는 JIT exploit을 막는 핵심 mitigation에 해당합니다. 반면 ScopedArgumentsTable은 JS에서 직접 도달할 수 없는 VM 내부 관리용 데이터입니다. arguments 객체의 슬롯을 ScopeOffset 값으로 매핑하며, 이 값은 이후 JSLexicalEnvironment의 변수 저장소에 대한 index로 별도의 bounds check 없이 그대로 사용됩니다. 해당 저장소가 신뢰 가능하다는 전제 위에서 설계되었기 때문입니다. 이번 commit은 이 테이블의 버퍼를 Primitive cage 밖으로 옮겨 일반 fastMalloc 기반의 Vector<ScopeOffset> 저장소로 변경합니다. 아울러 trySetLength의 locked branch에서 남아 있던 m_watchpointSets.resize() 호출도 함께 제거하는데, 이 호출은 원래 변경되지 않아야 할 locked 상태의 테이블을 잘못 수정하고 있었습니다.
Before: After:
Gigacage::Primitive region Regular fastMalloc heap
|- Float64Array backing store ScopedArgumentsTable::m_arguments
|- ScopedArgumentsTable::m_arguments <-- reachable via cage OOB write
\- other typed array buffers
Cage OOB write -> corrupt ScopeOffset -> Cage OOB write -> typed arrays only
unchecked index into
JSLexicalEnvironment::variables()
Significance
VM 내부 index를 Primitive cage 안에 두었다는 것은, typed array OOB write가 attacker가 제어 가능한 lexical-environment indexing에까지 도달할 수 있는 경로를 사실상 열어주고 있었다는 의미입니다. 이번 변경으로 cage의 신뢰 모델이 원래대로 복원됩니다. cage 안에 있어야 할 것은 JS가 제어하는 데이터이지, 신뢰된 저장소를 인덱싱하는 메타데이터가 아니기 때문입니다.
Audit directions
좁게 보면, 이번 마이그레이션으로 인한 회귀가 없는지 주변 allocation·resize 경로를 점검할 필요가 있습니다. Vector::tryGrow()는 기존 CagedUniquePtr allocation과 growth·재할당 semantics가 다르므로, caged allocator의 동작에 암묵적으로 의존하던 코드가 없는지 확인해야 합니다. 또한 trySetLength의 growth 경로가 새로 추가된 watchpoint 슬롯만 zero-fill하는지도 검증할 필요가 있습니다. 제거된 locked branch의 m_watchpointSets.resize(newLength) 호출은, commit message의 "어떤 caller도 이전의 resize된 상태를 관찰하지 않았다"는 주장을 그대로 받아들이기보다 SymbolTable::trySetArgumentsLength를 기준으로 독립적으로 재확인해볼 가치가 있습니다. 더 넓게 보면, 이번 패턴 — JS가 제어하는 데이터를 위한 cage에 VM 내부 메타데이터가 잘못 배치되는 경우 — 은 JSC 내 나머지 CagedUniquePtr 사용처 전반을 대상으로 검색해볼 가치가 있습니다. 유사한 클래스에서도 같은 노출이 존재할 가능성이 있기 때문입니다. 확인할 패턴은, T가 index·offset·pointer 타입이면서 bounds check 없이 사용되는 CagedUniquePtr<Gigacage::Primitive, T> 형태입니다.