Chrome · ANGLE
CVE-2026-79285
Uninitialized Memory in ANGLE
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
src/libANGLE/renderer/d3d/TextureD3D.cppsrc/libANGLE/renderer/d3d/TextureD3D.hsrc/tests/angle_end2end_tests_expectations.txtsrc/tests/gl_tests/RobustResourceInitTest.cpp
Patch
From ff68f46b370cccfff2410dd10428f7e57e386332 Mon Sep 17 00:00:00 2001 From: Tzarial <[email protected]> Date: Tue, 02 Jun 2026 19:21:31 +0000 Subject: [PATCH] Fix VRAM leak in D3D11 cube map redefinition When a cube map face is redefined with a size or format mismatch, TextureD3D_Cube::redefineImage discards the existing texture storage. Previously, it used a per-level TexLevelMask that excluded the entire level from being copied back to staging textures, causing the data for the other 5 sibling faces at that level to be lost. Since the front-end state still considered them initialized, this led to uninitialized VRAM exposure when the cube map was completed and read from again. This adds a granular releaseTexStorage overload that takes an array of TexLevelMasks (one for each face). redefineImage now specifically excludes only the face being redefined, ensuring the data for sibling faces is preserved in staging textures before the backend storage is destroyed. Bug: b/513918923 Test: angle_end2end_tests --gtest_filter=CubeMapTextureTest.RedefineSiblingFace* Change-Id: I3a4f9ff69eec00cd7b5b2510f472814b19ce09b9 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7893496 Commit-Queue: Tzarial <[email protected]> Reviewed-by: Geoff Lang <[email protected]> --- diff --git a/src/libANGLE/renderer/d3d/TextureD3D.cpp b/src/libANGLE/renderer/d3d/TextureD3D.cpp index 2fcb093..83b4ecf 100644 --- a/src/libANGLE/renderer/d3d/TextureD3D.cpp +++ b/src/libANGLE/renderer/d3d/TextureD3D.cpp @@ -820,6 +820,15 @@ angle::Result TextureD3D::releaseTexStorage(const gl::Context *context, const gl::TexLevelMask ©StorageToImagesMask) { + gl::CubeFaceArray<gl::TexLevelMask> copyMasks; + copyMasks.fill(copyStorageToImagesMask); + return releaseTexStorage(context, copyMasks); +} + +angle::Result TextureD3D::releaseTexStorage( + const gl::Context *context, + const gl::CubeFaceArray<gl::TexLevelMask> ©StorageToImagesMask) +{ if (!mTexStorage) { return angle::Result::Continue; @@ -837,11 +846,22 @@ ImageD3D *image = getImage(index); const int storageWidth = std::max(1, getLevelZeroWidth() >> index.getLevelIndex()); const int storageHeight = std::max(1, getLevelZeroHeight() >> index.getLevelIndex()); + + bool copyImage = false; + if (mState.getType() == gl::TextureType::CubeMap) + { + copyImage = + copyStorageToImagesMask[index.cubeMapFaceIndex()][index.getLevelIndex()]; + } + else + { + copyImage = copyStorageToImagesMask[0][index.getLevelIndex()]; + } + if (image && isImageComplete(index) && image->getWidth() == storageWidth && image->getHeight() == storageHeight && image->getInternalFormat() == storageFormat && - index.getLevelIndex() < static_cast<int>(storageLevels) && - copyStorageToImagesMask[index.getLevelIndex()]) + index.getLevelIndex() < static_cast<int>(storageLevels) && copyImage) { ANGLE_TRY(image->copyFromTexStorage(context, index, mTexStorage)); } @@ -2531,11 +2551,11 @@ { markAllImagesDirty(); - gl::TexLevelMask copyImageMask; - copyImageMask.set(); - copyImageMask.set(level, false); + gl::CubeFaceArray<gl::TexLevelMask> copyImageMasks; + copyImageMasks.fill(gl::TexLevelMask().set()); + copyImageMasks[faceIndex].set(level, false); - ANGLE_TRY(releaseTexStorage(context, copyImageMask)); + ANGLE_TRY(releaseTexStorage(context, copyImageMasks)); } } diff --git a/src/libANGLE/renderer/d3d/TextureD3D.h b/src/libANGLE/renderer/d3d/TextureD3D.h index 9b81f30..ee2dbe6 100644 --- a/src/libANGLE/renderer/d3d/TextureD3D.h +++ b/src/libANGLE/renderer/d3d/TextureD3D.h @@ -204,6 +204,9 @@ angle::Result releaseTexStorage(const gl::Context *context, const gl::TexLevelMask ©StorageToImagesMask); + angle::Result releaseTexStorage( + const gl::Context *context, + const gl::CubeFaceArray<gl::TexLevelMask> ©StorageToImagesMask); GLuint getBaseLevel() const { return mBaseLevel; } diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index 5ba8173..80e2980 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt @@ -2633,6 +2633,10 @@ // WGPU y-flip transforms have a bug when run on NVIDIA GPUs on Linux or Windows. 468025322 NVIDIA WGPU : CopyTextureVariationsTest.Copy*Texture/ES2_WebGPU__*YFlip* = SKIP +// WGPU does not support resizing textures or preserving sibling face data upon redefinition yet. +513918923 WGPU : RobustResourceInitTest.RedefineSiblingFace/* = SKIP +513918923 WIN OPENGL : RobustResourceInitTest.RedefineSiblingFace/* = SKIP + // SPIR-V compilation error 384593678 VULKAN : SimpleUniformTestES3.CopyOfUniformsWithArrays/* = SKIP diff --git a/src/tests/gl_tests/RobustResourceInitTest.cpp b/src/tests/gl_tests/RobustResourceInitTest.cpp index afab8d1..39fd638 100644 --- a/src/tests/gl_tests/RobustResourceInitTest.cpp +++ b/src/tests/gl_tests/RobustResourceInitTest.cpp @@ -4163,6 +4163,70 @@ EXPECT_GL_NO_ERROR(); } +// Tests that redefining a single face of a cube map does not discard the +// data of other faces at the same level. +TEST_P(RobustResourceInitTest, RedefineSiblingFace) +{ + ANGLE_SKIP_TEST_IF(!hasGLExtension()); + + const GLint N = 4; + + GLTexture tex; + glBindTexture(GL_TEXTURE_CUBE_MAP, tex); + + for (GLenum face = 0; face < 6; face++) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, N, N, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + tex, 0); + EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + glViewport(0, 0, N, N); + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); + EXPECT_GL_NO_ERROR(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Redefine POSITIVE_X with a mismatched size + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 2 * N, 2 * N, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + EXPECT_GL_NO_ERROR(); + + // Redefine POSITIVE_X back to normal size to make it cube complete again + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, N, N, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + EXPECT_GL_NO_ERROR(); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; + face++) + { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, face, tex, 0); + if (face == GL_TEXTURE_CUBE_MAP_NEGATIVE_X) + { + // Previously set to green with glClear and not redefined. + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); + } + else + { + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack); + } + } + EXPECT_GL_NO_ERROR(); +} + ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND( RobustResourceInitTest, ES3_METAL().enable(Feature::EmulateDontCareLoadWithRandomClear),
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index 5ba8173..80e2980 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -2633,6 +2633,10 @@
// WGPU y-flip transforms have a bug when run on NVIDIA GPUs on Linux or Windows.
468025322 NVIDIA WGPU : CopyTextureVariationsTest.Copy*Texture/ES2_WebGPU__*YFlip* = SKIP
+// WGPU does not support resizing textures or preserving sibling face data upon redefinition yet.
+513918923 WGPU : RobustResourceInitTest.RedefineSiblingFace/* = SKIP
+513918923 WIN OPENGL : RobustResourceInitTest.RedefineSiblingFace/* = SKIP
+
// SPIR-V compilation error
384593678 VULKAN : SimpleUniformTestES3.CopyOfUniformsWithArrays/* = SKIP
diff --git a/src/tests/gl_tests/RobustResourceInitTest.cpp b/src/tests/gl_tests/RobustResourceInitTest.cpp
index afab8d1..39fd638 100644
--- a/src/tests/gl_tests/RobustResourceInitTest.cpp
+++ b/src/tests/gl_tests/RobustResourceInitTest.cpp
@@ -4163,6 +4163,70 @@
EXPECT_GL_NO_ERROR();
}
+// Tests that redefining a single face of a cube map does not discard the
+// data of other faces at the same level.
+TEST_P(RobustResourceInitTest, RedefineSiblingFace)
+{
+ ANGLE_SKIP_TEST_IF(!hasGLExtension());
+
+ const GLint N = 4;
+
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
+
+ for (GLenum face = 0; face < 6; face++)
+ {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, N, N, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, nullptr);
+ }
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ EXPECT_GL_NO_ERROR();
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+ tex, 0);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ glViewport(0, 0, N, N);
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+ EXPECT_GL_NO_ERROR();
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ // Redefine POSITIVE_X with a mismatched size
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 2 * N, 2 * N, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, nullptr);
+ EXPECT_GL_NO_ERROR();
+
+ // Redefine POSITIVE_X back to normal size to make it cube complete again
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, N, N, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ nullptr);
+ EXPECT_GL_NO_ERROR();
+
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
+ face++)
+ {
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, face, tex, 0);
+ if (face == GL_TEXTURE_CUBE_MAP_NEGATIVE_X)
+ {
+ // Previously set to green with glClear and not redefined.
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+ }
+ else
+ {
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
+ }
+ }
+ EXPECT_GL_NO_ERROR();
+}
+
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(
RobustResourceInitTest,
ES3_METAL().enable(Feature::EmulateDontCareLoadWithRandomClear),
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