[2] Use-after-free after growing a resizable buffer on a WebAssembly memory
Severity: High | Component: JSC runtime — ArrayBuffer/JSArrayBufferView | 6b357f3
grow 이후 typed-array view가 해제된 Wasm 메모리 페이지를 역참조하는 현상이 실제로 관찰됩니다. Regression test에서는 관련 없는 allocation이 해제된 메모리를 재사용하는 과정이 확인되었으며, cached-pointer 모델을 기반으로 controlled R/W primitive로의 escalation 가능성이 confidence 0.9로 평가됩니다. 단, BoundsChecking heap layout에 대한 attacker의 제어 수준에 의해 제약됩니다.
Source/JavaScriptCore/runtime/ArrayBuffer.cpp
void ArrayBuffer::refreshAfterWasmMemoryGrow(Wasm::Memory* memory)
{
ASSERT(isWasmMemory());
+
+ void* oldData = m_contents.data();
m_contents.refreshAfterWasmMemoryGrow(memory);
+ void* newData = m_contents.data();
+ if (newData == oldData)
+ return;
+
+ for (size_t i = numberOfIncomingReferences(); i--;) {
+ JSCell* cell = incomingReferenceAt(i);
+ auto* view = dynamicDowncast<JSArrayBufferView>(cell);
+ if (view)
+ view->refreshVector(newData);
+ }
}
void ArrayBufferContents::refreshAfterWasmMemoryGrow(Wasm::Memory* memory)
{
ASSERT(isResizableNonShared());
m_memoryHandle = memory->handle();
+ m_data = memory->basePointer();
m_sizeInBytes = m_memoryHandle->size();
Source/JavaScriptCore/runtime/JSArrayBufferViewInlines.h
+inline void JSArrayBufferView::refreshVector(void* newData)
+{
+ if (hasVector()) {
+ void* newVectorPtr = static_cast<uint8_t*>(newData) + byteOffsetRaw();
+ m_vector.setWithoutBarrier(newVectorPtr);
+ }
+}
JSTests/wasm/stress/resizable-buffer-grow-view-refresh.js
+const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 });
+const buffer = memory.toResizableBuffer();
+const ta = new Uint32Array(buffer);
+ta[0] = 0xCAFEBABE;
+memory.grow(1);
+for (let i = 0; i < 100; i++) {
+ const arr = new ArrayBuffer(65536);
+ new Uint32Array(arr).fill(0xDEADBEEF);
+}
+assert.eq(ta[0], 0xCAFEBABE);
Patch Details
refreshAfterWasmMemoryGrow 계열에서 두 가지 변경이 함께 이루어졌습니다. 먼저 ArrayBufferContents::refreshAfterWasmMemoryGrow는 이제 m_data = memory->basePointer()도 함께 갱신합니다. 기존에는 m_memoryHandle과 m_sizeInBytes만 갱신하여, m_data가 이전 base 주소를 계속 가리키는 상태가 되었습니다. 한편 ArrayBuffer::refreshAfterWasmMemoryGrow는 기존 data() 포인터를 먼저 저장한 뒤 contents를 갱신하도록 변경되었습니다. base 주소가 실제로 이동한 경우에는 numberOfIncomingReferences() / incomingReferenceAt(i)를 순회하며 각 cell을 JSArrayBufferView로 downcast하고, 새로 추가된 refreshVector(newData) 메서드를 호출합니다. refreshVector는 newData + byteOffsetRaw() 계산을 통해 view의 cached m_vector를 새로 갱신합니다. hasVector() 조건이 있는 이유는 incoming-reference edge가 view detach 이후에도 유지되기 때문입니다.
재할당 가능한 backing store를 가리키는 stale cached pointer: view 생성 시 한 번 계산된 derived data pointer가 underlying buffer 재할당 시 갱신되지 않는 패턴.
Background
WebAssembly.Memory({initial, maximum})는 Wasm linear memory를 예약합니다. memory.toResizableBuffer()를 호출하면 해당 메모리가 JS ArrayBuffer로 노출되며, memory.grow()가 호출될 때 길이가 늘어납니다. BoundsChecking memory mode는 virtual-memory trap 대신 소프트웨어에서 bounds를 확인하는 Wasm 구현 방식입니다. 이 모드에서는 grow 시 새로운 backing 영역을 할당하고 내용을 복사하는 과정이 필요할 수 있어, base pointer가 변경됩니다.
JSArrayBufferView는 Uint32Array, DataView 등의 내부 C++ 객체로, buffer storage에 대한 직접 포인터(m_vector)를 캐싱합니다. 덕분에 element에 접근할 때마다 buffer->data()를 다시 조회하지 않아도 됩니다. 이 포인터는 view가 생성되는 시점에 buffer base + byteOffset으로 계산됩니다. WebKit의 GC는 각 cell에 대한 incoming references 집합을 추적합니다(numberOfIncomingReferences/incomingReferenceAt). 새롭게 추가된 순회 로직은 이를 활용하여 의존하는 view들을 탐색합니다.
Analysis
이 commit 이전에는, BoundsChecking grow로 인해 backing store가 이동하면 두 가지 포인터가 dangling 상태로 남았습니다. ArrayBufferContents::m_data는 refreshAfterWasmMemoryGrow 내부에서 새 base가 할당되지 않았고, 모든 JSArrayBufferView::m_vector 역시 재계산되지 않았습니다. refreshAfterWasmMemoryGrow가 도입된 것 자체는 개발자들이 relocation 이벤트를 인식하고 있었음을 보여줍니다. 다만 fan-out이 필요한 cached pointer 중 두 개를 누락한 셈입니다.
Aaaaaaaaaa Aaaaa a Aaa Aa Aaaaaa a Aaaaa Aaaaaaaaaaaaaaaaaaaa Aaa Aaaaaa Aaaaa Aaa Aaaa Aa Aaaaaaaaaaa Aaaaa Aaa Aaaaaaaaa Aaaaaaa Aaaaaa Aa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaa Aa Aaa Aaaaaaaaaaaaaa Aaaaaaa Aaa Aaaa Aaaaaaa Aa Aaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaaaa Aaaaa Aaaaaaaaaaa Aa Aa Aaa Aaa Aa Aaaaaa Aaaaaaaaaaaaa Aaaa Aaaa Aaaaaa
Aaaaa Aaa Aaa Aaaaa Aaaaa Aaaa Aaaaa Aaaa Aaaa Aaaaaa Aaaaaaa Aaaa Aa Aa Aaaaaaaaaaa Aaaaa Aa Aa Aa Aaa Aaaa Aaaaa Aaa Aaa Aaaa Aaaaaa Aaa Aaa Aaaaaaaaaaaaaa Aa Aaaaa Aaaa Aaaaaa Aaaaaa Aaaa Aaaa Aa Aaaaaaaaaaa Aaa Aaaaaaaaaaa Aaaaaaa Aa Aaaa a Aaaaa Aa Aaaaaaaaa Aa Aaaa Aa Aaa Aaa Aa Aaa Aaa Aaaaaaaaaa Aaaaaa Aa Aaaaaaaa Aaaaaaaaaa Aaaaaaaaaaa Aaaa Aaaa Aaaaaa Aaa Aaaaaa Aaaaaaaa Aaa Aaaa Aaaaaaaaaaaaaa Aaaa Aaaaaaaaaa Aaaa Aaaaaaa Aaa Aaa a Aaaa Aa Aaaaa
a Aaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaaa Aaa Aaaaaa Aaaaaaa Aaaaaa Aaaaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaa Aaaaaaaaa Aaa Aaa a Aa Aaaa Aaaa Aaaaa Aaa Aa Aaa Aaaaa Aaa Aaaaaaaaaa Aaaa Aaaaaaa Aaaaaa Aaaa Aaaa a Aa Aaa Aaaaaa Aaaaaaaaa Aaaaa Aaa Aaaaaaa Aaaaaaa Aaa Aaaaaa
Aaaaaaaaa Aaa Aaaaaaaaaaaaaa Aaaa Aaa Aaa Aaa Aaaa Aaaaa Aaa Aaaa Aaaaaa Aa Aaaa Aaa Aaaaa Aaaaaa Aaa Aaa Aaaaaaaaaaaaaa Aa Aaaaa Aaaaaa Aaa Aaaaaaa Aaa Aa Aaa Aaaaaaaaaaaa Aaaaa Aaaa Aa Aaaa Aaaaaaa Aaa Aaaaa Aaaaa Aaa Aa Aaaaaa Aa Aa Aa Aaaaaaaaaaa Aaaa Aa Aaa Aaaaaa Aa Aaaa Aaaaaa
🔒The lifetime and relocation implications of cached buffer pointers across WebAssembly memory growth are analyzed in depth, with a step-by-step walkthrough of the regression test's exploitation pattern.
더 확인하려면 구독해 주세요
Audit directions
a Aaaaa Aaa Aaaaaaa Aaaaaa Aaaa Aaaaaa Aaaaaaa Aaaaaaaaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaa Aaaaa a Aaaaaaaaaaaa a Aa Aaaa Aa Aaa Aaaa Aa Aaa Aa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaa Aaaa Aaaaaaaa Aaa Aaaaaaa Aaaaa Aa Aaaa Aaaaaaa Aaaaaaaaaaaa Aaaa Aaa Aaaa Aa Aaaaa a Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaa Aaaaa Aaaa Aaaa
a Aaaa Aaaaaaa Aaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaa Aaa Aaaaaa Aaaaaa Aaaaaaaa Aaa Aa Aaa Aaaaaa Aaaaaa Aaaa Aaaaa Aaa Aaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa a Aa Aa Aaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaa a Aaa Aaaaa Aaaa Aaa Aa Aaaaaaa Aaa Aaa Aaa Aaaa Aaa Aaa Aaaaa
a Aaaaaaaaaaaaaaaa Aaaa Aaaaaaa Aaa Aaaa a Aaaaaaaaaa Aa Aaaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaa Aaaa Aaaaaa Aaaaa Aaaa Aa Aaa Aaaa Aaaa Aaaa Aaa Aaaa Aaaaaaaaaa Aaaaaaaaaaaaaaaaaaaa a a Aa Aaa Aaaa Aaa Aa Aaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaaaaaa Aaaa Aaa Aa Aaaaaa
a Aaaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaa a Aaa Aa Aaaa Aaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaa Aaa Aaaa Aaaaaa Aaaaaaaa Aaaa a Aaaaaaaaaaaa Aaaaa Aaa Aaa Aaaa Aaa Aaaaaa Aa Aa Aaaa Aa Aaaaaaaaaaa Aaaa Aaaaaaaa Aaaaa Aaaa Aaaa Aaa Aaa Aaaaaaa Aaaaaa Aaaa Aaaaa Aaaa Aaaa Aaaa Aaa Aaaa Aaaaa
🔒Multiple reusable audit patterns identified for cached-pointer/relocation bugs across JSC, with concrete starting points spanning the typed-array, Wasm memory, and JIT inline-cache surfaces.
더 확인하려면 구독해 주세요