[6] LLInt stack overflow guard bypass in arity fixup
Severity: High | Component: JSC LLInt | 2a07f26
임의의 웹 페이지에서 조작된 JavaScript를 통해 soft stack limit을 초과하는 stack write를 유발할 수 있다는 점에서 High로 평가됩니다. 그 메커니즘(sp 대신 cfr을 기준으로 overflow 경계를 계산하여 local frame 전체 크기를 누락)은 세 파일에 걸친 diff를 통해 confidence 0.92로 확인됩니다. JIT 경로가 이미 올바르게 구현되어 있었다는 점은 이것이 실제 safety gap임을 뒷받침합니다.
LLInt arity fixup의 stack overflow check가 sp(stack pointer) 대신 cfr(call frame register)을 기준으로 계산되었습니다. Arity fixup은 callee의 local frame이 설정된 이후에 실행되므로, 이 시점에서 sp는 이미 cfr보다 훨씬 아래에 위치합니다. 이번 fix는 C++ slow path와 assembly fast path 양쪽에서 기준값을 sp로 변경했습니다.
Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
- int padding = numberOfStackPaddingSlotsWithExtraSlots(newCodeBlock, callFrame->argumentCountIncludingThis());
- Register* newStack = callFrame->registers() - WTF::roundUpToMultipleOf(stackAlignmentRegisters(), padding);
- if (!vm.ensureJSStackCapacityFor(newStack)) [[unlikely]]
+ int slotsToAdd = numberOfStackPaddingSlotsWithExtraSlots(newCodeBlock, callFrame->argumentCountIncludingThis());
+ Register* newStackPointer = callFrame->registers() - WTF::roundUpToMultipleOf(stackAlignmentRegisters(), slotsToAdd) - newCodeBlock->numCalleeLocals() - maxFrameExtentForSlowPathCallInRegisters;
+ if (!vm.ensureJSStackCapacityFor(newStackPointer)) [[unlikely]]
return -1;
- return padding;
+ return slotsToAdd;
Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
- subp cfr, t3, t5
+ subp sp, t3, t5
JSTests/stress/stack-overflow-llint-large-params-and-large-locals.js
+ var args = [];
+ for (var i = 0; i < 20; i++) {
+ args[i] = args.toLocaleString() + "x" + i;
+ }
+ var min = new Function(args, "return Math.min(" + args.join(",") + ");");
+ shouldThrow(() => {
+ min(0);
+ }, `RangeError: Maximum call stack size exceeded.`);
Patch Details
세 파일이 변경되었습니다. LLIntSlowPaths.cpp에서 arityCheckFor()는 이제 계산된 새 stack pointer를 vm.ensureJSStackCapacityFor()에 전달하기 전에 newCodeBlock->numCalleeLocals()와 maxFrameExtentForSlowPathCallInRegisters를 차감합니다. LowLevelInterpreter64.asm과 LowLevelInterpreter32_64.asm에서는 inline arity check 매크로의 subp cfr, t3, t5가 subp sp, t3, t5로 변경되어, overflow 비교에 frame pointer 대신 실제 stack pointer를 사용하게 되었습니다.
Before: After:
cfr ──────────── (frame pointer) cfr ──────────── (frame pointer)
│ │
│ numCalleeLocals │ numCalleeLocals
│ + maxFrameExtent │ + maxFrameExtent
│ │
sp ──────────── (stack pointer) sp ──────────── (stack pointer)
│ │
│ arity padding (slotsToAdd) │ arity padding (slotsToAdd)
│ │
└── old check: cfr - slotsToAdd └── new check: sp - slotsToAdd
(WRONG: missed frame gap) (CORRECT: from actual sp)
Background
JavaScript 함수가 N개의 매개변수를 선언했지만 N개보다 적은 인수로 호출될 경우, JSC는 callee가 완전한 인수 벡터를 볼 수 있도록 call frame을 undefined 슬롯으로 채워야 합니다. 이 과정을 "arity fixup"이라고 합니다. LLInt에서는 functionArityCheck 매크로(assembly)와 arityCheckFor slow path(C++)에서 이 작업이 수행됩니다.
JSC의 calling convention에서 cfr(call frame register)은 논리적 call frame 헤더의 상단을 가리키고, sp(stack pointer)는 모든 callee local과 scratch space 아래, 즉 할당된 frame의 실제 하단을 가리킵니다. Frame 설정 후에는 sp = cfr - numCalleeLocals - maxFrameExtent 관계가 성립합니다. JSC는 VM 단위로 soft stack limit을 유지하며, stack 확장 전에 예상 stack pointer가 이 limit을 초과하는지 검사합니다. 초과할 경우 RangeError(stack overflow)가 발생하게 됩니다.
Analysis
LLInt arity fixup 과정에서 stack pointer 대신 frame pointer를 기준으로 stack overflow guard를 계산하여, 이미 할당된 local frame 공간이 누락된 패턴.
cfr을 기준으로 overflow 경계를 계산했기 때문에, check는 남은 stack 용량을 local frame 크기(numCalleeLocals + maxFrameExtentForSlowPathCallInRegisters)만큼 과대평가했습니다. 여기서 두 조건이 겹치는 함수를 고려할 수 있습니다. 매개변수가 많으면 caller가 더 적은 인수를 전달하여 arity fixup이 유발되고, 지역 변수가 많으면 sp가 cfr보다 훨씬 아래에 위치하게 됩니다. 이 두 조건이 동시에 성립할 경우, arity fixup 이후 실제 stack pointer가 soft stack limit을 초과함에도 check가 통과하는 상황이 발생했습니다. Commit 메시지는 JIT 경로가 이미 올바르게 구현되어 있었음을 확인해 주므로, 이는 LLInt에만 존재하던 버그에 해당합니다.
Aaa Aaaa Aaaa Aaaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaa Aaaaa Aaaa Aaa Aa Aaaa Aaaa Aaa Aaa Aaa Aaaaa Aaaa Aaaaaaaaaa Aa Aaa Aaaa Aaaaa Aaaaaa Aaaaaa Aaa Aaaa a Aaaa Aaaaa Aaaaaaa Aaa Aaaa Aaaaa Aaaaaa Aaaa Aaaaaaaa Aaa Aaaa Aaaaaaaaaaaa Aaaaaaa Aaaa Aaaaa Aaaa Aaaaaaaaaa Aaaa Aaaaaa
Aaaaa Aaaaaa Aaaaaa Aa Aaaa Aaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaaaa Aaaaaa Aaaa Aaaa Aa Aa Aa Aaaaaaaaaaaaaaaa a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aa Aa Aaaaaa Aaaa Aaaaaa Aaaa Aaa Aaaaaaa Aaaaa Aa Aaa Aaa Aaaa Aaaa Aaaa Aaaaa Aa Aa Aaa Aaa a Aaaaa Aaaa Aa Aaaaaaaaaaa Aaaaaa Aaaaaaa Aaa Aaaaaaaaaaa Aaaa Aaa Aaa Aaaaaa Aa Aa Aaaaaa Aaaaa Aaaaa Aaaaaaaa Aaaaaa Aaaaaaaa Aa Aaaaaaaaaaaa Aaaaaaaaa Aaa Aaaa Aaa Aaaaaaaa Aaa Aaaa Aaaaaaaaaaaa Aaaaaa Aaaaaaaaaa Aaa a Aaaaa
a Aaaaaaaaaaaaaa Aaaaaaaa Aaaaaaaa Aaaaa Aaaa Aaaaaa Aaaaaaa Aaaaaaa Aaaa Aaaaa Aaaaaa Aaa Aa Aaaa Aaaaaa Aa Aaaaa Aaaaaaaaa Aa Aa Aaaaaaa Aaa Aaaa Aaaa Aa a Aaaaa a Aaa Aaaa Aaaaaaaaaaa Aaa a Aaaaaa
Aaaaaa Aaa Aaa Aaa Aaaaaa Aaa Aaaaaa Aaaaaaaaaa Aaa Aaa a Aaaaa Aaaaa Aaaaaaaaa Aaa Aaaa Aaaaa Aaaa Aaa Aaaaa Aaaaaa Aaa Aaaaa Aaaa Aaaa Aaa Aaaaaa Aaaa a Aa Aaaa Aaaa Aaa Aaa Aa Aaaaa
🔒Detailed analysis of how the cfr/sp divergence creates a precise stack overwrite window, and what memory regions fall within the corruption range
더 확인하려면 구독해 주세요
Audit directions
a Aaa Aaaa Aa Aaaaaa Aaaa Aaaa Aa Aaa Aaaaa Aaa Aaaa Aaaaa Aaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa a Aaaaaaaaaaaaaaaaaaaaaaa Aaaa Aa Aaaaa Aaa Aaaaaa a Aaa Aa Aaaaa Aaaaaaa Aaaa Aaaaa Aaaa Aaaaaaa Aa Aaaaa Aaaaa Aaa Aaa Aaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaa
a Aaa Aaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaa Aaaaaa Aaaa Aaaaaa Aaaa Aaaa Aaaa Aaa Aa Aaaaaa Aaa Aaaa Aaaaaa Aa Aaaaa Aa a Aaaaa Aa Aaaaa Aaa Aaa Aaa Aaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaa Aaa a Aaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaaaa Aaa Aa Aaaa Aaaaa Aaaaaaa Aaa Aaa Aaaaaa Aaaaaa Aaaa Aaaaaaaaaaaaaa Aaaaaa
a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaa Aa Aaaa Aaaa Aaaaa Aaaaaa a Aaa Aaaaaaaaa Aaa Aaa Aaa Aaaaaaa Aaaaaa Aaaaa Aaaaa Aaaaaaaa Aaa Aaa Aaaaa Aaaa Aaaaaa Aaa Aa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaaa a Aa Aaa Aaaaaaaa Aaaaaa Aa Aaaaa Aaaaaa
Aaaaaaaaa Aa Aa Aa a Aa Aaaaa Aa Aaaa Aaaaaaaaaa Aaa Aaaaaaaaaa Aa Aaaa Aa Aa a a Aaaaaa Aa Aaaa Aaa Aaa Aaaaa Aaaaaa Aaaaa Aaa Aaaaa Aa Aaaaaaaaaaa Aaaaaaaa Aaa Aaaa Aaaaaaaaaa Aaa a Aa Aaaa Aaaaaa
🔒Multiple audit patterns identified for LLInt/JIT safety-check parity, with specific search targets across stack management code
더 확인하려면 구독해 주세요