CVE-2026-9935
Overview
Background
- Robust resource initialization
- An ANGLE/WebGL security feature that guarantees newly-allocated texture/buffer storage is zero-cleared before it can be sampled or read, so a program never observes residual GPU memory. For performance the clear is deferred: new storage is marked
InitState::MayNeedInitand cleared just-in-time. - `mState.mInitState`
- The texture’s global initialization state, used by
Texture::ensureInitializedas a fast path: if it readsInitialized, the per-level clear loop is skipped entirely. - `ensureInitialized`
- The routine that actually performs the deferred zero-clear of any
MayNeedInitlevels; this is exactly what the patch now calls fromsyncState. - `FramebufferGL::shouldSyncStateBeforeCheckStatus`
- In the GL backend this returns true, so
glCheckFramebufferStatusforces attachmentsyncState, which is how a merely-dirty (parameter-changed) texture reachesTexture::syncStatewithout any draw/read.
Root Cause Analysis
The invariant robust-resource-init relies on is that mState.mInitState == Initialized must imply the underlying VRAM has actually been cleared. The vulnerable Texture::syncState broke this: after flushing dirty bits (mTexture->syncState(context, mDirtyBits, source); mDirtyBits.reset();) it executed mState.mInitState = InitState::Initialized; unconditionally. But a backend syncState only synchronizes state parameters (filters, swizzle, base level) — it does not perform the deferred zero-clear. So any code path that dirties a texture and then triggers syncState without a real initialization — most directly glCheckFramebufferStatus on a GL-backend FBO with that texture attached (via shouldSyncStateBeforeCheckStatus → syncAllDrawAttachmentState → Texture::syncState) — would flip the global init state to Initialized while the per-level ImageDesc states and the physical VRAM stayed uninitialized. A later draw sampling that texture hit ensureInitialized’s fast path, saw Initialized, and skipped the clear, sampling residual GPU memory.
The patch replaces the lie with the truth: syncState now calls ANGLE_TRY(ensureInitialized(context)); before flushing dirty bits and deletes the mState.mInitState = InitState::Initialized; assignment. Now syncing a dirty texture actually clears any MayNeedInit levels rather than merely asserting they are clean. Supporting changes: generateMipmap drops its now-redundant inline clear loop (since syncState initializes), renderer_utils.cpp marks multisample textures initialized after the backend clears them (an initialization invisible to the front-end), TextureGL.cpp broadens the blit-clear path, and Framebuffer.cpp drops a now-invalid ASSERT(drawBuffers.any()). New tests TextureAfterCheckStatus and SampleReinitSampleRGB verify a dirtied texture read back after glCheckFramebufferStatus, and a texture recreated with null data, both sample as cleared. The advisory labels this “Uninitialized Use” (CWE-457), which the diff substantiates; the realized security impact is cross-origin GPU-memory disclosure.
syncState asserted a texture was initialized as a side effect of flushing unrelated parameter dirty bits, without ever performing the deferred zero-clear — so a status check turned an uninitialized texture into one the init fast-path would skip.Attack Path
- Create a texture with lazy storage
In a WebGL/GLES context on the ANGLE GL backend, allocate
texImage2D(..., null)so storage isMayNeedInit(deferred clear). (inferred: reachable from WebGL; the diff proves the GL-backend sync path.) - Dirty a parameter
Call
texParameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST)so the texture has a dirty bit but is still uncleared. - Attach and check status
Attach the texture to an FBO and call
glCheckFramebufferStatus. In the GL backend this forcesTexture::syncState, which (pre-patch) marksmState.mInitState = Initializedwithout clearing. - Sample the poisoned texture
Bind the default framebuffer and draw a quad sampling the texture;
ensureInitializedseesInitialized, skips the clear, and the shader samples uninitialized VRAM. - Exfiltrate
glReadPixelsthe rendered result, reading back residual GPU memory (potentially cross-origin compositor tiles, video frames, or other contexts’ contents) — an information-disclosure primitive.
Impact Assessment
readPixels, defeating robust-resource-init and Site Isolation for GPU memory contents. No special privilege is needed beyond a WebGL context on an ANGLE GL-backend configuration; the precondition is that the texture be dirtied and routed through the GL-backend status-check sync path. The primitive is a read/disclosure of uninitialized (possibly cross-origin) VRAM — serious confidentiality loss but not, on its own, memory corruption or RCE (the low CVSS C:L is arguably understated for cross-origin leakage; classify from the diff as uninitialized-memory use yielding disclosure).Files Changed
src/libANGLE/Framebuffer.cppsrc/libANGLE/Texture.cppsrc/libANGLE/renderer/gl/TextureGL.cppsrc/libANGLE/renderer/renderer_utils.cppsrc/tests/angle_end2end_tests_expectations.txtsrc/tests/gl_tests/RobustResourceInitTest.cpp
Audit Directions
- Other `mInitState = Initialized` assignmentsGrep ANGLE for any place that sets a global/per-level init state to
Initializedwithout a corresponding clear (buffers, renderbuffers, image/EGLImage siblings). - Backend `shouldSyncStateBeforeCheckStatus` / status-check syncReview all callers that route
glCheckFramebufferStatus(and other non-draw operations) intoTexture::syncState, and whether other backends (Metal, D3D) share the fast-path assumption. - `ensureInitialized` fast-path consumersAudit every reader of
mState.mInitStatethat short-circuits per-level clears to confirm the global flag can no longer be set ahead of the physical clear. - `DIRTY_OBJECT_TEXTURES_INIT` corner casesThe commit notes
glTexImage2D(null)resetting an already-synced texture is still handled separately; audit that path and similar “already synced then invalidated” transitions.
Patch
From 6f0191582467d7163244badc45708a4560c510bf Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi <[email protected]> Date: Thu, 23 Apr 2026 09:38:04 -0400 Subject: [PATCH] Initialize textures during syncState Instead of marking the textures as initialized on syncState, actually initialize them. In most cases, this makes DIRTY_OBJECT_TEXTURES_INIT obsolete because textures needing initialization also get synced. However, this is not true for some corner cases, such as glTexImage2D with nullptr resetting a texture that is already synced. Bug: chromium:501584689 Change-Id: I88ab23f91fce6f3077a25303a9f352317f07bbe9 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7791058 Commit-Queue: Shahbaz Youssefi <[email protected]> Reviewed-by: Geoff Lang <[email protected]> --- diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp index 161f69f..cdfe0fc 100644 --- a/src/libANGLE/Framebuffer.cpp +++ b/src/libANGLE/Framebuffer.cpp @@ -2801,7 +2801,6 @@ switch (bufferType) { case GL_COLOR: - ASSERT(drawBuffers.any()); return partialClearNeedsInit(context, drawBuffers, false, false); case GL_DEPTH: return partialClearNeedsInit(context, {}, true, false); diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp index 102cffc..ae705c7 100644 --- a/src/libANGLE/Texture.cpp +++ b/src/libANGLE/Texture.cpp @@ -2013,24 +2013,6 @@ return angle::Result::Continue; } - // Clear the base image(s) immediately if needed - if (context->isRobustResourceInitEnabled()) - { - ImageIndexIterator it = - ImageIndexIterator::MakeGeneric(mState.mType, baseLevel, baseLevel + 1, - ImageIndex::kEntireLevel, ImageIndex::kEntireLevel); - while (it.hasNext()) - { - const ImageIndex index = it.next(); - const ImageDesc &desc = mState.getImageDesc(index.getTarget(), index.getLevelIndex()); - - if (desc.initState == InitState::MayNeedInit) - { - ANGLE_TRY(initializeContents(context, GL_NONE, index)); - } - } - } - ANGLE_TRY(syncState(context, Command::GenerateMipmap)); ANGLE_TRY(mTexture->generateMipmap(context)); @@ -2488,9 +2470,9 @@ angle::Result Texture::syncState(const Context *context, Command source) { ASSERT(hasAnyDirtyBit() || source == Command::GenerateMipmap); + ANGLE_TRY(ensureInitialized(context)); ANGLE_TRY(mTexture->syncState(context, mDirtyBits, source)); mDirtyBits.reset(); - mState.mInitState = InitState::Initialized; return angle::Result::Continue; } diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp index c91c6ac..dfaae06 100644 --- a/src/libANGLE/renderer/gl/TextureGL.cpp +++ b/src/libANGLE/renderer/gl/TextureGL.cpp @@ -2483,7 +2483,8 @@ GLenum nativeInternalFormat = getLevelInfo(imageIndex.getTarget(), imageIndex.getLevelIndex()).nativeInternalFormat; - if (features.allowClearForRobustResourceInit.enabled && + if ((features.allowClearForRobustResourceInit.enabled || + !nativegl::SupportsTexImage(getType())) && nativegl::SupportsNativeRendering(functions, mState.getType(), nativeInternalFormat)) { BlitGL *blitter = GetBlitGL(context); diff --git a/src/libANGLE/renderer/renderer_utils.cpp b/src/libANGLE/renderer/renderer_utils.cpp index facd04c..096732d 100644 --- a/src/libANGLE/renderer/renderer_utils.cpp +++ b/src/libANGLE/renderer/renderer_utils.cpp @@ -914,6 +914,8 @@ { // Call a specialized clear function to init a multisample texture. ANGLE_TRY(multisampleInitializer->initializeMultisampleTextureToBlack(context, t.get())); + // The above initialization is invisible to the front-end + t->setInitState(gl::InitState::Initialized); } else if (type == gl::TextureType::Buffer) { diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index 237a395..cc0f892 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt @@ -94,6 +94,7 @@ 42265226 VULKAN : GLSLTest_ES3.GLVertexIDIntegerTextureDrawElementsU8LineIds/* = SKIP 235877059 VULKAN : MultisampleTestES3.ResolveToFBO/ES3_Vulkan_EmulatedPrerotation180 = SKIP 235877059 VULKAN : MultisampleTestES3.ResolveToFBO/ES3_Vulkan_EmulatedPrerotation270 = SKIP +506165684 VULKAN : RobustResourceInitTest.SampleReinitSampleRGB/* = SKIP // ANGLE is broken if a context is made current before a surface is created in the presence of multiple queue families 42266900 VULKAN : EGLContextSharingTestNoFixture.*/*ForceDelayedDeviceCreationForTesting* = SKIP 42266900 VULKAN : EGLSurfacelessContextTest.*/*ForceDelayedDeviceCreationForTesting* = SKIP diff --git a/src/tests/gl_tests/RobustResourceInitTest.cpp b/src/tests/gl_tests/RobustResourceInitTest.cpp index 1e05ed0..6935603 100644 --- a/src/tests/gl_tests/RobustResourceInitTest.cpp +++ b/src/tests/gl_tests/RobustResourceInitTest.cpp @@ -1633,8 +1633,8 @@ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); } -// Basic test that textures are initialized correctly. -TEST_P(RobustResourceInitTest, Texture) +// Basic test that textures are initialized correctly. Verification is done via glReadPixels. +TEST_P(RobustResourceInitTest, TextureViaReadBack) { ANGLE_SKIP_TEST_IF(!hasGLExtension()); @@ -1653,6 +1653,58 @@ EXPECT_GL_TRUE(initState); } +// Basic test that textures are initialized correctly. Verification is done by sampling, after +// the texture is made dirty and glCheckFramebufferStatus on a framebuffer it is attached to. +TEST_P(RobustResourceInitTest, TextureAfterCheckStatus) +{ + ANGLE_SKIP_TEST_IF(!hasGLExtension()); + + GLTexture texture; + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + // Dirty the texture + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + GLFramebuffer framebuffer; + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + // Check framebuffer status. In the GL backend, this syncs the texture because it's dirty. + EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + // Unbind the framebuffer and sample from the texture. + glBindFramebuffer(GL_FRAMEBUFFER, 0); + ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); + drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.0f); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack); +} + +// Test that after a texture with data is sampled, recreating it with no data makes it cleared. +// Uses an RGB texture which may be emulated on some backends. +TEST_P(RobustResourceInitTest, SampleReinitSampleRGB) +{ + ANGLE_SKIP_TEST_IF(!hasGLExtension()); + + const std::vector<GLColorRGB> kInitData(kWidth * kHeight, GLColorRGB(255, 0, 0)); + + GLTexture texture; + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kWidth, kHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, + kInitData.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + // Draw once, the texture has data and should sample with data. The texture is also sync'ed at + // this step. + ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); + drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.0f); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); + + // Recreate the texture with no data. It should be cleared to black. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kWidth, kHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); + drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.0f); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black); +} + // Test that uploading texture data with an unpack state set correctly initializes the texture and // the data is uploaded correctly. TEST_P(RobustResourceInitTest, TextureWithUnpackState)
Regression Test / PoC
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index 237a395..cc0f892 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -94,6 +94,7 @@
42265226 VULKAN : GLSLTest_ES3.GLVertexIDIntegerTextureDrawElementsU8LineIds/* = SKIP
235877059 VULKAN : MultisampleTestES3.ResolveToFBO/ES3_Vulkan_EmulatedPrerotation180 = SKIP
235877059 VULKAN : MultisampleTestES3.ResolveToFBO/ES3_Vulkan_EmulatedPrerotation270 = SKIP
+506165684 VULKAN : RobustResourceInitTest.SampleReinitSampleRGB/* = SKIP
// ANGLE is broken if a context is made current before a surface is created in the presence of multiple queue families
42266900 VULKAN : EGLContextSharingTestNoFixture.*/*ForceDelayedDeviceCreationForTesting* = SKIP
42266900 VULKAN : EGLSurfacelessContextTest.*/*ForceDelayedDeviceCreationForTesting* = SKIP
diff --git a/src/tests/gl_tests/RobustResourceInitTest.cpp b/src/tests/gl_tests/RobustResourceInitTest.cpp
index 1e05ed0..6935603 100644
--- a/src/tests/gl_tests/RobustResourceInitTest.cpp
+++ b/src/tests/gl_tests/RobustResourceInitTest.cpp
@@ -1633,8 +1633,8 @@
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
-// Basic test that textures are initialized correctly.
-TEST_P(RobustResourceInitTest, Texture)
+// Basic test that textures are initialized correctly. Verification is done via glReadPixels.
+TEST_P(RobustResourceInitTest, TextureViaReadBack)
{
ANGLE_SKIP_TEST_IF(!hasGLExtension());
@@ -1653,6 +1653,58 @@
EXPECT_GL_TRUE(initState);
}
+// Basic test that textures are initialized correctly. Verification is done by sampling, after
+// the texture is made dirty and glCheckFramebufferStatus on a framebuffer it is attached to.
+TEST_P(RobustResourceInitTest, TextureAfterCheckStatus)
+{
+ ANGLE_SKIP_TEST_IF(!hasGLExtension());
+
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ // Dirty the texture
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+ GLFramebuffer framebuffer;
+ glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+ // Check framebuffer status. In the GL backend, this syncs the texture because it's dirty.
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Unbind the framebuffer and sample from the texture.
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
+ drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.0f);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
+}
+
+// Test that after a texture with data is sampled, recreating it with no data makes it cleared.
+// Uses an RGB texture which may be emulated on some backends.
+TEST_P(RobustResourceInitTest, SampleReinitSampleRGB)
+{
+ ANGLE_SKIP_TEST_IF(!hasGLExtension());
+
+ const std::vector<GLColorRGB> kInitData(kWidth * kHeight, GLColorRGB(255, 0, 0));
+
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kWidth, kHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
+ kInitData.data());
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+ // Draw once, the texture has data and should sample with data. The texture is also sync'ed at
+ // this step.
+ ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
+ drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.0f);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+
+ // Recreate the texture with no data. It should be cleared to black.
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kWidth, kHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
+ drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.0f);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
+}
+
// Test that uploading texture data with an unpack state set correctly initializes the texture and
// the data is uploaded correctly.
TEST_P(RobustResourceInitTest, TextureWithUnpackState)
Original Bug Report
Robust Resource Initialization Bypass in ANGLE GL backend via Framebuffer sync
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.
Overview: A logic error in ANGLE’s OpenGL backend potentially allows WebGL applications to bypass robust resource initialization and read uninitialized GPU memory. Calling glCheckFramebufferStatus on an FBO with a dirty texture attachment improperly sets the texture’s global state to initialized. This causes subsequent draw calls to skip clearing the texture, potentially disclosing cross-origin VRAM contents.
Affected files:
third_party/angle/src/libANGLE/Texture.cppthird_party/angle/src/libANGLE/Framebuffer.cppthird_party/angle/src/libANGLE/renderer/gl/FramebufferGL.cppthird_party/angle/src/libANGLE/renderer/gl/TextureGL.cppthird_party/angle/src/libANGLE/State.cppthird_party/angle/src/libANGLE/FramebufferAttachment.cpp
Estimated timestamp from git blame: 2021-08-16
Summary
A potential logic error in ANGLE’s OpenGL backend allows WebGL renderers to bypass robust resource initialization. This vulnerability can lead to the disclosure of uninitialized GPU memory (VRAM), which may contain sensitive cross-origin data such as compositor tiles, video frames, or canvas contents from other processes.
Technical Details
When robust_resource_initialization is enabled, ANGLE defers the zero-clearing of new textures for performance reasons. The texture is allocated but its state is marked as InitState::MayNeedInit. The actual clearing is performed just-in-time before a draw or read operation.
However, a bypass exists in how Framebuffer Object (FBO) status checks are handled in the GL backend. When glCheckFramebufferStatus is called, gl::Framebuffer::checkStatusImpl checks if backend synchronization is required. For the GL backend, rx::FramebufferGL::shouldSyncStateBeforeCheckStatus() unconditionally returns true.
This forces a call to syncAllDrawAttachmentState, which iterates through attachments. If an attached texture has any dirty bits (e.g., from a modified texture parameter), it calls gl::Texture::syncState (third_party/angle/src/libANGLE/Texture.cpp).
The vulnerability is located at the end of gl::Texture::syncState:
angle::Result Texture::syncState(const Context *context, Command source)
{
ASSERT(hasAnyDirtyBit() || source == Command::GenerateMipmap);
ANGLE_TRY(mTexture->syncState(context, mDirtyBits, source));
mDirtyBits.reset();
mState.mInitState = InitState::Initialized; // <-- Flaw
return angle::Result::Continue;
}
Here, mState.mInitState is unconditionally marked as Initialized. The GL backend’s syncState routine only synchronizes state parameters (like filters) and does not perform the robust resource clearing.
This creates a dangerous state mismatch: the texture’s global mState.mInitState claims the texture is clean, while the underlying per-level ImageDesc states and actual VRAM remain uninitialized. When the texture is subsequently bound as a sampler for a draw call, Texture::ensureInitialized uses the global mState.mInitState as a fast-path optimization, returns early, and completely skips the initialization step.
Potential Reproduction Steps
Note: These are suggested steps based on static analysis. Our tooling agent cannot dynamically run code to verify them.
- In a browser using the ANGLE GL backend, create a WebGL context.
- Create a WebGL texture and allocate empty storage:
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1024, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE, null). - Modify a texture parameter to set a dirty bit:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST). - Create a Framebuffer Object (FBO) and attach the texture to it.
- Call
gl.checkFramebufferStatus(gl.FRAMEBUFFER). This triggers the sync logic and improperly marks the texture as initialized. - Bind the default framebuffer (the canvas).
- Draw a full-screen quad using the poisoned texture as the source sampler. The shader will sample directly from uninitialized VRAM.
- Call
gl.readPixels()on the default canvas to capture the leaked VRAM contents.
Suggested Fix
Remove the unconditional mState.mInitState = InitState::Initialized; assignment from gl::Texture::syncState in third_party/angle/src/libANGLE/Texture.cpp.
Synchronizing dirty state bits (like texture parameters) should not implicitly fulfill the robust resource initialization requirements. If syncState needs to update the global initialization state, it should strictly re-derive it by checking the initialization status of all its underlying ImageDesc components.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.