Chrome · ANGLE › Metal
CVE-2026-118329
Input Validation in ANGLE (Metal)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
src/tests/gl_tests/TextureTest.cppsrc/libANGLE/renderer/metal/TextureMtl.mm
Patch
From 974b79ad146a3dca3d9a54e13f38519a8269964e Mon Sep 17 00:00:00 2001 From: Le Hoang Quyen <[email protected]> Date: Fri, 09 Oct 2026 02:33:55 +0800 Subject: [PATCH] Metal: Fix 2D array layer count check in TextureMtl::redefineImage For MTLTextureType2DArray textures, MTLTexture.depth is always 1 while the layer count is stored in arrayLength. Substitute arrayLength into the depth component via GetTextureGLSize when comparing GL image extents in TextureMtl::redefineImage so layer-count-only redefinitions properly release stale storage and per-level views. Bug: chromium:568900535 Change-Id: I9d0b6a36941d739fab15ab1fa37767946bc0441b Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8542591 Reviewed-by: Kenneth Russell <[email protected]> Commit-Queue: Quyen Le <[email protected]> --- diff --git a/src/libANGLE/renderer/metal/TextureMtl.mm b/src/libANGLE/renderer/metal/TextureMtl.mm index ab28138..e60b91e 100644 --- a/src/libANGLE/renderer/metal/TextureMtl.mm +++ b/src/libANGLE/renderer/metal/TextureMtl.mm @@ -713,6 +713,19 @@ return levelTextureViews[level]; } +// Returns the texture extents in GL terms, substituting arrayLength into depth for array textures +// since MTLTexture stores layer count in arrayLength with depth == 1. +gl::Extents GetTextureGLSize(const mtl::TextureRef &texture, + const mtl::MipmapNativeLevel &nativeLevel) +{ + gl::Extents size = texture->size(nativeLevel); + if (texture->textureType() == MTLTextureType2DArray) + { + size.depth = static_cast<int>(texture->arrayLength()); + } + return size; +} + } // namespace // TextureMtl::NativeTextureWrapper implementation. @@ -799,6 +812,10 @@ uint32_t depth(GLuint glLevel) const { return mNativeTexture->depth(getNativeLevel(glLevel)); } gl::Extents size(GLuint glLevel) const { return mNativeTexture->size(getNativeLevel(glLevel)); } + gl::Extents getGLSize(GLuint glLevel) const + { + return GetTextureGLSize(mNativeTexture, getNativeLevel(glLevel)); + } // Get width, height, depth, size at base level. uint32_t widthAt0() const { return width(mBaseGLLevel); } @@ -2094,7 +2111,7 @@ // must release it. ASSERT(mNativeTextureStorage->textureType() == mtl::GetTextureType(index.getType())); if (mNativeTextureStorage->getFormat() != mtlFormat || - size != mNativeTextureStorage->size(glLevel)) + size != mNativeTextureStorage->getGLSize(glLevel)) { // Keep other images deallocateNativeStorage(/*keepImages=*/true); @@ -2118,7 +2135,7 @@ imageDef.image->textureType() == mtl::GetTextureType(GetTextureImageType(index.getType())) && imageDef.formatID == mNativeTextureStorage->getFormat().intendedFormatId && - imageDef.image->sizeAt0() == size) + GetTextureGLSize(imageDef.image, mtl::kZeroNativeMipLevel) == size) { // Keep it! (No-op) } diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp index 1bb7cc7..0177499 100644 --- a/src/tests/gl_tests/TextureTest.cpp +++ b/src/tests/gl_tests/TextureTest.cpp @@ -9796,6 +9796,52 @@ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green); } +// Test that redefining level 0 of a multi-layer 2D array texture to 1 layer (with unchanged +// width, height, and format) followed by glGenerateMipmap preserves the newly uploaded level 0 +// data and generates mipmaps from it. +TEST_P(Texture2DArrayTestES3, RedefineBaseLevelLayerCountToOneThenGenerateMipmap) +{ + constexpr GLsizei kSize = 8; + constexpr GLsizei kInitialLayers = 4; + + glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Define level 0 with 4 layers filled with red and draw to materialize native storage. + const std::vector<GLColor> pixelsRed(kSize * kSize * kInitialLayers, GLColor::red); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, kInitialLayers, 0, GL_RGBA, + GL_UNSIGNED_BYTE, pixelsRed.data()); + ASSERT_GL_NO_ERROR(); + + glUseProgram(mProgram); + glUniform1i(mTextureArraySliceUniformLocation, 0); + drawQuad(mProgram, "position", 0.5f); + ASSERT_GL_NO_ERROR(); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); + + // Redefine level 0 with 1 layer (unchanged width, height, and format) filled with green. + const std::vector<GLColor> pixelsGreen(kSize * kSize * 1, GLColor::green); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, + pixelsGreen.data()); + ASSERT_GL_NO_ERROR(); + + // Generate mipmaps; this recreates storage for the full mip chain of the 1-layer array. + glGenerateMipmap(GL_TEXTURE_2D_ARRAY); + ASSERT_GL_NO_ERROR(); + + // Verify level 0 and level 1 contain green. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, GLColor::green); + + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 1, 0); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + EXPECT_PIXEL_RECT_EQ(0, 0, kSize / 2, kSize / 2, GLColor::green); +} + // Create a 2D array texture, use it, then redefine one level without changing dimensions. TEST_P(Texture2DArrayTestES3, RedefineLevelData) {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index 1bb7cc7..0177499 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -9796,6 +9796,52 @@
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
}
+// Test that redefining level 0 of a multi-layer 2D array texture to 1 layer (with unchanged
+// width, height, and format) followed by glGenerateMipmap preserves the newly uploaded level 0
+// data and generates mipmaps from it.
+TEST_P(Texture2DArrayTestES3, RedefineBaseLevelLayerCountToOneThenGenerateMipmap)
+{
+ constexpr GLsizei kSize = 8;
+ constexpr GLsizei kInitialLayers = 4;
+
+ glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ // Define level 0 with 4 layers filled with red and draw to materialize native storage.
+ const std::vector<GLColor> pixelsRed(kSize * kSize * kInitialLayers, GLColor::red);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, kInitialLayers, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, pixelsRed.data());
+ ASSERT_GL_NO_ERROR();
+
+ glUseProgram(mProgram);
+ glUniform1i(mTextureArraySliceUniformLocation, 0);
+ drawQuad(mProgram, "position", 0.5f);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+
+ // Redefine level 0 with 1 layer (unchanged width, height, and format) filled with green.
+ const std::vector<GLColor> pixelsGreen(kSize * kSize * 1, GLColor::green);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ pixelsGreen.data());
+ ASSERT_GL_NO_ERROR();
+
+ // Generate mipmaps; this recreates storage for the full mip chain of the 1-layer array.
+ glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
+ ASSERT_GL_NO_ERROR();
+
+ // Verify level 0 and level 1 contain green.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, GLColor::green);
+
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 1, 0);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+ EXPECT_PIXEL_RECT_EQ(0, 0, kSize / 2, kSize / 2, GLColor::green);
+}
+
// Create a 2D array texture, use it, then redefine one level without changing dimensions.
TEST_P(Texture2DArrayTestES3, RedefineLevelData)
{
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page