Add missing writeBarrier to Array.unshift
Two array opcodes shared one case label in JSC's write-barrier phase.
Component: JSC DFG JIT | f641af0
JavaScriptCore의 concurrent GC는 mutator(JS 실행)가 병렬로 계속 동작하는 동안에도 object storage를 스캔합니다. 그래서 heap reference를 object의 backing store 내부에서 이동시키는 코드는 반드시 write barrier를 emit해야 합니다. Write barrier란 해당 slot에 collector가 아직 보지 못한 값이 들어갈 수 있다는 사실을 collector에게 알리는 notification입니다. Array.prototype.push의 DFG fast path는 이번 변경 이전부터 존재해왔습니다. 반면 unshift의 inline fast path는 313475@main에서 push의 구현을 본떠 추가되었고, DFGStoreBarrierInsertionPhase.cpp의 switch문에서도 push의 case를 그대로 공유해서 사용했습니다.
Source/JavaScriptCore/dfg/DFGStoreBarrierInsertionPhase.cpp
이번 변경으로 ArrayUnshift가 공유되던 ArrayPush case에서 분리되어, 자신만의 barrier 규칙을 갖게 되었습니다. Inline으로 emit되는 경우는 single-element Contiguous shape 하나뿐이며, 그 외 나머지 경우는 자체적으로 barrier를 수행하는 operation을 통해 처리됩니다. 그리고 이 single-element Contiguous shape에 대해서는 이제 phase가 prepend되는 값의 edge에 considerBarrier를 호출하도록 되어 있습니다.
Significance
Push는 항상 storage의 끝을 넘어서 append하는 동작만 수행하므로, 기존의 공유 로직만으로도 충분했습니다. 반면 unshift는 prepend할 값을 위한 공간을 만들기 위해 기존 원소들을 위로 이동시키는데, 이 과정에서 publicLength가 갱신되기 전까지 live cell이 일시적으로 스캔 범위 밖에 놓이게 됩니다. 기존의 공유 case는 이 차이를 전혀 구분하지 못했습니다. Single-element Contiguous unshift fast path가 live heap cell을 collector의 스캔 범위 밖으로 이동시킬 수 있었고, 그 결과 여전히 array에서 도달 가능한 객체를 GC가 회수해버릴 수 있었습니다. 이는 GC 압력이 걸린 상황에서 순수 JS만으로 유발 가능한 use-after-free primitive에 해당합니다.
Audit directions
여기서 재사용 가능한 패턴은, heap reference를 이동시키거나 재해석하는 DFG/FTL inline fast path가 그에 대응하는 write barrier 없이 존재하는 경우입니다. 특히 이번 unshift처럼 인접한 opcode를 복사·붙여넣기하는 방식으로 새 경로가 만들어졌을 때 이런 문제가 생기기 쉽습니다. 좁게 보면, 최근 DFG inline fast path가 추가된 다른 array/typed-array/string builtin들(splice, copyWithin, fill, sort)에서도 동일한 비대칭이 있는지 점검할 필요가 있습니다. 특히 단순히 append만 하는 게 아니라 이미 할당된 storage 내부에서 기존 원소를 이동시키는 operation을 우선적으로 살펴봐야 합니다. 넓게 보면, DFGStoreBarrierInsertionPhase.cpp의 case grouping 전체를 다시 읽어보면서, 실제로는 storage-mutation semantics가 서로 다른 opcode들을 여전히 하나로 묶어놓은 fall-through group이 남아 있는지 확인해야 합니다. 버그를 만들어낸 원인은 개별 opcode가 아니라 바로 이 grouping 구조 자체이기 때문입니다. 가장 넓게 보면, barrier insertion이 operation의 storage effect가 아니라 opcode identity만을 기준으로 결정되는 concurrent-collector runtime이라면 어디든 이런 형태의 취약점에 노출될 수 있습니다. Review 과정에서 눈여겨봐야 할 신호는, barrier phase 안에서 case A: case B:로 fall-through되어 있는데 A와 B가 기존 원소를 이동시키는지 여부에서 서로 다른 경우입니다.