[7] Wasm OMG Tail Call F32 Spill Stack OOB Write
Severity: High | Component: JSC Wasm OMG JIT — tail call FPR spilling | a73d24d
F32 tail call argument를 갖는 Wasm 모듈을 통해 도달 가능한 JIT 생성 코드에서, 항상 동일하게 4바이트 stack 인접 write가 발생합니다. 인접 영역에는 saved register나 control flow metadata가 포함될 수 있습니다. 다만 쓰여지는 값에 대한 attacker의 제어는 제한적입니다. 해당 값은 상위 FPR 비트일 가능성이 높으며, x86 SSE에서는 대체로 zero이므로 primitive의 강도는 이에 따라 낮아집니다. High로 평가하며, confidence는 0.85입니다.
OMG tail call에서 FPR 값을 spill할 때 F32와 F64 모두 double store를 수행하고 있었습니다. F32는 spill slot 경계를 벗어나는 write를 방지하기 위해 float store를 사용해야 합니다. 이 임시 stack 손상은 안정적이고 관찰 가능한 형태로 재현할 수 없기 때문에, 별도의 테스트는 추가되지 않았습니다.
Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp
} else if (src.isFPR()) {
srcOffset = allocateSpill(dstType.width());
- if (dstType.width() <= Width::Width64)
- jit.storeDouble(src.fpr(), CCallHelpers::Address(MacroAssembler::stackPointerRegister, srcOffset));
- else
- jit.storeVector(src.fpr(), CCallHelpers::Address(MacroAssembler::stackPointerRegister, srcOffset));
+ auto dst = CCallHelpers::Address(MacroAssembler::stackPointerRegister, srcOffset);
+ if (dstType == Types::F32)
+ jit.storeFloat(src.fpr(), dst);
+ else if (dstType == Types::F64)
+ jit.storeDouble(src.fpr(), dst);
+ else {
+ ASSERT(dstType == Types::V128);
+ jit.storeVector(src.fpr(), dst);
+ }
JIT 생성 tail call 코드에서 spill slot 할당과 store 명령어 간 type-width 불일치.
Patch Details
prepareForTailCallImpl에서 OMG 계층 Wasm tail call의 FPR spill 경로는, F32를 포함한 width ≤ 64비트인 모든 FPR 타입에 대해 단일 storeDouble(8바이트 store)을 사용하고 있었습니다. 수정을 통해 이를 세 가지 경우로 분리했습니다. Types::F32에는 storeFloat(4바이트), Types::F64에는 storeDouble(8바이트), Types::V128에는 storeVector(16바이트)를 각각 사용합니다. spill slot은 allocateSpill(dstType.width())를 통해 할당되는데, F32의 경우 4바이트 slot이 반환됩니다. 따라서 기존의 8바이트 storeDouble은 4바이트 slot에 쓰면서 할당 범위를 4바이트 초과하는 write를 발생시켰습니다.
Background
WebAssembly tail call(return_call / return_call_indirect)은 callee가 현재 stack frame을 그대로 재사용합니다. tail call 준비 과정에서 JIT는 활성 argument 값을 일시적으로 scratch stack 공간에 spill한 뒤, callee가 기대하는 위치로 재배치합니다. allocateSpill(width)는 지정된 width에 맞는 stack 영역을 예약하며, store 명령어는 이 width와 정확히 일치해야 합니다. storeFloat는 4바이트를, storeDouble은 8바이트를 씁니다. 현대 아키텍처의 FPR 레지스터는 64비트 또는 128비트 너비를 가지며 다양한 부동소수점 너비의 값을 담을 수 있습니다. 레지스터 너비와 메모리 store 너비는 동일하지 않습니다.
Analysis
근본 원인은 F32와 F64 모두 64비트 FPR에 들어갈 수 있다는 이유로 이를 동일하게 처리한 width 기반 dispatch에 있습니다. spill slot 할당에서는 allocateSpill(dstType.width())가 올바르게 동작하여 F32에 4바이트를 할당했습니다. 그러나 이후 store에서는 width <= Width64인 모든 FPR 값, F32 포함, 에 대해 storeDouble(8바이트)을 사용했습니다. 결과적으로 F32 tail-call argument를 spill할 때마다 할당된 slot 끝에서 4바이트를 초과하는 write가 발생했습니다. 이 초과분은 인접한 영역, 즉 다른 spill slot, saved register, 또는 stack frame 위의 return metadata를 덮어쓰게 됩니다.
주목할 점은, 같은 함수 내 바로 아래에 위치한 constant 처리 경로가 이미 F32(Width32/storeFloat)와 F64(Width64/storeDouble)를 올바르게 구분하고 있었다는 것입니다. register 경로는 잘못되어 있고 constant 경로는 올바른 이 비대칭성은, FPR 경로가 float과 double FPR 값의 의미적 차이를 고려하지 않은 채 Width 기반 조건으로만 작성되었음을 강하게 시사합니다.
Aaaa Aa Aaaa Aaa Aaaaa Aa Aaaaaaaa Aaa Aaaaa Aaa Aa Aa a a Aaa Aaa Aaaaa Aaaaa Aaaa Aa Aaaaaaa Aaa Aaaa Aa a Aaaaa Aaaa Aa Aa Aaa Aaaaaaa Aaaaaaa Aaa Aaaaaaaaaa Aaa Aaaaaa Aa Aa Aaaa Aaaaa Aaaa Aaaaa Aaaaa Aaaaaaa Aa Aaaaaaa Aaaaa Aaaaa Aaa Aaaa Aaaa Aaaaa
Aaaa Aaaa Aaaaaaaa Aaaaa Aaaa Aaaaa Aa Aa Aaaaa Aaaa Aaaa Aa a Aaaaa Aa Aaaaaa Aaaa Aa Aaaaa Aaaaaaa Aaaaa Aaaaaaa Aaaa Aaaaa Aaaaa Aaaaaaa Aa Aaaaaa Aaaaaaaa Aaa a a Aaaaa Aaa Aaa Aaa Aaaa Aaaa Aaaaaaaaa Aa Aaa Aaaa Aaa Aa Aaa Aaaa Aaaa Aaaaaa Aa Aaaa a Aaa Aaaa Aaaa
a Aaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaaaa Aaaaaaa Aa Aaaaaa Aaaaaaa Aaaaaaa Aaa Aaaaa Aaaaa Aaa Aa Aa Aaa Aaaaaaa Aaaaaaaa Aaa Aaaaaaa Aaaa Aaa Aaa Aaaa Aaaaa Aaa Aaa Aaaa Aaaa Aaa Aaaaaaa Aaaaaaa Aaaaaa
Aaaaaaaa a Aaa Aaa Aaaaa Aaa Aaaaa Aa Aaa Aaaa Aaaa a Aaaa Aaaa Aaaaa Aa Aaaaa Aaa Aa Aa Aaa Aa Aaaaaa Aaaaaa Aaa Aaaaaaaa Aaa Aaa Aaaaaaaa Aa Aaa Aaaaaa Aaa Aa Aa Aaaa Aaaa Aaa Aaaaaaa
Audit directions
a Aaa Aaaaaaaa Aaaa Aaaaaa Aa Aaaaaaaa Aaaaaa Aaaa Aaa Aaaaaaaaaaaa Aaa Aaaa Aaaaa Aaaa Aaaaa Aa Aa Aa a Aa Aaa Aaaaaaaa Aaaaa Aa Aaa Aa Aaaa Aaaaa Aaa Aaa Aaa Aaaaaaaaaa Aaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaa Aaaa Aaaa Aaaa Aaaa Aaa Aaaaa Aa Aaa Aaaaaaa Aaaaaaaaaaaaaaa Aa Aaaaaaaaa Aa Aaa Aaaaaaaaaaaaaa Aaaaaaa
a Aaa Aaa Aaaaaaaa Aa Aa Aa Aaaa Aaa Aaa Aaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaa a Aaa Aaaa Aa Aaaa Aaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaa Aaa Aa Aaa Aaaa Aaa Aaaaaaa Aaaa Aaaa Aa Aaaaaaaa Aaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaa Aaaa Aaaaaaa
a Aaa Aaa Aa Aaaa Aaaaaaaaa Aaaaaa Aa Aaaaa Aaa Aaaaaaaaaaaaaaa Aa Aa Aaaaa Aaaa Aaa Aaaa Aa Aaa Aaaa Aaa Aaaaa Aa Aaa Aaaaa Aaaaaa Aaa Aaaaaa Aaaaa Aaaaaaa Aa Aaaa Aa Aa Aaaa Aa Aaaaaaaa Aaaaaaaaaaa Aaaaaaa Aaaaaaaaa Aaaaaaaaaa Aaaaaaa