← All issues

[10] [JSC] StringAt should respect arrayMode in CSE

Severity: High | Component: JSC DFG JIT | 1f4e4ca

High 평가. String.prototype.at의 잘못된 CSE 패치. GCSE 키에서 arrayMode 누락. in-bounds (String 반환)와 OOB (undefined 반환) 연산 병합. JIT-IR 수준 type confusion. 웹 JS에서 도달 가능.

StringAtStringCharAtdef(PureValue(node)) 절로 fall through되었습니다. charAt은 반환 타입이 일정합니다. OOB의 경우에도 항상 빈 문자열을 반환합니다. 반면 at은 범위에 따라 반환 타입이 달라집니다. 범위 내이면 string을, 범위 밖이면 undefined를 반환합니다.

Source/JavaScriptCore/dfg/DFGClobberize.h

case StringAt:
+ def(PureValue(node, node->arrayMode().asWord()));
+ return;
case StringCharAt:
def(PureValue(node));
return;

StringAt에 별도의 case가 추가되어 def(PureValue(node, node->arrayMode().asWord()))를 호출합니다. 이로써 ArrayMode가 CSE 키에 포함되고, array mode가 다른 node들은 더 이상 병합되지 않습니다.

CSE 키에서 연산의 반환 타입을 결정하는 mode 구분자가 누락되어, optimizer가 출력 타입 계약이 서로 다른 두 연산을 병합하게 됩니다.

clobberize()는 pure node에 대한 CSE를 구동하기 위해 read/write/def 요약을 생성합니다. PureValue는 side effect가 없는 node의 CSE 키로, 기본적으로 (opcode, child edges)를 해시하는데, PureValue(node, word) 형태로 추가 구분자를 삽입하는 것도 가능합니다. ArrayMode는 DFG에서 indexed 연산을 node별로 특수화하는 정보이며, bytecode별 array profile에서 도출됩니다. String.prototype.at(i)charAt의 핵심 차이는 OOB 처리에 있습니다. charAt은 OOB에서 빈 문자열을 반환하지만, atundefined를 반환합니다.

같은 함수 안에 있는 두 s.at(i) 호출은 서로 다른 방식으로 특수화될 수 있습니다. 하나는 in-bounds 전용 String 모드로, 다른 하나는 OOB를 감지하는 Generic 모드입니다. ArrayMode가 빠진 PureValue(node) 키를 공유하는 상황에서는, string과 index 입력이 동일하다는 이유만으로 CSE가 mode-A StringAt과 mode-B StringAt을 병합하게 됩니다.

병합 후 남겨진 node에는 자체적인 특수화 lowering이 결정되어 있습니다. 문제는 downstream node들이 이 node의 타입을 기반으로 abstract-interpret한 결과가, 다른 mode의 반환 타입 계약을 기대하는 사용 지점에 적용된다는 것입니다. 결과적으로 StringUndefined 사이의 type confusion이 발생합니다. 회귀 테스트는 opt("hello", 1)을 20000번 실행해 JIT를 warm up한 뒤, --jitPolicyScale=0.1 조건에서 opt("hello", 100)을 200번 호출합니다. 전자는 항상 in-bounds, 후자는 항상 OOB 상황입니다. JIT 추적에서 String으로 타입이 지정되었지만 runtime에서 Undefined로 태그된 값은 DFG type confusion의 전형적인 전조 패턴입니다.

이 vulnerability는 WebContent process 내부에서 DFG의 타입 시스템 불변 조건을 약화시킵니다. CSE로 병합된 두 node는 출력 타입을 공유해야 하지만, 이 경우에는 그렇지 않았습니다.