[15] FTL MultiGetByOffset constant-function reduced under DoubleRepUse (test backport)
이 commit은 regression test만 backport하므로 Low로 평가합니다. 기반이 되는 type confusion fix는 이미 release branch에 적용된 상태이며, 이번 변경이 mainline에 추가하는 것은 fix의 regression 방지 범위를 넓히는 것 이상이 아닙니다.
이 commit은 DFGValueRepReductionPhase가 MultiGetByOffset 노드를 처리하는 과정에서 발생했던 FTL 버그(305413.251@safari-7624-branch)의 regression test를 backport합니다. 해당 버그는 case set에 Constant:Function slot과 numeric slot이 혼재하는 상황에서 발생했습니다.
JSTests/stress/ftl-multigetbyoffset-constant-numberuse.js
Patch Details
이 commit에서 추가된 것은 test 파일 하나뿐입니다. test는 prototype slot을 공유하는 두 가지 object 구조를 구성합니다. obj1은 prototype에서 prop을 Constant:Function으로 상속받고, obj2는 prop을 NaN으로 shadowing합니다. 이후 IC와 ValueProfile을 warm up한 뒤, DFG/FTL이 두 structure를 모두 포함하면서도 Number만 예측하는 MultiGetByOffset을 바라보도록 만듭니다. 이를 통해 value-rep reduction pass가 buggy path로 진입하도록 유도합니다.
double-typed use 하에서 MultiGetByOffset을 lower할 때, 비수치형 case constant에 대한 escape 처리 누락.
Background
MultiGetByOffset은 property access의 inline cache가 두 개 이상의 structure를 관찰했지만 set의 크기가 inline 가능한 수준일 때 생성되는 DFG/FTL IR 노드입니다. 각 case마다 GetByOffsetMethod를 하나씩 가지며, case 종류로는 offset에서 Load하는 방식, 값이 IR에 직접 고정되는 Constant 방식, 그 외의 변형이 있습니다. ValueProfile은 Baseline JIT와 LLInt가 use 지점에서 관찰한 JSValue 종류를 기록하는 per-bytecode profiling slot입니다. Constant:Function case는 property가 단일 함수를 정적으로 보유하는 것으로 알려진 structure에서 오는 상황에 해당합니다. prototype method에서 흔히 나타나는 패턴으로, IC는 해당 값을 function cell의 Constant로 기록합니다. DoubleRepUse와 NumberUse는 consumer가 값을 boxed JSValue가 아닌 unboxed IEEE-754 double 형태로 받기를 원한다는 사실을 FTL backend에 알리는 edge-kind annotation입니다. DFGValueRepReductionPhase는 FTL 계층의 DFG phase로, unboxed double representation을 정의 지점 가까이로 끌어당기도록 IR을 재작성합니다.
Analysis
이전 fix가 적용되기 전, downstream consumer가 DoubleRepUse를 요구하면 DFGValueRepReductionPhase는 Constant:Function slot을 포함한 case set을 가진 MultiGetByOffset을 double-typed value chain으로 lower하려 시도했습니다. 이 pass는 MultiGetByOffset의 constant case slot 값을 직접 사용합니다. case가 JSCell constant(여기서는 prototype에서 상속된 function 객체)를 반환하는 상황에서, pass가 모든 값이 수치로 표현 가능하다고 전제하고 double로 변환하면, function cell의 bit pattern이 double로 처리됩니다. 또는 cell을 정확히 표현하지 못하는 강제 변환이 수행될 가능성도 있습니다.
이미 적용된 fix는 이 gap을 보완합니다. 수치가 아닌 case set constant는 double-typed reduction에 참여하는 대신, JSValueRep을 유지하는 방식으로 escape 처리되어야 합니다. test는 IC가 Structure A(Constant:Function)를 정확히 한 번만 관찰하는 동안 ValueProfile이 Structure B(수치형 NaN)에 지배되도록 구성함으로써 이 trigger를 재현합니다. 결과적으로 DFG 수준의 MultiGetByOffset이 NumberUse/DoubleRepUse 예측 하에 Constant:Function case를 갖게 됩니다.
이 vulnerability는 FTL로 컴파일된 renderer 코드 내부의 type 안전성을 약화시켰습니다. IC와 ValueProfile을 조작할 수 있는 공격자는, FTL이 JSCell constant를 반환하는 MultiGetByOffset case를 double-typed value chain을 통해 lower하도록 유도할 수 있습니다. 이 chain은 JSC type confusion에서 WebContent의 arbitrary read/write로 확장하는 데 사용되는 핵심 primitive입니다. ValueRepReduction은 IC의 per-case GetByOffsetMethod와 FTL의 edge-kind type system이 만나는 경계에 위치합니다. 이 두 시스템 사이를 변환하는 pass는, use 지점의 예측이 Number라고 하더라도, case set의 모든 항목을 잠재적으로 비수치형으로 취급해야 합니다.
Audit directions
- polymorphic IC 노드를 lower하는 DFG/FTL pass (
MultiGetByOffset,MultiPutByOffset,MultiDeleteByOffset): case set constant가 use 지점의 예측 타입과 호환된다고 가정하는 코드를 점검합니다.Source/JavaScriptCore/dfg/에서MultiGetByOffsetData::cases를 사용하는 모든 코드 중, constant의 JSValue kind를 확인하지 않고 unboxed representation으로 변환하는 부분을 살펴봅니다.DFGValueRepReductionPhase.cpp,DFGStrengthReductionPhase.cpp,DFGAbstractInterpreterInlines.h부터 먼저 점검할 것을 권장합니다. - ValueProfile/IC 관찰 불일치: profile slot이
Number를 예측하는 동안, IC가 관찰한 structure set에는 비수치형Constant를 slot에 보유한 structure가 포함되어 있을 가능성이 있습니다. structure set과 value profile 예측을 결합하는 DFG 지점(Node::prediction()과MultiGetByOffsetData조합)을 살펴보고, 예측과 일치하지 않는 constant case가 올바르게 거부되거나 escape 처리되는지 확인합니다. - JIT IR에서 cell constant의 암묵적
JSValue→double변환: case set constant를 double로 materialize하는 모든 지점에서value.isNumber()(또는 동등한 검사)를 먼저 수행하는지 확인합니다. 검사에 실패하는 경우 cell bit를 masking하지 않고 값을 escape 처리하는지도 점검합니다.FTLLowerDFGToB3.cpp의MultiGetByOffsetlowering 부분과, IR constant를 double로 강제 변환하는 데 사용되는DFGGraph또는JSValue의 helper를 살펴봅니다.