CVE-2026-106245
Overview
Background
- glCopyTexImage2D robust-init
- when the copy’s source area is out of the source framebuffer’s bounds, the destination must be robust-initialized (zeroed) for the out-of-bounds region.
- Self-copy exclusion
- robust-init is skipped when source == destination, because initializing would destroy the source content before the copy.
- Subresources / mips
- different mip levels of one texture are different subresources.
Root Cause Analysis
The self-copy exclusion that skips robust-init was too broad: it keyed only on sourceReadAttachment->getResource() != this (same texture object), so an out-of-bounds glCopyTexImage2D copying between different mips of the same texture also skipped robust-init — leaving the destination mip’s out-of-bounds region uninitialized.
The fix adds a backend limitation noRobustInitOnOOBCopyTexImageSameTexture (set for Vulkan) and narrows the exclusion so that copies between different subresources (sourceReadAttachment->getTextureImageIndex() != index) of the same texture still robust-init. The advisory’s “uninitialized resource” label matches: an out-of-range read returns uninitialized GPU memory. Kept as UninitializedMemory.
Attack Path
- ENTRY From WebGL, create a texture with multiple mip levels.
- TRIGGER
glCopyTexImage2Dinto one mip from a framebuffer whose read attachment is a different mip of the same texture, with the source area out of bounds. - MISSING CHECK The self-copy exclusion skipped robust-init because it matched on the texture object, not the subresource.
- IMPACT The destination mip’s out-of-bounds region is left uninitialized and can be read back, exposing stale GPU memory.
Impact Assessment
Files Changed
src/tests/gl_tests/TextureTest.cppsrc/libANGLE/renderer/vulkan/vk_caps_utils.cppsrc/libANGLE/Caps.hsrc/libANGLE/Texture.cppsrc/tests/angle_end2end_tests_expectations.txt
Audit Directions
- Subresource-granular self-copy checksAudit copy robust-init exclusions that compare only the resource/object; different mips/layers are distinct subresources.
- OOB copy zero-fillConfirm every out-of-bounds copy destination region is robust-initialized across backends.
Patch
From f9c58091598545e4d2def8a3b18bdb45e383341f Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi <[email protected]> Date: Wed, 02 Sep 2026 15:16:44 -0400 Subject: [PATCH] Fix robust-init when OOB-copying between mips of same texture The front-end has logic to initialize the destination texture when the target of glCopyTexImage2D is out of bounds of the source framebuffer. However, this is skipped during self-copies as the initialization would destroy source(==destination)'s content before the copy. This self-copy check is improved in this change to exclude copies between different subresources of the same texture. Bug: chromium:550302121 Change-Id: I565ea820d49ea8d37910247b03729468280509f0 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8347448 Reviewed-by: Geoff Lang <[email protected]> Commit-Queue: Shahbaz Youssefi <[email protected]> --- diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h index 27df744..375164b 100644 --- a/src/libANGLE/Caps.h +++ b/src/libANGLE/Caps.h @@ -133,6 +133,10 @@ // http://anglebug.com/42266263 bool noRasterOrderGroupWithoutAttachmentZero = false; + // Some backends don't apply robust init when glCopyTexImage2D reads out of bounds of source + // framebuffer, whose read attachment is a different mip of the same texture being redefined. + bool noRobustInitOnOOBCopyTexImageSameTexture = false; + // ETC1 texture support is emulated. bool emulatedEtc1 = false; diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp index d1757ba..34ff710 100644 --- a/src/libANGLE/Texture.cpp +++ b/src/libANGLE/Texture.cpp @@ -1587,11 +1587,15 @@ const FramebufferAttachment *sourceReadAttachment = source->getReadColorAttachment(); Extents fbSize = sourceReadAttachment->getSize(); // Force using copySubImage when the source area is out of bounds AND - // we're not copying to and from the same texture - forceCopySubImage = ((sourceArea.x < 0) || (sourceArea.y < 0) || - ((sourceArea.x + sourceArea.width) > fbSize.width) || - ((sourceArea.y + sourceArea.height) > fbSize.height)) && - (sourceReadAttachment->getResource() != this); + // we're not copying to and from the same texture. When + // noRobustInitOnOOBCopyTexImageSameTexture is set, copySubImage is used also when copying + // between different subresources of the same texture. + forceCopySubImage = (sourceArea.x < 0 || sourceArea.y < 0 || + sourceArea.x + sourceArea.width > fbSize.width || + sourceArea.y + sourceArea.height > fbSize.height) && + (sourceReadAttachment->getResource() != this || + (context->getLimitations().noRobustInitOnOOBCopyTexImageSameTexture && + sourceReadAttachment->getTextureImageIndex() != index)); Rectangle clippedArea; if (ClipRectangle(sourceArea, Rectangle(0, 0, fbSize.width, fbSize.height), &clippedArea)) { diff --git a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp index 45ad108..7ec5e2d 100644 --- a/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp +++ b/src/libANGLE/renderer/vulkan/vk_caps_utils.cpp @@ -1517,6 +1517,10 @@ mNativeLimitations.maxBufferBytes = static_cast<size_t>(mMaxMemoryAllocationSize); mNativeLimitations.maxTextureBytes = static_cast<size_t>(mMaxMemoryAllocationSize); + // Ask the front-end to handle robust-init when copying between mips of the same texture via + // glCopyTexImage2D. + mNativeLimitations.noRobustInitOnOOBCopyTexImageSameTexture = true; + // Log any missing extensions required for GLES 3.2. LogMissingExtensionsForGLES32(mNativeExtensions); } diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index 1d8f85c..25e7958 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt @@ -142,6 +142,10 @@ 524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP 524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyANGLE/* = SKIP +// Out of bounds self-copies don't robust-init +550302121 VULKAN : Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf/* = SKIP +550302121 VULKAN : Texture2DTestES3RobustInit.CopyTexImagePartiallyOutOfBoundsSelf/* = SKIP + // Asserts on Win/D3D11 524406308 WIN D3D11 : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP 524406308 WIN D3D11 : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyANGLE/* = SKIP @@ -325,6 +329,8 @@ 437259106 LINUX INTEL : EGLDisplayTest.ContextLeakAfterTerminate/* = SKIP 448675904 LINUX INTEL OPENGL : GLSLTestLoops.ForContinueInSwitchComplex/* = SKIP 537235696 LINUX INTEL OPENGL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP +556789075 LINUX INTEL OPENGL : Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf/* = SKIP +556789075 LINUX INTEL OPENGL : Texture2DTestES3RobustInit.CopyTexImagePartiallyOutOfBoundsSelf/* = SKIP //Angle has bugs for sampler validation 401546698 : TextureCubeTest.CubeMapBug2/* = SKIP @@ -387,6 +393,10 @@ 540256225 NVIDIA VULKAN : ImageTestES3.SourceYUVTextureTargetExternalRGBSample/* = SKIP 546627192 NVIDIA OPENGL : GLSLTest_ES3.VaryingAnonymousStructTernary/* = SKIP 546627192 NVIDIA GLES : GLSLTest_ES3.VaryingAnonymousStructTernary/* = SKIP +556790356 NVIDIA OPENGL : Texture2DTestES3RobustInit.CopyTexSubImageOutOfBoundsAcrossMips/* = SKIP +556790356 NVIDIA GLES : Texture2DTestES3RobustInit.CopyTexSubImageOutOfBoundsAcrossMips/* = SKIP +556790356 NVIDIA OPENGL : Texture2DTestES3RobustInit.CopyTexSubImagePartiallyOutOfBoundsAcrossMips/* = SKIP +556790356 NVIDIA GLES : Texture2DTestES3RobustInit.CopyTexSubImagePartiallyOutOfBoundsAcrossMips/* = SKIP // Failing on NVidia OpenGL 549916890 NVIDIA OPENGL : Texture3DIncreaseDepthTestES3.TexImage3DDepthIncreaseOutsideRangeDrawnVerifyByDraw/* = SKIP @@ -610,6 +620,7 @@ 526451592 WIN D3D11 : ImageTestES3.NonZeroLevelCopyTexImageSrcDst/* = SKIP 526451592 WIN D3D11 : ImageTestES3.NonZeroLevelTexSubImageDstWithUnpackSlow/* = SKIP 526451592 WIN D3D11 : ImageTestES3.NonZeroLevelBlitDst/* = SKIP +556789073 WIN D3D11 : Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf/* = SKIP // Android 42264624 ANDROID GLES : GLSLTest_ES3.InitGlobalComplexConstant/* = SKIP @@ -766,6 +777,7 @@ 526518127 PIXEL4ORXL GLES : ImageTestES3.NonZeroLevel*/* = SKIP 546627192 PIXEL4ORXL GLES : GLSLTest_ES3.VaryingAnonymousStructAssignment/* = SKIP 546627192 PIXEL4ORXL GLES : GLSLTest_ES3.VaryingAnonymousStructTernary/* = SKIP +556789076 PIXEL4ORXL GLES : Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf/* = SKIP 42267082 PIXEL4ORXL GLES : BlobCacheTest.FragmentOutputLocationKey/* = SKIP 42267082 PIXEL4ORXL GLES : EGLBlobCacheTest.FragmentOutputLocationKey/* = SKIP diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp index d0e3565..4c77f9f 100644 --- a/src/tests/gl_tests/TextureTest.cpp +++ b/src/tests/gl_tests/TextureTest.cpp @@ -23246,6 +23246,369 @@ EXPECT_PIXEL_RECT_EQ(0, 0, 128, 1, GLColor::blue); } +// Test that robust initialization works when glCopyTexImage2D is outside the bounds of the +// framebuffer. +TEST_P(Texture2DTestES3RobustInit, CopyTexImageOutOfBounds) +{ + constexpr GLuint kSrcWidth = 11; + constexpr GLuint kSrcHeight = 17; + constexpr GLuint kDstWidth = 5; + constexpr GLuint kDstHeight = 9; + + // Set up framebuffer as source of copy. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLRenderbuffer rbo; + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSrcWidth, kSrcHeight); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); + EXPECT_GL_NO_ERROR(); + + // Define the destination texture by copying from the source framebuffer out of bounds. + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, kDstWidth, kDstHeight, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + // The texture should be robust-initialized. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight, GLColor::transparentBlack); + ASSERT_GL_NO_ERROR(); +} + +// Test that robust initialization works when glCopyTexImage2D is partially outside the bounds of +// the framebuffer. +TEST_P(Texture2DTestES3RobustInit, CopyTexImagePartiallyOutOfBounds) +{ + constexpr GLuint kSrcWidth = 11; + constexpr GLuint kSrcHeight = 17; + constexpr GLuint kDstWidth = 5; + constexpr GLuint kDstHeight = 9; + + // Set up framebuffer as source of copy. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLRenderbuffer rbo; + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSrcWidth, kSrcHeight); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); + glClearColor(0, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_GL_NO_ERROR(); + + // Define the destination texture by copying from the source framebuffer out of bounds. + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth - kDstWidth, kSrcHeight - kDstHeight / 2, + kDstWidth, kDstHeight, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + // The texture should be robust-initialized. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight / 2, GLColor::blue); + EXPECT_PIXEL_RECT_EQ(0, kDstHeight / 2, kDstWidth, kDstHeight - kDstHeight / 2, + GLColor::transparentBlack); + ASSERT_GL_NO_ERROR(); +} + +// Test that robust initialization works when glCopyTexSubImage2D is outside the bounds of the +// framebuffer. +TEST_P(Texture2DTestES3RobustInit, CopyTexSubImageOutOfBounds) +{ + constexpr GLuint kSrcWidth = 11; + constexpr GLuint kSrcHeight = 17; + constexpr GLuint kDstWidth = 5; + constexpr GLuint kDstHeight = 9; + + // Set up framebuffer as source of copy. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLRenderbuffer rbo; + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSrcWidth, kSrcHeight); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); + EXPECT_GL_NO_ERROR(); + + // Define the destination texture and copy to it from the source framebuffer out of bounds. + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kDstWidth, kDstHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSrcWidth, kSrcHeight, kDstWidth, kDstHeight); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + // The texture should be robust-initialized. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight, GLColor::transparentBlack); + ASSERT_GL_NO_ERROR(); +} + +// Test that robust initialization works when glCopyTexSubImage2D is partially outside the bounds of +// the framebuffer. +TEST_P(Texture2DTestES3RobustInit, CopyTexSubImagePartiallyOutOfBounds) +{ + constexpr GLuint kSrcWidth = 11; + constexpr GLuint kSrcHeight = 17; + constexpr GLuint kDstWidth = 5; + constexpr GLuint kDstHeight = 9; + + // Set up framebuffer as source of copy. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLRenderbuffer rbo; + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSrcWidth, kSrcHeight); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); + glClearColor(0, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_GL_NO_ERROR(); + + // Define the destination texture and copy to it from the source framebuffer out of bounds. + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kDstWidth, kDstHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSrcWidth - kDstWidth, kSrcHeight - kDstHeight / 2, + kDstWidth, kDstHeight); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + // The texture should be robust-initialized. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight / 2, GLColor::blue); + EXPECT_PIXEL_RECT_EQ(0, kDstHeight / 2, kDstWidth, kDstHeight - kDstHeight / 2, + GLColor::transparentBlack); + ASSERT_GL_NO_ERROR(); +} + +// Test that robust initialization works when glCopyTexImage2D is outside the bounds of the +// framebuffer, but source and dest are the same texture. +TEST_P(Texture2DTestES3RobustInit, CopyTexImageOutOfBoundsSelf) +{ + constexpr GLuint kSrcWidth = 11; + constexpr GLuint kSrcHeight = 17; + constexpr GLuint kDstWidth = 5; + constexpr GLuint kDstHeight = 9; + + // Set up framebuffer as source of copy. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + const std::vector<GLColor> kInitColor(kSrcWidth * kSrcHeight, GLColor::blue); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + kInitColor.data()); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + EXPECT_GL_NO_ERROR(); + + // Redefine the texture by copying from the source framebuffer out of bounds. + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, kDstWidth, kDstHeight, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + // The texture should be robust-initialized. + EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight, GLColor::transparentBlack); + ASSERT_GL_NO_ERROR(); +} + +// Test that robust initialization works when glCopyTexImage2D is partially outside the bounds of +// the framebuffer, but source and dest are the same texture. +TEST_P(Texture2DTestES3RobustInit, CopyTexImagePartiallyOutOfBoundsSelf) +{ + constexpr GLuint kSrcWidth = 11; + constexpr GLuint kSrcHeight = 17; + constexpr GLuint kDstWidth = 5; + constexpr GLuint kDstHeight = 9; + + // Set up framebuffer as source of copy. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + const std::vector<GLColor> kInitColor(kSrcWidth * kSrcHeight, GLColor::blue); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + kInitColor.data()); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + EXPECT_GL_NO_ERROR(); + + // Redefine the texture by copying from the source framebuffer out of bounds. + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth - kDstWidth / 2, kSrcHeight - kDstHeight, + kDstWidth, kDstHeight, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + // The texture should be robust-initialized. + EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth / 2, kDstHeight, GLColor::blue); + EXPECT_PIXEL_RECT_EQ(kDstWidth / 2, 0, kDstWidth - kDstWidth / 2, kDstHeight, + GLColor::transparentBlack); + ASSERT_GL_NO_ERROR(); +} + +// Test that robust initialization works when glCopyTexImage2D is outside the bounds of the +// framebuffer, but source and dest are different mips of the same texture. +TEST_P(Texture2DTestES3RobustInit, CopyTexImageOutOfBoundsAcrossMips) +{ + constexpr GLuint kSrcWidth = 11; + constexpr GLuint kSrcHeight = 17; + constexpr GLuint kDstWidth = 5; + constexpr GLuint kDstHeight = 9; + + // Set up framebuffer as source of copy. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + const std::vector<GLColor> kInitColor(kSrcWidth * kSrcHeight, GLColor::blue); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + kInitColor.data()); + glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + kInitColor.data()); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + EXPECT_GL_NO_ERROR(); + + // Redefine the texture by copying from the source framebuffer out of bounds. + glCopyTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSrcWidth, kSrcHeight, kDstWidth, kDstHeight, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + EXPECT_GL_NO_ERROR(); + + // The texture should be robust-initialized. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); + EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight, GLColor::transparentBlack); + ASSERT_GL_NO_ERROR(); +} + +// Test that robust initialization works when glCopyTexImage2D is partially outside the bounds of +// the framebuffer, but source and dest are different mips of the same texture. +TEST_P(Texture2DTestES3RobustInit, CopyTexImagePartiallyOutOfBoundsAcrossMips) +{ + constexpr GLuint kSrcWidth = 11; + constexpr GLuint kSrcHeight = 17; + constexpr GLuint kDstWidth = 5; + constexpr GLuint kDstHeight = 9; + + // Set up framebuffer as source of copy. + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLTexture tex; + glBindTexture(GL_TEXTURE_2D, tex); + const std::vector<GLColor> kInitColor0(kSrcWidth * kSrcHeight, GLColor::blue); + const std::vector<GLColor> kInitColor1(kSrcWidth * kSrcHeight, GLColor::green); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + kInitColor0.data()); + glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + kInitColor1.data()); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + EXPECT_GL_NO_ERROR(); + + // Redefine the texture by copying from the source framebuffer out of bounds. + glCopyTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSrcWidth - kDstWidth / 2, ... (file diff truncated)
Regression Test / PoC
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index 1d8f85c..25e7958 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -142,6 +142,10 @@
524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP
524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyANGLE/* = SKIP
+// Out of bounds self-copies don't robust-init
+550302121 VULKAN : Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf/* = SKIP
+550302121 VULKAN : Texture2DTestES3RobustInit.CopyTexImagePartiallyOutOfBoundsSelf/* = SKIP
+
// Asserts on Win/D3D11
524406308 WIN D3D11 : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP
524406308 WIN D3D11 : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyANGLE/* = SKIP
@@ -325,6 +329,8 @@
437259106 LINUX INTEL : EGLDisplayTest.ContextLeakAfterTerminate/* = SKIP
448675904 LINUX INTEL OPENGL : GLSLTestLoops.ForContinueInSwitchComplex/* = SKIP
537235696 LINUX INTEL OPENGL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP
+556789075 LINUX INTEL OPENGL : Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf/* = SKIP
+556789075 LINUX INTEL OPENGL : Texture2DTestES3RobustInit.CopyTexImagePartiallyOutOfBoundsSelf/* = SKIP
//Angle has bugs for sampler validation
401546698 : TextureCubeTest.CubeMapBug2/* = SKIP
@@ -387,6 +393,10 @@
540256225 NVIDIA VULKAN : ImageTestES3.SourceYUVTextureTargetExternalRGBSample/* = SKIP
546627192 NVIDIA OPENGL : GLSLTest_ES3.VaryingAnonymousStructTernary/* = SKIP
546627192 NVIDIA GLES : GLSLTest_ES3.VaryingAnonymousStructTernary/* = SKIP
+556790356 NVIDIA OPENGL : Texture2DTestES3RobustInit.CopyTexSubImageOutOfBoundsAcrossMips/* = SKIP
+556790356 NVIDIA GLES : Texture2DTestES3RobustInit.CopyTexSubImageOutOfBoundsAcrossMips/* = SKIP
+556790356 NVIDIA OPENGL : Texture2DTestES3RobustInit.CopyTexSubImagePartiallyOutOfBoundsAcrossMips/* = SKIP
+556790356 NVIDIA GLES : Texture2DTestES3RobustInit.CopyTexSubImagePartiallyOutOfBoundsAcrossMips/* = SKIP
// Failing on NVidia OpenGL
549916890 NVIDIA OPENGL : Texture3DIncreaseDepthTestES3.TexImage3DDepthIncreaseOutsideRangeDrawnVerifyByDraw/* = SKIP
@@ -610,6 +620,7 @@
526451592 WIN D3D11 : ImageTestES3.NonZeroLevelCopyTexImageSrcDst/* = SKIP
526451592 WIN D3D11 : ImageTestES3.NonZeroLevelTexSubImageDstWithUnpackSlow/* = SKIP
526451592 WIN D3D11 : ImageTestES3.NonZeroLevelBlitDst/* = SKIP
+556789073 WIN D3D11 : Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf/* = SKIP
// Android
42264624 ANDROID GLES : GLSLTest_ES3.InitGlobalComplexConstant/* = SKIP
@@ -766,6 +777,7 @@
526518127 PIXEL4ORXL GLES : ImageTestES3.NonZeroLevel*/* = SKIP
546627192 PIXEL4ORXL GLES : GLSLTest_ES3.VaryingAnonymousStructAssignment/* = SKIP
546627192 PIXEL4ORXL GLES : GLSLTest_ES3.VaryingAnonymousStructTernary/* = SKIP
+556789076 PIXEL4ORXL GLES : Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf/* = SKIP
42267082 PIXEL4ORXL GLES : BlobCacheTest.FragmentOutputLocationKey/* = SKIP
42267082 PIXEL4ORXL GLES : EGLBlobCacheTest.FragmentOutputLocationKey/* = SKIP
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index d0e3565..4c77f9f 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -23246,6 +23246,369 @@
EXPECT_PIXEL_RECT_EQ(0, 0, 128, 1, GLColor::blue);
}
+// Test that robust initialization works when glCopyTexImage2D is outside the bounds of the
+// framebuffer.
+TEST_P(Texture2DTestES3RobustInit, CopyTexImageOutOfBounds)
+{
+ constexpr GLuint kSrcWidth = 11;
+ constexpr GLuint kSrcHeight = 17;
+ constexpr GLuint kDstWidth = 5;
+ constexpr GLuint kDstHeight = 9;
+
+ // Set up framebuffer as source of copy.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLRenderbuffer rbo;
+ glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSrcWidth, kSrcHeight);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+ EXPECT_GL_NO_ERROR();
+
+ // Define the destination texture by copying from the source framebuffer out of bounds.
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, kDstWidth, kDstHeight, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ EXPECT_GL_NO_ERROR();
+
+ // The texture should be robust-initialized.
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+ EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight, GLColor::transparentBlack);
+ ASSERT_GL_NO_ERROR();
+}
+
+// Test that robust initialization works when glCopyTexImage2D is partially outside the bounds of
+// the framebuffer.
+TEST_P(Texture2DTestES3RobustInit, CopyTexImagePartiallyOutOfBounds)
+{
+ constexpr GLuint kSrcWidth = 11;
+ constexpr GLuint kSrcHeight = 17;
+ constexpr GLuint kDstWidth = 5;
+ constexpr GLuint kDstHeight = 9;
+
+ // Set up framebuffer as source of copy.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLRenderbuffer rbo;
+ glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSrcWidth, kSrcHeight);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+ glClearColor(0, 0, 1, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_GL_NO_ERROR();
+
+ // Define the destination texture by copying from the source framebuffer out of bounds.
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth - kDstWidth, kSrcHeight - kDstHeight / 2,
+ kDstWidth, kDstHeight, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ EXPECT_GL_NO_ERROR();
+
+ // The texture should be robust-initialized.
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+ EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight / 2, GLColor::blue);
+ EXPECT_PIXEL_RECT_EQ(0, kDstHeight / 2, kDstWidth, kDstHeight - kDstHeight / 2,
+ GLColor::transparentBlack);
+ ASSERT_GL_NO_ERROR();
+}
+
+// Test that robust initialization works when glCopyTexSubImage2D is outside the bounds of the
+// framebuffer.
+TEST_P(Texture2DTestES3RobustInit, CopyTexSubImageOutOfBounds)
+{
+ constexpr GLuint kSrcWidth = 11;
+ constexpr GLuint kSrcHeight = 17;
+ constexpr GLuint kDstWidth = 5;
+ constexpr GLuint kDstHeight = 9;
+
+ // Set up framebuffer as source of copy.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLRenderbuffer rbo;
+ glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSrcWidth, kSrcHeight);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+ EXPECT_GL_NO_ERROR();
+
+ // Define the destination texture and copy to it from the source framebuffer out of bounds.
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kDstWidth, kDstHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ nullptr);
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSrcWidth, kSrcHeight, kDstWidth, kDstHeight);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ EXPECT_GL_NO_ERROR();
+
+ // The texture should be robust-initialized.
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+ EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight, GLColor::transparentBlack);
+ ASSERT_GL_NO_ERROR();
+}
+
+// Test that robust initialization works when glCopyTexSubImage2D is partially outside the bounds of
+// the framebuffer.
+TEST_P(Texture2DTestES3RobustInit, CopyTexSubImagePartiallyOutOfBounds)
+{
+ constexpr GLuint kSrcWidth = 11;
+ constexpr GLuint kSrcHeight = 17;
+ constexpr GLuint kDstWidth = 5;
+ constexpr GLuint kDstHeight = 9;
+
+ // Set up framebuffer as source of copy.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLRenderbuffer rbo;
+ glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSrcWidth, kSrcHeight);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+ glClearColor(0, 0, 1, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_GL_NO_ERROR();
+
+ // Define the destination texture and copy to it from the source framebuffer out of bounds.
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kDstWidth, kDstHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ nullptr);
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSrcWidth - kDstWidth, kSrcHeight - kDstHeight / 2,
+ kDstWidth, kDstHeight);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ EXPECT_GL_NO_ERROR();
+
+ // The texture should be robust-initialized.
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+ EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight / 2, GLColor::blue);
+ EXPECT_PIXEL_RECT_EQ(0, kDstHeight / 2, kDstWidth, kDstHeight - kDstHeight / 2,
+ GLColor::transparentBlack);
+ ASSERT_GL_NO_ERROR();
+}
+
+// Test that robust initialization works when glCopyTexImage2D is outside the bounds of the
+// framebuffer, but source and dest are the same texture.
+TEST_P(Texture2DTestES3RobustInit, CopyTexImageOutOfBoundsSelf)
+{
+ constexpr GLuint kSrcWidth = 11;
+ constexpr GLuint kSrcHeight = 17;
+ constexpr GLuint kDstWidth = 5;
+ constexpr GLuint kDstHeight = 9;
+
+ // Set up framebuffer as source of copy.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ const std::vector<GLColor> kInitColor(kSrcWidth * kSrcHeight, GLColor::blue);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ kInitColor.data());
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+ EXPECT_GL_NO_ERROR();
+
+ // Redefine the texture by copying from the source framebuffer out of bounds.
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, kDstWidth, kDstHeight, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ EXPECT_GL_NO_ERROR();
+
+ // The texture should be robust-initialized.
+ EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight, GLColor::transparentBlack);
+ ASSERT_GL_NO_ERROR();
+}
+
+// Test that robust initialization works when glCopyTexImage2D is partially outside the bounds of
+// the framebuffer, but source and dest are the same texture.
+TEST_P(Texture2DTestES3RobustInit, CopyTexImagePartiallyOutOfBoundsSelf)
+{
+ constexpr GLuint kSrcWidth = 11;
+ constexpr GLuint kSrcHeight = 17;
+ constexpr GLuint kDstWidth = 5;
+ constexpr GLuint kDstHeight = 9;
+
+ // Set up framebuffer as source of copy.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ const std::vector<GLColor> kInitColor(kSrcWidth * kSrcHeight, GLColor::blue);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ kInitColor.data());
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+ EXPECT_GL_NO_ERROR();
+
+ // Redefine the texture by copying from the source framebuffer out of bounds.
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth - kDstWidth / 2, kSrcHeight - kDstHeight,
+ kDstWidth, kDstHeight, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ EXPECT_GL_NO_ERROR();
+
+ // The texture should be robust-initialized.
+ EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth / 2, kDstHeight, GLColor::blue);
+ EXPECT_PIXEL_RECT_EQ(kDstWidth / 2, 0, kDstWidth - kDstWidth / 2, kDstHeight,
+ GLColor::transparentBlack);
+ ASSERT_GL_NO_ERROR();
+}
+
+// Test that robust initialization works when glCopyTexImage2D is outside the bounds of the
+// framebuffer, but source and dest are different mips of the same texture.
+TEST_P(Texture2DTestES3RobustInit, CopyTexImageOutOfBoundsAcrossMips)
+{
+ constexpr GLuint kSrcWidth = 11;
+ constexpr GLuint kSrcHeight = 17;
+ constexpr GLuint kDstWidth = 5;
+ constexpr GLuint kDstHeight = 9;
+
+ // Set up framebuffer as source of copy.
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLTexture tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ const std::vector<GLColor> kInitColor(kSrcWidth * kSrcHeight, GLColor::blue);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ kInitColor.data());
+ glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSrcWidth, kSrcHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ kInitColor.data());
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
+ EXPECT_GL_NO_ERROR();
+
+ // Redefine the texture by copying from the source framebuffer out of bounds.
+ glCopyTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSrcWidth, kSrcHeight, kDstWidth, kDstHeight, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ EXPECT_GL_NO_ERROR();
+
+ // The texture should be robust-initialized.
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 1);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
+ EXPECT_PIXEL_RECT_EQ(0, 0, kDstWidth, kDstHeight, GLColor::transparentBlack);
+ ASSERT_GL_NO_ERROR();
+}
... (truncated)
Original Bug Report
ANGLE/Vulkan: same-texture different-mip copyTexImage2D leaks cross-origin GPU texture data
Security Bug
Important: Please do not change the component of this bug manually.
Please READ THIS FAQ before filing a bug: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md
Please see the following link for instructions on filing security bugs: https://www.chromium.org/Home/chromium-security/reporting-security-bugs
Reports may be eligible for reward payments under the Chrome VRP: https://g.co/chrome/vrp
NOTE: Security bugs are normally made public once a fix has been widely deployed.
VULNERABILITY DETAILS
ANGLE’s WebGL robust-initialization path mishandles a legal copyTexImage2D whose read
framebuffer and destination are different mip levels of the same texture. If the source
rectangle is fully outside the framebuffer, the Vulkan backend writes no destination pixels,
but ANGLE marks the redefined destination image initialized. A later readPixels can therefore
return stale bytes from a reused Vulkan allocation instead of zeroes.
On official Chrome Stable, a localhost iframe wrote and released a marker texture. A fresh
WebGL2 context at 127.0.0.1 recovered 1,024 exact marker pixels through this path. These are
distinct same-origin-policy origins and distinct WebGL contexts. The iframe’s postMessage
contained only type and status metadata; it did not transfer pixels, marker bytes, an image,
canvas data, or a WebGL object.
The faulty condition in src/libANGLE/Texture.cpp, Texture::copyImage, compares the entire
texture resource rather than the source and destination images/subresources:
forceCopySubImage = (source rectangle is out of bounds) &&
(sourceReadAttachment->getResource() != this);
Different mip levels share the resource pointer, so the zero-initializing fallback is skipped. The tested disclosure requires compatible GPU-allocation reuse; it does not claim arbitrary address selection or universal backend exposure.
VERSION
Chrome Version: 151.0.7922.173 stable
Operating System: Parrot Security 7.3 (Debian 13.5 base), Linux 7.0.13+parrot7-amd64, x86-64
Tested renderer: ANGLE Vulkan 1.4.348, Intel Arc/Meteor Lake, Mesa 26.1.2.
The launch used --use-angle=vulkan; no sandbox-disabling or GPU-sandbox-disabling flag was used.
On this host, a no-flag run selected ANGLE/OpenGL and did not disclose.
REPRODUCTION CASE
Attachment: poc.html (complete, self-contained PoC; SHA-256
c44c05fa794035ba046a048b25c8f107c890537d217a0bd262b1577118b72857).
-
Put
poc.htmlin a directory and start these two servers there:python3 -m http.server 8771 --bind 127.0.0.1 python3 -m http.server 8772 --bind 127.0.0.1 -
Launch a fresh Chrome profile:
profile=$(mktemp -d /tmp/chrome-copytex-mip.XXXXXX) google-chrome --user-data-dir="$profile" --no-first-run \ --no-default-browser-check --ozone-platform=x11 --use-angle=vulkan \ --window-size=720,620 --window-position=0,0 \ 'http://127.0.0.1:8771/poc.html' -
Wait for the title to become
done. The page prints the result as JSON.
The essential attacker-side trigger is:
// destination mip 1 is the complete 4x4 read framebuffer; mip 0 is redefined.
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, destination, 1);
gl.bindTexture(gl.TEXTURE_2D, destination);
gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 100, 100, 64, 64, 0);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, destination, 0);
gl.readPixels(0, 0, 64, 64, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
Observed in three separately launched fresh profiles:
{"exactMarkerObserved":true,"attackerResult":{"markerPixels":1024,"nonzeroBytes":4096,
"setupErrors":[],"copyErrors":[],"readErrors":[]}}
Expected: all 16,384 destination bytes are initialized to zero. Paired negative control: close
Chrome, use another fresh profile, and open
http://127.0.0.1:8771/poc.html?control=distinct. This changes only the source relationship to a
distinct texture object. Two of two control runs returned markerPixels: 0, nonzeroBytes: 0,
with no WebGL errors. Candidate reliability was 3/3; control reliability was 2/2.
This is a data-disclosure PoC, not a crash, so no ASAN trace or crash state applies.
CREDIT INFORMATION
Reporter credit: JonathanBouman