[JSC] Fix memory.init's overflow check
Component: JSC WebAssembly | de45b9b
WebAssembly의 memory64 proposal은 linear memory address를 32비트가 아닌 64비트로 다룰 수 있게 합니다. memory.init은 data segment의 바이트를 linear memory로 복사하는 연산이며, Memory::init은 memory 크기와 비교하기 전에 offset + length에 대한 overflow-safe check로 이를 보호합니다. 문제는 memory64 하에서 destination offset이 2^32를 넘어설 수 있다는 점입니다. 이 check가 64비트 연산으로 수행되지 않으면, 정상적인 호출에서도 오작동하게 됩니다.
Source/JavaScriptCore/wasm/WasmMemory.cpp
JSTests/wasm/wasm/stress/memory64-bulk-memory.js
이 commit은 overflow check를 uint64_t로 확장하는 동시에, 이제 bulk-memory 연산에서 더 이상 도달하지 않는 Memory::fill / Memory::copy의 dead code를 제거합니다. 해당 연산들은 이제 Wasm::memoryFill / Wasm::memoryCopy를 거치기 때문입니다. 새로 추가된 테스트는 memory.init의 operand type을 명확히 고정합니다. Destination offset만 memory64에서 i64로 확장되고, source offset과 length는 i32로 유지됩니다. Data segment의 크기는 memory의 addressing mode와 무관하게 module encoding 단계에서 32비트로 제한되기 때문입니다 (반면 length가 함께 확장되는 memory.fill/memory.copy와는 다른 지점입니다).
Significance
기존 32비트 check는 지나치게 관대했던 것이 아니라 오히려 지나치게 엄격했습니다. 4 GiB 이상의 destination을 대상으로 하는 정상적인 memory.init 호출이 잘못 거부되고 있었습니다. 이제 uint64_t check는 memory의 실제 address type과 일치하면서도, offset + length의 진짜 64비트 wraparound는 그대로 잡아냅니다. 이번 변경으로 새로운 OOB가 생기거나 기존 OOB가 사라지는 것은 아닙니다. 이는 security fix가 아니라 correctness fix에 해당합니다. 제거된 Memory::fill/Memory::copy 역시 버그가 있던 코드는 아니었습니다. 이미 addressType().is64Bit() ? sumOverflows<uint64_t> : sumOverflows<uint32_t> 형태로 overflow width를 선택하고 있었고, 단지 다른 구현으로 대체되면서 불필요해졌을 뿐입니다.
Audit directions
앞으로 살펴봐야 할 질문은, 살아남은 모든 bulk-memory 및 bulk-table 경로가 overflow width를 고정값이 아니라 address type으로부터 도출하고 있는가입니다. 좁게 보면, 삭제된 구현을 대체한 Wasm::memoryFill과 Wasm::memoryCopy가 address-type에 따른 width를 끝까지 일관되게 유지하는지 확인할 필요가 있습니다. Memory::init에도 정확히 같은 수정이 필요했기 때문입니다. 범위를 넓히면, table.init과 table.copy도 점검 대상입니다. table64 proposal이 다른 index space에서 동일한 32-vs-64 width 선택 문제를 만들어내기 때문입니다. 가장 넓게 보면, overflow guard의 width가 operand의 선언된 type이 아니라 template parameter로 고정되어 있는 모든 지점이 후보가 됩니다. 더 넓은 addressing mode가 도입되는 순간 drift가 발생할 수 있기 때문입니다. 점검 시에는 parameter가 이미 uint64_t인 함수 안에 sumOverflows<uint32_t> (또는 이에 준하는) 고정 width literal이 남아 있는지를 확인하면 됩니다.