# Static Code Evidence

## The Missing Call

Both `readPixelsRowByRow` and `readPixelsAllAtOnce` reset pack alignment before their internal
`glReadPixels` calls but never clear the `GL_PIXEL_PACK_BUFFER` binding.

---

## Vulnerable: `readPixelsRowByRow` (`FramebufferGL.cpp:1683`)

```cpp
gl::PixelPackState directPack;
directPack.alignment = 1;
ANGLE_TRY(stateManager->setPixelPackState(context, directPack));
// *** setPixelPackBuffer(context, nullptr) MISSING ***

GLubyte *readbackPixels = workaround.Pixels();  // heap address e.g. 0x6d3781cf9670
readbackPixels += skipBytes;
for (GLint y = area.y; y < area.y + area.height; ++y)
{
    ANGLE_GL_TRY(context,
                 functions->readPixels(area.x, y, area.width, 1, format, type, readbackPixels));
    // ^ If PBO bound: driver reads readbackPixels as a PBO byte offset → GPU OOB write
    readbackPixels += rowBytes;
}
```

---

## Vulnerable: `readPixelsAllAtOnce` — bulk branch (`FramebufferGL.cpp:1743`)

```cpp
if (height > 0)
{
    ANGLE_TRY(stateManager->setPixelPackState(context, pack));
    // *** setPixelPackBuffer(context, nullptr) MISSING ***
    ANGLE_GL_TRY(context, functions->readPixels(area.x, area.y, area.width, height, format,
                                                type, workaround.Pixels()));
    // ^ workaround.Pixels() = heap address → interpreted as PBO offset → GPU OOB write
}
```

---

## Vulnerable: `readPixelsAllAtOnce` — last-row branch (`FramebufferGL.cpp:1754`)

```cpp
if (readLastRowSeparately)
{
    gl::PixelPackState directPack;
    directPack.alignment = 1;
    ANGLE_TRY(stateManager->setPixelPackState(context, directPack));
    // *** setPixelPackBuffer(context, nullptr) MISSING ***

    GLubyte *readbackPixels = workaround.Pixels();
    readbackPixels += skipBytes + (area.height - 1) * rowBytes;
    ANGLE_GL_TRY(context, functions->readPixels(area.x, area.y + area.height - 1, area.width, 1,
                                                format, type, readbackPixels));
    // ^ same GPU OOB write
}
```

---

## Safe Reference: `BlitGL.cpp:862` (CVE-2026-6296 fix)

```cpp
gl::PixelPackState pack;
pack.alignment = 1;
ANGLE_TRY(mStateManager->setPixelPackState(context, pack));
ANGLE_TRY(mStateManager->setPixelPackBuffer(context, nullptr));  // <-- present here
ANGLE_GL_TRY(context, mFunctions->readPixels(readPixelsArea.x, readPixelsArea.y,
                                              readPixelsArea.width, readPixelsArea.height,
                                              readPixelsFormat, GL_UNSIGNED_BYTE, sourceMemory));
```

The CVE-2026-6296 fix explicitly added both `setPixelPackState` and `setPixelPackBuffer(nullptr)`
before the internal `readPixels`. The identical pattern is absent from all three call sites in
`FramebufferGL.cpp`.

---

## `setPixelPackState` Does Not Clear the Buffer Binding

`StateManagerGL.cpp:647–682`:

```cpp
angle::Result StateManagerGL::setPixelPackState(const gl::Context *context,
                                                const gl::PixelPackState &pack)
{
    if (mPackAlignment != pack.alignment)
    {
        mPackAlignment = pack.alignment;
        mFunctions->pixelStorei(GL_PACK_ALIGNMENT, mPackAlignment);
        mLocalDirtyBits.set(gl::State::DIRTY_BIT_PACK_STATE);
    }
    if (mPackRowLength != pack.rowLength)
    {
        mPackRowLength = pack.rowLength;
        mFunctions->pixelStorei(GL_PACK_ROW_LENGTH, mPackRowLength);
        mLocalDirtyBits.set(gl::State::DIRTY_BIT_PACK_STATE);
    }
    // ... (skipRows, skipPixels, reverseRowOrder) ...
    return angle::Result::Continue;
    // NOTE: GL_PIXEL_PACK_BUFFER binding is never touched here.
}
```

`StateManagerGL.cpp:685–693`:

```cpp
angle::Result StateManagerGL::setPixelPackBuffer(const gl::Context *context,
                                                 const gl::Buffer *pixelBuffer)
{
    GLuint bufferID = pixelBuffer ? GetImplAs<BufferGL>(pixelBuffer)->getBufferID() : 0;
    mFunctions->bindBuffer(GL_PIXEL_PACK_BUFFER, bufferID);
    // ...
    return angle::Result::Continue;
}
```

These are separate functions. Calling `setPixelPackState` without `setPixelPackBuffer`
leaves whatever buffer was previously bound in `GL_PIXEL_PACK_BUFFER` still active.

---

## Unified Diff (Proposed Fix)

```diff
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ readPixelsRowByRow @@
     gl::PixelPackState directPack;
     directPack.alignment = 1;
     ANGLE_TRY(stateManager->setPixelPackState(context, directPack));
+    ANGLE_TRY(stateManager->setPixelPackBuffer(context, nullptr));

@@ readPixelsAllAtOnce — bulk branch @@
     if (height > 0)
     {
         ANGLE_TRY(stateManager->setPixelPackState(context, pack));
+        ANGLE_TRY(stateManager->setPixelPackBuffer(context, nullptr));

@@ readPixelsAllAtOnce — last-row branch @@
         gl::PixelPackState directPack;
         directPack.alignment = 1;
         ANGLE_TRY(stateManager->setPixelPackState(context, directPack));
+        ANGLE_TRY(stateManager->setPixelPackBuffer(context, nullptr));
```
