← All issues

[3] [JSC FTL] Fix indexing-type mismatch in OSR exit array materialization during bad time

Severity: High | Component: JSC FTL JIT | eba64ef

FTL OSR exit으로 생성된 script-visible JSArray에서 SlowPutArrayStorage Structure와 contiguous butterfly 사이의 type confusion을 수정하는 diff로, High로 평가합니다. 결과로 만들어진 cell을 ArrayStorage code path로 처리하면 contiguous 메모리 위에서 동작하게 되며, 인접한 heap에 대한 controlled out-of-bounds primitive가 발생합니다.

operationMaterializeObjectInOSR는 rematerialized array의 structure를 originalArrayStructureForIndexingType으로 선택하도록 변경되었습니다. 이를 통해 bad-time 상태와 관계없이 contiguous structure가 유지됩니다. butterfly를 채운 뒤 일관된 상태가 확보되면 switchToSlowPutArrayStorage(vm)을 호출합니다. operationPopulateObjectInOSR에는 migration 이후 hole fixup을 위한 hasAnyArrayStorage 분기가 추가되었습니다.

Source/JavaScriptCore/ftl/FTLOperations.cpp

- Structure* structure = globalObject->arrayStructureForIndexingTypeDuringAllocation(materialization->indexingType());
+ Structure* structure = globalObject->originalArrayStructureForIndexingType(materialization->indexingType());
JSArray* array = JSArray::createWithButterfly(vm, nullptr, structure, butterfly);
...
+ if (globalObject->isHavingABadTime())
+ array->switchToSlowPutArrayStorage(vm);

Sunk array의 FTL OSR materialization 과정에서 SlowPutArrayStorage Structure와 contiguous butterfly 사이에 발생하는 type confusion.

PhantomNewArrayWithButterfly rematerialization 과정에서 더 이상 arrayStructureForIndexingTypeDuringAllocation을 호출하지 않습니다. 대신 originalArrayStructureForIndexingType을 사용합니다. butterfly가 채워진 뒤(contiguous 슬롯, contiguous 방식으로 public/vector length 설정) 전용 transition을 통해 SlowPutArrayStorage로 migration됩니다. operationPopulateObjectInOSR는 contiguous와 ArrayStorage 형태 모두에서 hole을 수정할 수 있게 되었습니다.

FTL OSR exit은 optimizer가 sunk 처리한 객체의 materialization을 재구성합니다. PhantomNewArrayWithButterfly의 경우, runtime은 새 JSArray를 할당하고 Structure를 선택한 뒤 butterfly를 allocate하여 채웁니다. JSC의 "bad time" mode는 Array.prototype 또는 Object.prototype에 indexed accessor가 설치될 때 진입합니다. 이 mode에서는 global object의 기본 array structure가 SlowPutArrayStorage로 전환됩니다. SlowPutArrayStorage는 contiguous와 다른 butterfly header(m_vector, m_numValuesInVector, m_sparseMap)와 슬롯 레이아웃을 사용합니다.

패치 이전에는 arrayStructureForIndexingTypeDuringAllocation으로 structure를 조회했습니다. 이 함수는 해당 indexing type의 현재 기본값을 반환합니다. 즉, bad time 상태일 때는 대체 structure가 반환됩니다. butterfly를 채우는 코드는 contiguous 방식으로 고정되어 있었습니다. butterfly->contiguous().atUnsafe(index)에 값을 기록하고 contiguous 방식의 public/vector length를 설정했습니다. 결과로 생성된 JSArrayStructure를 통해 SlowPutArrayStorage를 나타냈지만, butterfly 자체는 ArrayStorage header 없이 contiguous 형태였습니다.

이후 indexing type에 대한 dispatch는 contiguous 슬롯을 ArrayStorage vector entry로 해석합니다. operationPopulateObjectInOSR의 hole fixup도 이에 해당합니다. 이 fixup은 contiguous().atUnsafe(index).setStartingValue()를 통해 JSValue() sentinel을 기록하며, 이후의 script-visible load/store 역시 contiguous 슬롯을 ArrayStorage vector entry로 해석합니다. contiguous payload 영역에 aliased된 header 필드가 문제의 근원입니다. m_vector는 contiguous 슬롯이 시작하는 위치와 겹치는데, 이로 인해 contiguous 범위를 초과한 읽기와 인접 메모리에 대한 쓰기가 발생합니다. 결과적으로 heap 위의 controlled OOB read/write primitive가 만들어집니다.

패치는 동작 순서를 재정립합니다. array를 contiguous 형태로 생성하고, butterfly를 일관되게 채운 뒤, header를 재구성하는 실제 transition을 통해 SlowPutArrayStorage로 migration합니다. 새로 추가된 hasAnyArrayStorage 분기는 migration 이후의 hole fixup을 올바르게 처리합니다.

이 버그는 FTL deoptimization 과정에서 JSC의 array shape invariant를 약화시킵니다. exploit primitive는 script에서 접근 가능한 JSArray에 대한 type-confused indexed access로, JIT exploitation chain의 전형적인 시작점에 해당합니다.