Numen

Logo

CVE-2026–5283: Uninitialized GPU Memory Disclosure via Partial Clear in ANGLE (Chrome WebGL)

Summary

A vulnerability in ANGLE’s Framebuffer::partialClearNeedsInit() allows reading uninitialized GPU memory from GL_TEXTURE_2D_ARRAY textures. When glClear targets a single layer of an arrayed texture, ANGLE fails to zero-initialize the remaining layers but marks the entire texture as initialized, exposing stale GPU memory contents to web content via glReadPixels or shader sampling.

Affected Component

  • Filesrc/libANGLE/Framebuffer.cpp
  • FunctionFramebuffer::partialClearNeedsInit()
  • PatchCL 7703897

Root Cause

Vulnerable Code (Before Patch)

bool Framebuffer::partialClearNeedsInit(const Context *context,
                                        bool color,
                                        bool depth,
                                        bool stencil)
{
    if (!context->isRobustResourceInitEnabled() || mState.mResourceNeedsInit.none())
        return false;

    const auto &glState = context->getState();

    // Check 1: Scissor test partially overlaps the framebuffer
    if (glState.isScissorTestEnabled()) {
        // ... returns true if scissor doesn't cover full FB
    }

    // Check 2: Color channels masked
    if (color && glState.anyActiveDrawBufferChannelMasked())
        return true;

    // Check 3: Stencil values masked
    if (stencil && /* stencil mask check */)
        return true;

    // MISSING: No check for layered attachments (GL_TEXTURE_2D_ARRAY, etc.)

    return false;
}

The calling code in Framebuffer::clear():

if (partialClearNeedsInit(context, color, depth, stencil))
{
    ANGLE_TRY(ensureDrawAttachmentsInitialized(context));
}

// glClear only affects the currently bound layer,
// then marks the ENTIRE texture as "initialized"

The Bug

When a framebuffer attachment targets a single layer of a GL_TEXTURE_2D_ARRAY (bound via glFramebufferTextureLayer), glClear only writes to that specific layer. However, partialClearNeedsInit() does not account for layered attachments — it returns false, causing ensureDrawAttachmentsInitialized() to be skipped. After the clear completes, ANGLE marks the entire texture (all layers, all mips) as initialized, even though only one layer was actually written.

This leaves the remaining layers containing uninitialized GPU memory that can be read back via glReadPixels or sampled in a shader, bypassing the robust resource initialization security guarantee.

The patch adds layer detection logic to partialClearNeedsInit():

bool Framebuffer::partialClearNeedsInit(const Context *context,
                                        DrawBufferMask color,  // Changed: bool -> DrawBufferMask
                                        bool depth,
                                        bool stencil)
{
    // ... existing checks (scissor, color mask, stencil mask) ...

    // NEW: For layered attachments, treat this as a partial clear.
    // Otherwise the framebuffer clears some layers but marks the
    // entire mip level as initialized.
    if (depth && mState.mDepthAttachment.hasLayer())
        return true;

    if (stencil && mState.mStencilAttachment.hasLayer())
        return true;

    for (size_t colorIndex : color)
    {
        if (mState.mColorAttachments[colorIndex].hasLayer())
            return true;
    }

    return false;
}

The new hasLayer() method:

bool FramebufferAttachment::hasLayer() const
{
    return mTarget.textureIndex().hasLayer();
}

hasLayer() returns true when the attachment was created via glFramebufferTextureLayer() targeting a specific layer of a 2D array, 3D, or cube map texture. With this check in place, the clear is correctly identified as partial, and ensureDrawAttachmentsInitialized() is invoked to zero-initialize all layers before the clear proceeds.

The patch also changes the color parameter from bool to DrawBufferMask for more precise per-attachment tracking, and applies the same layer checks to partialBufferClearNeedsInit() (used by glClearBuffer*).

Reproduction

PoC

https://github.com/numencyber/Vulnerability_PoC/blob/main/CVE-2026-5283/poc.html

Share:

More Posts