← All reports

[JSC] IPInt fast path for `memory.atomic.wait32`/`wait64` wraps the Memory64 effective address

Component: JSC | 370255e

Source/JavaScriptCore/llint/InPlaceInterpreter64.asm

// memory.atomic.wait32
loadq (StackValueSize * 3)[sp], t0
loadq IPInt::AtomicMemoryAccessMetadata::offset[MC], t1
- addq t1, t0
+ baddpc(t1, t0, _ipint_throw_OutOfBoundsMemoryAccess)
storeq t0, (StackValueSize * 3)[sp] # replace pointer with pointer + offset
 
// memory.atomic.wait64
loadq (StackValueSize * 3)[sp], t0
loadq IPInt::AtomicMemoryAccessMetadata::offset[MC], t1
- addq t1, t0
+ baddpc(t1, t0, _ipint_throw_OutOfBoundsMemoryAccess)
storeq t0, (StackValueSize * 3)[sp] # replace pointer with pointer + offset

IPInt는 WebKit이 사용하는 assembly 수준의 WebAssembly bytecode interpreter입니다. Memory64는 Wasm의 주소 체계를 완전한 i64로 확장하는데, 이 과정에서 runtime base pointer와 명령어에 인코딩된 static immediate offset이 모두 64비트가 됩니다. 그 결과 둘의 합이 overflow될 수 있는 조건이 생깁니다.

baddpc 매크로는 일반 Memory64 load/store fast path에서 이미 사용 중인 carry-aware add입니다. 덧셈을 수행한 뒤 carry flag가 설정되어 있으면 _ipint_throw_OutOfBoundsMemoryAccess로 분기합니다. 그런데 atomic wait fast path만은 이 Memory64 fast path 중 유일하게 해당 guard를 도입하지 않은 상태였습니다. pointer + offset을 순수 addq로 계산한 뒤 그 결과를 slow path로 그대로 넘기고 있었는데, slow path 쪽에도 자체적인 overflow check가 없었습니다.

Memory64 atomic wait — effective address computation:

Before (pointer=0xFFFF_FFFF_FFFF_FFF8, offset=8):
  addq t1, t0  →  result = 0x0000_0000_0000_0000  (carry ignored)
  storeq → stack           ↓
  slow path receives 0  →  in-bounds, no trap  ← WRONG

After:
  baddpc(t1, t0, OOB)
    ├─ no carry → result stored, continue to slow path  (correct)
    └─ carry    → jump to _ipint_throw_OutOfBoundsMemoryAccess  ← FIXED

atomic wait를 사용하는 Memory64 모듈이라면 필수 out-of-bounds trap을 우회해, wrap된 임의 주소를 대상으로 블로킹하거나 그 주소를 읽어낼 수 있습니다. 이 명령어 클래스에서는 trap 자체가 bounds enforcement의 전부이기 때문에, 이 우회는 곧 memory safety 보장이 무너지는 것과 같습니다.

Primitive는 구체적입니다. memory.atomic.wait32 offset=8을 사용하는 Memory64 모듈에서 pointer로 0xFFFF_FFFF_FFFF_FFF8n을 넘기면, 엔진이 이를 주소 0으로 인식하고 그대로 진행하게 됩니다.

이어서 점검할 방향은 두 가지입니다. 먼저 InPlaceInterpreter64.asm에 있는 다른 모든 Memory64 명령어 핸들러를 대상으로, pointer+offset 계산에서 순수 addq를 사용하는 곳이 남아 있는지 확인할 필요가 있습니다. atomic wait 핸들러는 주요 memory access fast path와 별도로 작성된 것으로 보이며, baddpc 적용이 누락된 곳이 이곳 하나만은 아닐 가능성이 있습니다. 리뷰 시에는 Memory64 경로 안에서 effective address를 만들어내는 addq가 있는데 바로 옆에 분기 대상이 없는 형태가 시각적 단서가 됩니다. 올바른 형태라면 같은 줄에 항상 OOB 핸들러 이름이 명시되어 있어야 합니다.

두 번째로는 atomic slow path 자체(ipint_slow_path_memory_atomic_wait32/wait64)를 점검해, 전달받은 주소에 대해 독립적인 bounds check를 수행하는지 확인해야 합니다. 이번 사례에서 확인된 바로는 overflow detection이 전혀 없었으며, wrap된 주소와 정상적으로 작은 pointer를 구분할 방법이 없는 상태였습니다.