[1] ANGLE Metal: stale texture views during size transitions
Severity: High | Component: ANGLE Metal backend | baeadcd
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
ANGLE_TRY(ensureNativeStorageCreated(context, false));
+ int numCubeFaces = static_cast<int>(mNativeTextureStorage->cubeFaces());
+ for (int face = 0; face < numCubeFaces; ++face)
+ {
+ const GLuint mips = mNativeTextureStorage->mipmapLevels();
+ for (mtl::MipmapNativeLevel mip = mtl::kZeroNativeMipLevel; mip.get() < mips; ++mip)
+ {
+ GLuint level = mNativeTextureStorage->getGLLevel(mip);
+ mTexImageDefs[face][level] = {};
+ }
+ }
ContextMtl *contextMtl = mtl::GetImpl(context);
+ contextMtl->invalidateCurrentTextures();
...
- bool imageWithinNativeStorageLevels = false;
- if (mNativeTextureStorage && mNativeTextureStorage->isGLLevelSupported(index.getLevelIndex()))
+ GLuint cubeFaceOrZero = GetImageCubeFaceIndexOrZeroFrom(index);
+ GLuint glLevel = index.getLevelIndex();
+ ImageDefinitionMtl &imageDef = mTexImageDefs[cubeFaceOrZero][glLevel];
+ if (mNativeTextureStorage && mNativeTextureStorage->isGLLevelSupported(glLevel))
{
- imageWithinNativeStorageLevels = true;
- GLuint glLevel = index.getLevelIndex();
- if (mNativeTextureStorage->getFormat() != mtlFormat ||
- size != mNativeTextureStorage->size(glLevel))
+ if (mNativeTextureStorage->getFormat() == mtlFormat && size == mNativeTextureStorage->size(glLevel))
{
- deallocateNativeStorage(/*keepImages=*/true);
+ return angle::Result::Continue;
}
+ deallocateNativeStorage(/*keepImages=*/true);
}
+ imageDef = {};
...
- if (mNativeTextureStorage && imageDef.image && imageWithinNativeStorageLevels)
- {
- ASSERT(...);
- }
- else
- {
- imageDef.formatID = mtlFormat.intendedFormatId;
- ...
- }
+ mtl::TextureRef image;
+ imageDef = {image, mtlFormat.intendedFormatId};
Source/ThirdParty/ANGLE/src/tests/gl_tests/MipmapTest.cpp
+TEST_P(MipmapTest, UploadAfterSizeTransition)
+{
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 128, 128, ...);
+ glGenerateMipmap(GL_TEXTURE_2D);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, ...);
+ glGenerateMipmap(GL_TEXTURE_2D);
+ glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 128, 128, ..., blueData.data());
+}
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 업로드가 요청됩니다.
Aaaaaaaaaaaaaaaaa Aaa Aa Aaaa a Aaa Aaa Aaaaaa Aaaa Aaaaaaa Aaaa Aaaaaa Aaaa Aaaaaaaaaaaaaaaaa Aaaa Aaaaa Aaaaa Aaa Aaa Aa Aaa Aaaaaa Aa Aaaaa Aaa Aa Aaaaa Aaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaa a Aaaaaa Aaaaa Aa Aaaaaaaa Aaaa Aa Aaaaaaaaaaaaaaaaaaaaa Aaa Aa Aaaaaaaa Aaaaa Aaaaa Aaa Aaaaaa a Aaaa Aaaaa Aa Aa Aaaa Aaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaa Aaaaa Aaa Aaaaaa Aaaaa Aaaaaa Aa Aa Aaaa Aaaaaa Aaaaaaaaaaaaaaaa a Aaa a Aaaaaa Aaaaa Aaaaa Aaaaaaaaaaa Aa Aaa Aaa Aa a Aaa Aaaaaa
Aaaaaaaaaaa Aaaa Aaaaa Aaaaaa Aaaa Aa Aaaaa Aa Aaa Aa Aa Aaa Aaaaaaaa Aa Aaa Aaaaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaa Aa Aaa Aaa Aa Aaa Aaaaaa Aaaaa Aaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaa Aaa Aaa Aa Aa Aaaaaa
a Aaa Aaaaaa Aaaa a Aaaaaa Aaaaaa Aaaaaaaaaa Aaaaaa Aaa Aa Aaaaaa Aaa Aaaa Aaaaa Aaaaaaaaaaa Aa Aaaaa Aaa Aaa Aaaaaaaaaaa Aaaaaa Aa Aaa Aaaa Aaaa Aaaaa Aaaaa Aaaa Aaaaaaaaaa a Aaa Aaaaaa Aa Aaa Aaa Aaa Aaaa Aaa Aaaaa Aaaaa Aaa Aaaa Aaaaa
Aaaa Aaaaaaaaaaaa Aaaaa Aa Aaaaaa Aaaaaaa Aaa Aaaaaa Aa Aaaaaa Aa Aa Aaaa Aaaaaa Aaaaaaaaaaa Aaaa Aaa a Aaaaa Aaa Aaaa Aaaaaaaaaa Aaaaaaaaaaaaaa Aaaaaaa Aaa Aaa Aaa Aaa Aaaaaaaaaa Aaaaaa Aaaaaa
a Aaaaaaaaaaaaaa Aaaaa Aaaaaaaa Aaa Aaaaaaa Aaa Aaa Aaa Aaa Aaaaaaa Aaaaaaaaaa Aa Aaa Aaaa Aaaaaaa Aaaaa Aaa Aaaaa Aaaaaa Aa Aaa Aaaa Aaaa Aaa Aaaaaa Aaa Aaa Aa Aaaaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaaa Aa Aaa Aaa a Aaa Aaaaaa
Aaa Aaaaaa Aaaaaa Aaaa Aaaaaaaaaaaaaa Aaaaaaa Aaa Aaa Aaaa Aa Aa Aa Aaaaaaaa Aaaaa Aaa Aaaaaaa Aaaaaaa Aaaaaa
🔒The boundary between a crash-only validation abort and a latent driver-level OOB depends on which build configuration is running — the analysis explores both branches and what an attacker controls in each.
더 확인하려면 구독해 주세요
Audit directions
a Aaaaaaaaaaaaaaaa Aaaaaaaaaaaa Aaaa Aaaaa a Aaaaaa Aaaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaaaaaa Aaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaa Aa Aaa Aaaaaa Aaa Aaaaaaaaaaaaaaa Aa Aaaa Aa Aaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaa a Aaaaaaaaaaaaaaa a Aaaaaaaaaaaaaaaaaaaaa Aaaa Aaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaa Aa Aaa Aa Aaaa Aaaaaaaaaaaaaaaa Aaaaaa Aaaaaa
a Aaaaaaaaaaaa Aaaaaaaaaa Aa Aaa Aaaa Aaaaaaa Aaaaaaa a Aaaaa Aaa Aa a Aaaaaaaaaaa Aaaaa Aaa Aa Aaa Aaaaaa Aaaaaaaaaaaa Aa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaa a Aaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaaaaa Aaaa Aaaaaa Aa Aaa Aaaaa Aaaaaaaaaa Aaa Aa Aaaaaaaa Aaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaa Aaaaaa Aaa Aaa Aaa a Aaaa Aaaa Aaaa Aaaaaa Aaa Aaaaaaa Aaaa Aaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa
a Aaaaaaaaaaaa Aaaaa Aa Aaaaaa Aaaaaaaaaaaaa Aaaaaaa Aa a Aaaaa Aaaaaa Aaaaaaaa Aaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa a Aaa Aaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaa Aa Aaaaaaaaaaaaa a Aaaaaaaaaaaa Aa Aaaa Aaaaaaa Aa Aa Aaa Aa Aaa Aaaaaa Aaaaaaaaaaaaaa Aaa Aaa Aaaa Aa Aaa Aaaaaaa Aaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaaa Aa Aaaa Aaaaaa
a Aaaaaa Aaaaa Aaaaaaaaaaa Aaaa Aaaaaaaaaaa Aaaaaaa Aaaaaaa Aaaaaaa Aaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaaa Aaa Aaa Aaaaaaaaaa Aaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaaaaaaaaa Aa Aaaa Aaaaa Aaaaaa
Aaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaa Aaaaaa Aaa Aaa Aa a Aa Aa Aa Aaa Aaaa Aa Aaaa Aaaa Aaaaa Aaa Aaaaaaaaaa Aaaaa Aaa Aaaaaa Aa Aa Aaa Aaaaa Aa Aaa Aaa Aaa Aaaaaaaaaa Aaaaa Aa Aaaa Aaaaaaa
🔒Four reusable patterns covering cache-vs-backing-store invariants, GPU-validation-as-only-barrier risks, and analogous fast paths in sibling ANGLE backends.
더 확인하려면 구독해 주세요