CVE-2026-102313
Overview
Background
- `glCopyTexImage2D` self-copy
- a GL command copying framebuffer pixels into a texture where the source framebuffer and destination texture are the same image at the same mip level, handled in ANGLE’s D3D11 backend by
handleCopyImageSelfCopyRedefine. - Robust resource initialization / WebGL
- modes (
isRobustResourceInitEnabled(),isWebGL()) that require newly created or resized texture storage to be deterministically zero-initialized so no stale GPU memory is observable. - `initializeContents`
- the ANGLE call that performs that zero-initialization for a texture image and clears the “needs init” state; the GL front-end otherwise marks the image initialized after a redefine.
- `ClipRectangle` / out-of-bounds source
- when the copy’s source rectangle lies fully outside the framebuffer, no pixels are copied, so the code redefines the destination and returns early.
Root Cause Analysis
handleCopyImageSelfCopyRedefine detected the case where the source rectangle is fully out of bounds of the framebuffer (nothing to copy), called redefineDest(destExtents) to resize the destination texture, and then returned angle::Result::Continue early. That early return skipped initializeContents(), so the newly (re)sized destination texture storage was left with stale or undefined contents, while the GL front-end nonetheless marked the image as initialized. This violates the robust-init invariant that any texture image the front-end treats as initialized has actually been zero-filled under robust resource init or WebGL.
The fix inserts, before the early return, a guarded call ANGLE_TRY(initializeContents(context, GL_NONE, destIndex)) when context->isWebGL() || context->isRobustResourceInitEnabled(), ensuring the fully-out-of-bounds self-copy zero-initializes the resized destination so its later-observable contents are defined; the corresponding Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf skip is removed from the D3D11 expectations.
redefineDest() that skipped initializeContents() for a fully out-of-bounds self-copy, leaving resized texture storage uninitialized while marked initialized; the fix calls initializeContents() before that return whenever robust init or WebGL is enabled.Attack Path
- Set up a self-copy target
In a WebGL/robust-init context on the D3D11 backend, create a texture bound as both the framebuffer source and the destination for
glCopyTexImage2Dat the same mip level. - Issue an out-of-bounds self-copy that resizes
Call
glCopyTexImage2Dwith a source rectangle fully outside the framebuffer and destination extents larger than the current image, reaching thehandleCopyImageSelfCopyRedefinebranch that redefines the destination and returns early. - Leave storage uninitialized but marked initialized
Pre-patch,
redefineDestresizes the destination whileinitializeContents()is skipped, so the new storage holds stale/undefined data yet the front-end considers it initialized. - Read back the stale contents Subsequently sample or read the destination texture (inferred) to observe uninitialized GPU memory, an information-disclosure primitive.
Impact Assessment
(inferred) and not shown in the diff, and no escalation beyond information disclosure is demonstrated.Files Changed
src/tests/angle_end2end_tests_expectations.txtsrc/libANGLE/renderer/d3d/TextureD3D.cpp
Audit Directions
- Early returns after resize/redefineLook for texture/buffer paths that resize or redefine storage and then return before the corresponding
initializeContents()/zero-fill, especially guarded by “nothing to copy/clip” conditions. - Front-end marks initialized vs. backend actually initializesAudit cases where the GL front-end sets an image initialized while a backend fast/early path can skip the actual initialization under robust-resource-init or WebGL.
- Copy/blit corner cases with out-of-bounds or empty regionsReview
CopyTexImage,CopySubImage, blit, and self-copy handlers for out-of-bounds, empty, or clipped-to-nothing rectangles that bypass initialization while altering storage dimensions.
Patch
From c2b3395143b5b0ae3f74bd120ad5e7f1fbd9318a Mon Sep 17 00:00:00 2001
From: Le Hoang Quyen <lehoangquyen@chromium.org>
Date: Thu, 10 Sep 2026 23:37:38 +0800
Subject: [PATCH] D3D11: fixing robust init for self copy same mip lvl
When performing a glCopyTexImage2D self-copy on the same mip level
with a source rectangle fully out of bounds of the framebuffer,
handleCopyImageSelfCopyRedefine checked ClipRectangle and returned
early after redefining the destination texture.
This early return bypassed the call to initializeContents(), leaving
the newly resized destination texture uninitialized with stale or
undefined contents while the front-end marked the image initialized.
This patch calls initializeContents() prior to the early return when
robust resource initialization or WebGL is enabled, ensuring that
fully out-of-bounds self-copies are properly zero-initialized. Also
removes the test skip expectation for the
Texture2DTestES3RobustInit.CopyTexImageOutOfBoundsSelf test on
Win D3D11.
Bug: chromium:556789073
Change-Id: I03b5f6c6f7fc15dfc4696808b35f9d5f8ed44195
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8387697
Auto-Submit: Quyen Le <lehoangquyen@google.com>
Commit-Queue: Quyen Le <lehoangquyen@google.com>
Reviewed-by: Kenneth Russell <kbr@chromium.org>
---
diff --git a/src/libANGLE/renderer/d3d/TextureD3D.cpp b/src/libANGLE/renderer/d3d/TextureD3D.cpp
index 2141213..fa1a434 100644
--- a/src/libANGLE/renderer/d3d/TextureD3D.cpp
+++ b/src/libANGLE/renderer/d3d/TextureD3D.cpp
@@ -134,6 +134,10 @@
{
// We won't be copying, but redefine the destination texture in case sourceArea is larger
ANGLE_TRY(redefineDest(destExtents));
+ if (context->isWebGL() || context->isRobustResourceInitEnabled())
+ {
+ ANGLE_TRY(initializeContents(context, GL_NONE, destIndex));
+ }
return angle::Result::Continue;
}
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index e0fd24e..ab192b7 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -644,7 +644,6 @@
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
Regression Test / PoC
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index e0fd24e..ab192b7 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt @@ -644,7 +644,6 @@ 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