← All issues

[25] [JSC] Don't advance bytecode when reifying inline frames at a checkpoint

Severity: Medium | Component: JSC DFG JIT | 4e802a1

Medium severity로 분류됩니다. diff는 잘못된 exception-handler dispatch metadata를 수정합니다. OSR exit 중 reified baseline frame에 기록되는 CallSiteIndex가 checkpoint 이후의 bytecode 위치를 가리키고 있어, re-executed checkpoint instruction에서 throw가 발생하면 잘못된 HandlerInfo try-range로 매핑됩니다.

reifyInlinedCallFramesbytecodeIndexForExit(...)를 사용했습니다. 이 함수는 checkpoint가 포함된 index를 checkpoint 이후의 bytecode 위치로 정규화합니다. 재개(resumption) 목적으로는 올바른 동작이지만, unwinding 시에는 잘못된 위치를 참조하게 됩니다.

Source/JavaScriptCore/dfg/DFGOSRExitCompilerCommon.cpp

- BytecodeIndex exitIndex = baselineCodeBlock->bytecodeIndexForExit(codeOrigin->bytecodeIndex());
+ BytecodeIndex exitIndex(codeOrigin->bytecodeIndex().offset());

Checkpoint에서 inline frame을 reify할 때, resumption에 사용되는 bytecode index와 exception-handler 조회에 사용되는 bytecode index 간의 off-by-one.

Checkpoint는 baseline frame layout과 HandlerInfo 조회에 고정된 "스택 프레임당 하나의 bytecode index"라는 기존 관례 위에 sub-bytecode resumption을 덧씌우는 구조입니다. 재개 index는 OSR sidestate에 별도 보관되는 반면, unwinder는 프레임에 기록된 CallSiteIndex를 사용합니다. 두 index가 동일한 "baseline 정규화" 헬퍼에서 기록되는 모든 지점은 구조적 위험을 내포합니다.

도달 경로를 살펴보면, 세 가지 조건이 충족되어야 합니다. 먼저 DFG로 컴파일된 인라인 함수 내에 op_instanceof(또는 op_iterator_open/next, op_get_by_val_with_this)가 포함되어야 합니다. 또한 prototype/structure 무효화로 인해 re-execution 중 throw가 발생해야 하며, 호출 지점에 try/catch가 있어야 합니다. 이 세 조건이 충족되면 exception이 잘못된 catch 블록으로 전달됩니다.

이 vulnerability는 renderer 내부의 JS exception-handling 경계를 약화시킵니다. Baseline과 DFG semantics 사이의 JS state 불일치로 인해, 원래 control flow에서는 절대 생성되지 않았을 값이 노출될 가능성이 있습니다.