[8] ANGLE Metal BufferPool Integer Truncation
Severity: Medium | Component: ANGLE Metal backend — BufferPool | 4369d90
관찰 가능한 영향이 4GB를 초과한 시점에서의 silent offset truncation이고, 이로 인해 GPU 측 OOB access가 발생하기 때문에 Medium으로 평가되었습니다. 다만 exploit을 위해서는 WebGL content로부터 단일 BufferPool에서 누적 allocation이 4GB를 초과하도록 유발해야 합니다. 고용량 메모리 시스템에서는 이론적으로 가능하지만 간단히 도달 가능한 수준은 아니며, corruption은 Metal buffer의 virtual address 범위 안으로 제한됩니다.
BufferPool의 Metal backend integer overflow vulnerability를 수정하였습니다. 기존에는 BufferPool이 offset 추적에 uint32_t를 사용하고 있어, 4GB를 초과하는 시점에서 silent truncation이 발생했습니다. mNextAllocationOffset과 mLastFlushOffset을 size_t로 변경하고, truncating cast를 제거하였습니다. 또한 BufferPool 클래스를 검증하는 ANGLE white box test가 추가되었습니다.
Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/mtl_buffer_pool.h
Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/mtl_buffer_pool.mm
Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/mtl_command_buffer.h
Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/mtl_command_buffer.mm
GPU buffer offset 추적 과정에서 size_t에서 uint32_t로의 integer truncation이 발생하여, 4GB 경계를 초과하면 silent wraparound가 일어났습니다.
Patch Details
BufferPool의 mNextAllocationOffset과 mLastFlushOffset이 uint32_t에서 size_t로 확장되었습니다. 이 변경은 전체 offset pipeline으로 전파되었는데, 구체적으로는 다음 항목들이 수정되었습니다. RenderCommandEncoderShaderStates::bufferOffsets 배열, RenderCommandEncoder 및 ComputeCommandEncoder의 모든 setBuffer/setVertexBuffer/setFragmentBuffer 메서드 시그니처, SetComputeOrVertexBuffer helper, ProgramExecutableMtl의 mLegalizedOffsetedUniformBuffers pair 타입, VertexArrayMtl.mm의 vertex array offset, 그리고 직렬화된 offset 명령을 처리하는 IntermediateCommandStream의 fetch 타입입니다. offset 값에 대한 모든 static_cast<uint32_t>(...) truncating cast도 제거되었습니다. white-box test에서는 4GB에 근접한 buffer를 할당하고, 두 번째 allocation의 offset이 truncation되지 않는지 검증합니다.
Background
ANGLE은 Apple 플랫폼에서 OpenGL ES를 Metal로 변환하는 WebKit의 translation layer로, WebGL 구현에 사용됩니다. BufferPool은 대형 Metal buffer에서 영역을 분할하는 sub-allocator입니다. allocate()를 호출할 때마다 내부 offset인 mNextAllocationOffset이 증가하고, 그 결과로 얻은 offset이 Metal의 setVertexBuffer:offset:atIndex: 등의 API에 전달되어 GPU에 shader data의 읽기/쓰기 위치를 알려줍니다. Metal buffer offset의 타입은 NSUInteger로, 최신 Apple 플랫폼에서는 64비트입니다. Integer truncation은 더 넓은 타입의 값이 더 좁은 타입에 저장될 때 발생합니다. uint32_t의 경우 0xFFFFFFFF(4GB)를 초과하는 값의 상위 비트가 silent하게 손실됩니다.
Analysis
근본 원인은 구조적인 문제였습니다. BufferPool이 allocation offset을 uint32_t로 추적하는 동시에, 하위 offset pipeline 전체 — command encoder state, command stream 직렬화, uniform buffer offset 저장, vertex buffer offset 로컬 변수 — 도 동일하게 uint32_t를 사용하고 있었습니다. 단일 buffer 내 누적 allocation이 4GB를 초과하면, unsigned integer truncation에 의해 offset이 silent하게 wrap되었습니다. 이렇게 truncation된 offset이 Metal API 호출에 전달되면서, GPU가 buffer data를 잘못된 위치에 바인딩하게 되었습니다.
한편 원래 코드에는 출력 시점에 static_cast<size_t>(mNextAllocationOffset)가 있었습니다. 내부에서 truncation이 이미 일어난 이후에 size_t로 다시 넓히는 구조로, 하위 호출자에게 데이터 손실을 숨기는 효과가 있었습니다. 이른바 '관례적 truncation' 패턴입니다. offset을 uint32_t로 정의한 초기 설계 결정이 모든 소비자에게 명시적인 static_cast<uint32_t>() 캐스트 형태로 전파된 사례로, integer width 버그가 어떻게 구조적 문제로 확대되는지를 잘 보여줍니다.