← All issues

[5] [JSC] Move DataView null vector check in IC outside of register save/restore

Severity: High | Component: JSC InlineCacheCompiler | 34669c8

IC handler가 매칭되는 push 없이 register를 pop하는 문제를 수정한 diff입니다. 이 경우 손상된 register file과 stack 상태로 polymorphic continuation에 진입하게 됩니다. 이후 JIT 코드는 공격자가 영향을 줄 수 있는 register 상태에서 실행됩니다. Severity를 High로 평가한 이유입니다.

emitIntrinsicGetter에서 push 이전의 null-vector check와 push 이후의 out-of-bounds check가 동일한 JumpList를 공유하고 있었습니다. 공유된 link site는 두 경우 모두에 대해 register를 pop하는 구조였습니다. 결과적으로 push 이전의 실패 경로에서 restoreReusedRegistersByPopping이 매칭되는 push 없이 실행되었습니다.

Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp

if (isResizableOrGrowableSharedTypedArrayIncludingDataView(accessCase.structure()->classInfoForCells())) {
+ m_failAndIgnore.append(failAndIgnore);
auto allocator = makeDefaultScratchAllocator(m_scratchGPR);
...
+ CCallHelpers::JumpList postPushFailAndIgnore;
if (isDataView) {
auto [outOfBounds, doneCases] = jit.loadDataViewByteLength(...);
- failAndIgnore.append(outOfBounds);
+ postPushFailAndIgnore.append(outOfBounds);
doneCases.link(&jit);
}
- if (allocator.didReuseRegisters() && !failAndIgnore.empty()) {
- failAndIgnore.link(&jit);
+ if (allocator.didReuseRegisters() && !postPushFailAndIgnore.empty()) {
+ postPushFailAndIgnore.link(&jit);
allocator.restoreReusedRegistersByPopping(jit, preservedState);
m_failAndIgnore.append(jit.jump());
} else
- m_failAndIgnore.append(failAndIgnore);
+ m_failAndIgnore.append(postPushFailAndIgnore);

push 이전 단계의 failAndIgnore jump는 pop 없이 m_failAndIgnore에 직접 추가됩니다. 새로 도입된 postPushFailAndIgnore 목록은 push 이후의 실패를 수집하며, restoreReusedRegistersByPopping을 올바르게 경유하는 경로로 처리됩니다.

JIT IC 실패 경로에서 register save/restore 불균형 발생: push 이전의 실패 jump가 post-push pop 경로를 통해 처리되어 stack이 비정상 상태가 되는 패턴.

JSC는 byteLengthget_by_id와 같은 property 접근 연산을 작은 machine code stub으로 캐싱합니다. check 실패 시에는 m_failAndIgnore로 fall through합니다. IntrinsicGetterAccessCaseDataView.prototype.byteLength와 같은 built-in getter를 특화 처리합니다. ScratchRegisterAllocator::preserveReusedRegistersByPushing / restoreReusedRegistersByPopping은 stub에 필요한 scratch register가 여유 register를 초과할 때, 재사용 register를 native stack에 push합니다. 이 두 함수 사이의 모든 exit 경로는 push와 pop을 모두 수행하거나, 둘 다 수행하지 않아야 합니다. loadDataViewByteLength는 underlying buffer의 length를 읽는 inline code를 생성하고, detach되거나 범위를 초과한 view에 대해 outOfBounds jump를 생성합니다.

push 이전의 null-vector check가 생성하는 jump는 매칭되는 push 없이 재사용 register를 pop하는 코드에 연결되어 있었습니다. 이로 인해 SP가 push 크기만큼 어긋나고, 재사용된 callee/scratch register가 잘못된 값을 담게 됩니다.

🔒

The escalation potential of a JIT stack desynchronization bug, and the conditions under which a misbalanced pop becomes more than a crash.

더 확인하려면 구독해 주세요

🔒

Multiple reusable audit patterns identified for JIT register-save discipline, with specific JSC compilation paths as starting points.

더 확인하려면 구독해 주세요