[1] ANGLE Metal: stale texture views during size transitions
High로 평가된 근거를 정리하면 다음과 같습니다. diff는 GPU/renderer process에서 web content를 통해 유발 가능한, 매번 재현 가능한 Metal validation abort를 수정하고, TextureMtl::redefineImage의 optimistic stale-view reuse 분기도 제거합니다. Metal driver에서 validation이 비활성화된 경로에서는 동일한 흐름이 stale region descriptor로 resize된 storage에 업로드를 요청하게 되며, destination mip level을 backing하는 Metal heap allocation 내에서 bounded OOB pixel-data write가 발생할 가능성이 있습니다.
Texture의 base level 크기가 변경될 때(예: 128x128에서 256x256으로), native storage는 재생성되지만 mTexImageDefs에는 이전 크기를 기반으로 한 mipmap view가 그대로 남을 수 있습니다. 이 stale view에 업로드를 시도하면 Metal validation이 (origin.x + size.width)(128) must be <= width(64) 오류를 발생시킵니다. 패치는 generateMipmap()과 redefineImage()에서 mTexImageDefs 항목을 초기화하여 view가 항상 현재 storage의 올바른 크기로 재생성되도록 보장합니다.
Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/TextureMtl.mm
Source/ThirdParty/ANGLE/src/tests/gl_tests/MipmapTest.cpp
Patch Details
generateMipmap은 새로 할당된 mNativeTextureStorage의 모든 face와 level을 순회하여 대응하는 mTexImageDefs[face][level] 슬롯을 초기화하도록 재구성되었습니다. 이후 contextMtl->invalidateCurrentTextures()를 호출해 후속 draw 시 sampler slot이 다시 바인딩됩니다.
redefineImage는 제어 흐름이 새롭게 작성되었습니다. 먼저 imageDef 슬롯을 미리 계산하고, 기존 storage의 format과 size가 모두 일치하면 즉시 반환하는 early-return 구조로 바뀌었습니다. 일치하지 않으면 deallocateNativeStorage(/*keepImages=*/true)를 호출하고, 이어서 imageDef = {}를 무조건 초기화한 뒤 새로운 mtl::TextureRef를 생성합니다.
내부 GetTextureImageType 헬퍼와 optimistic reuse 분기(imageDef.image && imageWithinNativeStorageLevels)는 제거되었습니다. ensureNativeStorageCreated 내부의 불필요한 imageToTransfer = nullptr 코드는 mTexImageDefs[face][imageMipLevel] = {}로 교체되었고, 128 → 256 → 128 크기 전환 및 resize 후 mipmap 재생성을 검증하는 regression test 두 건이 추가되었습니다.
Backing storage 재할당 시 파생 view metadata가 무효화되지 않아, stale 크기 기반으로 새 storage에 작업이 전달되는 패턴.
Background
ANGLE은 WebKit이 WebGL 구현에 사용하는 OpenGL ES 번역 레이어로, Apple 플랫폼에서는 Metal backend를 대상으로 합니다. TextureMtl은 GLES texture를 지원하는 ANGLE Metal 객체이며, texture 상태에 대한 두 가지 설명을 병렬로 관리합니다.
mNativeTextureStorage는 texture가 완성된 시점에 전체 mipmap chain을 나타내는 단일 Metal MTLTexture입니다. mTexImageDefs[face][level]는 mipmap chain이 구성되기 전후로 GLES image 작업을 처리하기 위한 per-image ImageDefinitionMtl 값의 캐시입니다. 이 값은 {mtl::TextureRef view, formatID} 쌍으로 구성됩니다.
glTexImage2D(level=0, w, h) 호출은 base level을 resize할 수 있으며, 이에 따라 mNativeTextureStorage가 재생성되어 후속 level들이 새로운 chain을 수용합니다. glGenerateMipmap은 ANGLE에 전체 chain의 할당 및 채우기를 요청합니다. redefineImage는 glTexImage* 호출 시 per-level image를 (재)할당하기 위해 진입하는 ANGLE entry point입니다. mtl::TextureRef는 id<MTLTexture>에 대한 reference-counted wrapper로, imageDef = {}를 할당하면 보유한 reference가 해제됩니다. Metal의 replaceRegion:mipmapLevel:withBytes:bytesPerRow:는 origin + size가 해당 mip level의 실제 크기 내에 들어와야 함을 강제합니다. validation layer가 활성화된 상태에서 이 조건이 위반되면 validation 오류가 발생합니다.
Analysis
이 버그의 유형은 stale cached metadata에 해당합니다. TextureMtl은 동일한 texture에 대해 두 가지 설명을 병렬로 유지하는데, mNativeTextureStorage가 변경될 때마다 이에 의존하는 모든 mTexImageDefs 항목을 무효화해야 한다는 불변식이 일부 코드 경로에서 지켜지지 않았습니다.
구체적으로, level 0에서 새로운 크기로 glTexImage2D를 호출하면 storage 재생성이 유발됩니다. 이때 mTexImageDefs의 non-base level에 해당하는 per-level view는 이전 storage에서 넘어온 채로 남아, 이전 chain의 크기를 그대로 반영합니다. 이 stale view를 참조하는 후속 작업에서 문제가 발생합니다. 가장 직접적인 경로는 redefineImage의 optimistic reuse 분기인데, imageDef.image && imageWithinNativeStorageLevels 조건으로 보호되어 있습니다. 이 분기에 진입하면 이전 view의 크기 정보를 기반으로 새 per-level storage에 Metal 업로드가 요청됩니다.
MipmapTest.cpp에 추가된 재현 케이스는 이 경로를 정확히 추적합니다. 테스트는 128x128 base level을 할당하고 generateMipmap을 호출하여 level 1..N을 64, 32, 16 순서로 생성합니다. 이후 level 0에서 크기 256으로 glTexImage2D를 호출하면 mNativeTextureStorage 재생성이 강제됩니다. 새 chain의 level 1은 128x128이 되지만, 기존 mTexImageDefs[0][1] 항목은 이전 storage의 64x64 view를 여전히 기술합니다. 이 상태에서 level 1에 크기 128로 glTexImage2D를 호출하면 redefineImage가 실행되고 optimistic reuse 분기에 진입합니다. 결과적으로 stale한 64 폭의 view 메타데이터로 replaceRegion이 새 128 폭 level에 요청되며, Metal validation이 앞서 인용한 오류와 함께 이 호출을 거부합니다.
validation이 활성화된 빌드에서는 WebGL을 지원하는 모든 페이지에서 도달 가능한 매번 재현 가능한 renderer 또는 GPU process abort가 발생합니다. web content에 대한 단순한 DoS 공격 표면에 해당합니다. Metal driver에서 validation이 비활성화된 경로에서는 동일한 명령이 검사 없이 실행됩니다.
이 버그를 crash로 이어지게 한 bounds check는 validation layer의 속성일 뿐, driver 수준의 불변식이 아닙니다. validation이 없는 경로에서는 업로드 영역이 destination level의 실제 크기를 초과하게 됩니다. 결과적으로 Metal heap allocation 내 mip level의 정상 범위를 벗어난 위치에 공격자가 제공한 pixel data가 기록될 가능성이 있습니다.
공격자는 texImage2D data를 통해 source buffer의 내용을 제어합니다. 또한 resize 전후 크기 선택으로 region descriptor의 초과량도 조정할 수 있습니다. 따라서 잠재적인 primitive는 GPU/WebContent process 내에서 크기가 제한된 OOB pixel-data write에 해당합니다.
이 vulnerability는 WebGL content와 GPU process 사이의 메모리 안전성 경계를 약화시킵니다. HTML/WebGL 신뢰 모델은 GLES texture state 전환이 비정상적인 driver 수준 명령을 생성하지 않는다는 전제에 기반합니다. 그러나 파생된 캐시 메타데이터가 authoritative backing store보다 오래 유지될 때마다 이 전제가 위반됩니다.
OOB write에 성공하더라도 공격자는 GPU/WebContent sandbox 내부에 머물게 됩니다. 커널 또는 다른 process에 도달하려면 별도의 sandbox escape이 필요합니다.
Audit directions
-
Parallel-cache invalidation gaps where a 'fast' per-element descriptor cache outlives its authoritative backing store.
Source/ThirdParty/ANGLE/src/libANGLE/renderer/metal/TextureMtl.mm에서mNativeTextureStorage를 변경하는(할당, 해제, 재생성) 모든 위치를 점검합니다. 각각이mTexImageDefs전체 초기화와 쌍을 이루는지 확인합니다.deallocateNativeStorage,ensureNativeStorageCreated,setImageImpl,copyImage*,setStorage*, 그리고Texture::Make2DTexture/Make3DTexture/Make2DArrayTexture를 호출하는 경로에서 시작합니다.mNativeTextureStorage에 대한 할당을 검색하여 도달 가능한 모든 분기에서mTexImageDefs가 초기화되는지 확인합니다. -
GPU-driver validation as the only barrier between a logic bug in a translation layer and an OOB region operation. 다른 ANGLE backend(
renderer/vulkan/,renderer/d3d/,renderer/gl/)에서redefineImage/generateMipmap주변의 유사한 level-cache 무효화를 점검합니다. 이번 버그는 Metal validation 오류를 통해 발견되었습니다. 동등한 Vulkan layer(VK_LAYER_KHRONOS_validation)와 D3D debug layer도 유사한 문제를 감지할 수 있지만, 활성화된 경우에만 해당합니다. 캐시된 extent로 발행되는replaceRegion동등 호출(vkCmdCopyBufferToImage,ID3D11DeviceContext::UpdateSubresource)을 검색합니다. -
Optimistic reuse of cached views/handles guarded by a stale 'shape matches' predicate. 제거된
imageDef.image && imageWithinNativeStorageLevels분기가 정확히 이 패턴에 해당합니다. ANGLE에서if (cached.handle && shapeMatches) { ASSERT(...); /* skip rebuild */ }형태의 유사 구조를 검색합니다.shapeMatches조건이 저장된 복사본이 아닌 실시간 backing store로부터 계산되는지 확인합니다.TextureMtl::ensureImageCreated,TextureMtl::getImageDefinition,keepImages=true로 해제를 호출하는 모든 지점에서 시작합니다. -
GLES state transitions that re-allocate backing storage without sweeping every dependent cache.
glTexStorage*,glCopyTexImage*,glCopyTexSubImage*, level==0 resize를 포함한glTexImage*, EGLImage/IOSurface rebinding 경로에서 동일한 유형의 stale-view 버그를 점검합니다.TextureMtl::releaseTexImage,TextureMtl::bindTexImage, surface-attachment 경로가 동등한mTexImageDefs전체 초기화를 수행하는지 확인합니다.
Note: validation이 비활성화된 Metal driver 경로의 정확한 동작 등 일부 구현 세부 사항은 소스에서 직접 확인되지 않으며, diff와 인용된 validation 오류로부터 추론된 내용입니다. 핵심 실행 흐름과 crash 유발 과정은 패치와 추가된 regression test에 의해 일관되게 뒷받침됩니다.