Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper access control in ANGLE
DescriptionImproper access control in ANGLE
ComponentANGLE
Sub-componentPLS
Bug ClassLogic Error
Tracker523708661
Fix commit789ac8612946 (angle/angle) +150/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Files Changed

  • src/tests/gl_tests/PixelLocalStorageTest.cpp
  • extensions/ANGLE_shader_pixel_local_storage.txt
  • src/libANGLE/validationES.cpp
From 789ac86129465774fadbf0e9bb59a308f3065e36 Mon Sep 17 00:00:00 2001
From: Alexey Knyazev <[email protected]>
Date: Wed, 09 Sep 2026 00:00:00 +0000
Subject: [PATCH] PLS: Disallow EGL attachment redefinitions

EGLImageTarget* commands can resize the current
framebuffer's attachments while PLS is active.

To avoid undefined behavior caused by mismatching sizes
of PLS planes and framebuffer attachments, such command
calls now generate INVALID_OPERATION errors.

Fixed: chromium:523708661
Bug: angleproject:40096838
Change-Id: I1d3d30dbe1e8abd7d168b5bfc020f4345b596914
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8407626
Reviewed-by: Kenneth Russell <[email protected]>
Reviewed-by: Shahbaz Youssefi <[email protected]>
Commit-Queue: Shahbaz Youssefi <[email protected]>
---
diff --git a/extensions/ANGLE_shader_pixel_local_storage.txt b/extensions/ANGLE_shader_pixel_local_storage.txt
index d673501..c60c549 100644
--- a/extensions/ANGLE_shader_pixel_local_storage.txt
+++ b/extensions/ANGLE_shader_pixel_local_storage.txt
@@ -56,6 +56,8 @@
 
     This extension interacts with ANGLE_provoking_vertex.
 
+    This extension interacts with EXT_EGL_image_storage.
+
     This extension interacts with EXT_blend_func_extended.
 
     This extension interacts with EXT_clip_control.
@@ -78,6 +80,8 @@
 
     This extension interacts with NV_scissor_exclusive.
 
+    This extension interacts with OES_EGL_image.
+
     This extension interacts with OES_draw_buffers_indexed.
 
     This extension interacts with OES_sample_variables.
@@ -493,10 +497,10 @@
         e.g., TexSubImage*, TexParameter*, etc.
 
       * INVALID_OPERATION is generated by calls that would redefine a texture
-        or renderbuffer that is bound to the current framebuffer (either as an
-        attachment or as an active pixel local storage plane).
+        or renderbuffer that is bound to the current framebuffer as an
+        attachment.
 
-        e.g., RenderbufferStorage*, TexStorage*, TexImage*
+        e.g., RenderbufferStorage*, TexStorage*, TexImage*, EGLImageTarget*
 
       * INVALID_OPERATION is generated by Enable(), Disable() if <cap> is not
         one of: BLEND, CULL_FACE, DEBUG_OUTPUT, DEBUG_OUTPUT_SYNCHRONOUS,
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index 763d028..485538d 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -4786,6 +4786,12 @@
             ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kTextureIsImmutable);
             return false;
         }
+
+        if (ANGLE_UNLIKELY(!ValidateNotAttachmentWithActivePLS(context, entryPoint, texture->id())))
+        {
+            // Error already generated.
+            return false;
+        }
     }
 
     return ValidateEGLImageObject(context, entryPoint, targetPacked, imagePacked);
@@ -4806,12 +4812,18 @@
 
     // Renderbuffer is bound and can be redefined.
     {
-        Renderbuffer *renderbuffer = state.getCurrentRenderbuffer();
-        if (ANGLE_UNLIKELY(renderbuffer == nullptr))
+        const RenderbufferID id = context->getState().getRenderbufferId();
+        if (ANGLE_UNLIKELY(id.value == 0))
         {
             ANGLE_VALIDATION_ERROR(GL_INVALID_OPERATION, kRenderbufferNotBound);
             return false;
         }
+
+        if (ANGLE_UNLIKELY(!ValidateNotAttachmentWithActivePLS(context, entryPoint, id)))
+        {
+            // Error already generated.
+            return false;
+        }
     }
 
     // EGL image is valid and can be used as a renderbuffer.
diff --git a/src/tests/gl_tests/PixelLocalStorageTest.cpp b/src/tests/gl_tests/PixelLocalStorageTest.cpp
index 07f6c85..acdc0ad 100644
--- a/src/tests/gl_tests/PixelLocalStorageTest.cpp
+++ b/src/tests/gl_tests/PixelLocalStorageTest.cpp
@@ -4929,6 +4929,135 @@
     }
 }
 
+// Check that redefining textures or renderbuffers bound to the current framebuffer with
+// EGL sources while pixel local storage is active generates GL_INVALID_OPERATION.
+TEST_P(PixelLocalStorageTest, EGLRedefineBoundAttachmentsConflict)
+{
+    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
+
+    const EGLWindow *window = getEGLWindow();
+
+    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_OES_EGL_image"));
+    ANGLE_SKIP_TEST_IF(
+        !IsEGLDisplayExtensionEnabled(window->getDisplay(), "EGL_KHR_gl_texture_2D_image"));
+
+    // Support is implied by the extensions checked above.
+    ASSERT(IsEGLDisplayExtensionEnabled(window->getDisplay(), "EGL_KHR_image_base"));
+
+    // Create a source texture.
+    GLTexture eglSrcTex;
+    glBindTexture(GL_TEXTURE_2D, eglSrcTex);
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
+    ASSERT_GL_NO_ERROR();
+
+    // Create an EGL image from the texture.
+    constexpr EGLint attribs[] = {EGL_NONE};
+    const EGLImageKHR image    = eglCreateImageKHR(
+        window->getDisplay(), window->getContext(), EGL_GL_TEXTURE_2D_KHR,
+        (reinterpret_cast<EGLClientBuffer>(static_cast<size_t>(eglSrcTex))), attribs);
+    ASSERT_EGL_SUCCESS();
+
+    // Case 1: Texture attachment
+    {
+        GLTexture texFB;
+        glBindTexture(GL_TEXTURE_2D, texFB);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+        ASSERT_GL_NO_ERROR();
+
+        GLTexture texNonFB;
+        glBindTexture(GL_TEXTURE_2D, texNonFB);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+        ASSERT_GL_NO_ERROR();
+
+        GLFramebuffer fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texFB, 0);
+        ASSERT_GL_NO_ERROR();
+
+        GLTexture texPLS;
+        glBindTexture(GL_TEXTURE_2D, texPLS);
+        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
+        glFramebufferTexturePixelLocalStorageANGLE(0, texPLS, 0, 0, 0);
+        ASSERT_GL_NO_ERROR();
+
+        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
+        ASSERT_GL_NO_ERROR();
+
+        // Attempt to redefine texFB (bound to FB)
+        glBindTexture(GL_TEXTURE_2D, texFB);
+        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+        // Attempt to redefine texNonFB (NOT bound to FB)
+        glBindTexture(GL_TEXTURE_2D, texNonFB);
+        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+        EXPECT_GL_NO_ERROR();
+
+        if (EnsureGLExtensionEnabled("GL_EXT_EGL_image_storage"))
+        {
+            // Attempt to redefine texFB (bound to FB)
+            glBindTexture(GL_TEXTURE_2D, texFB);
+            glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, image, nullptr);
+            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+            // Attempt to redefine texNonFB (NOT bound to FB)
+            glBindTexture(GL_TEXTURE_2D, texNonFB);
+            glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, image, nullptr);
+            EXPECT_GL_NO_ERROR();
+        }
+
+        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
+        ASSERT_GL_NO_ERROR();
+    }
+
+    // Case 2: Renderbuffer attachment
+    {
+        GLRenderbuffer rboFB;
+        glBindRenderbuffer(GL_RENDERBUFFER, rboFB);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
+        ASSERT_GL_NO_ERROR();
+
+        GLRenderbuffer rboNonFB;
+        glBindRenderbuffer(GL_RENDERBUFFER, rboNonFB);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
+        ASSERT_GL_NO_ERROR();
+
+        GLFramebuffer fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboFB);
+        ASSERT_GL_NO_ERROR();
+
+        GLTexture texPLS;
+        glBindTexture(GL_TEXTURE_2D, texPLS);
+        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
+        glFramebufferTexturePixelLocalStorageANGLE(0, texPLS, 0, 0, 0);
+        ASSERT_GL_NO_ERROR();
+
+        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
+        ASSERT_GL_NO_ERROR();
+
+        // Attempt to redefine rboFB (bound to FB)
+        glBindRenderbuffer(GL_RENDERBUFFER, rboFB);
+        glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
+        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+        // Attempt to redefine rboNonFB (NOT bound to FB)
+        glBindRenderbuffer(GL_RENDERBUFFER, rboNonFB);
+        glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
+        EXPECT_GL_NO_ERROR();
+
+        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
+        ASSERT_GL_NO_ERROR();
+    }
+
+    // Clean up
+    eglDestroyImageKHR(window->getDisplay(), image);
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageTest);
 #define PLATFORM(API, BACKEND) API##_##BACKEND()
 #define PLS_INSTANTIATE_RENDERING_TEST_AND(TEST, API, ...)                                \
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/gl_tests/PixelLocalStorageTest.cpp b/src/tests/gl_tests/PixelLocalStorageTest.cpp
index 07f6c85..acdc0ad 100644
--- a/src/tests/gl_tests/PixelLocalStorageTest.cpp
+++ b/src/tests/gl_tests/PixelLocalStorageTest.cpp
@@ -4929,6 +4929,135 @@
     }
 }
 
+// Check that redefining textures or renderbuffers bound to the current framebuffer with
+// EGL sources while pixel local storage is active generates GL_INVALID_OPERATION.
+TEST_P(PixelLocalStorageTest, EGLRedefineBoundAttachmentsConflict)
+{
+    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
+
+    const EGLWindow *window = getEGLWindow();
+
+    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_OES_EGL_image"));
+    ANGLE_SKIP_TEST_IF(
+        !IsEGLDisplayExtensionEnabled(window->getDisplay(), "EGL_KHR_gl_texture_2D_image"));
+
+    // Support is implied by the extensions checked above.
+    ASSERT(IsEGLDisplayExtensionEnabled(window->getDisplay(), "EGL_KHR_image_base"));
+
+    // Create a source texture.
+    GLTexture eglSrcTex;
+    glBindTexture(GL_TEXTURE_2D, eglSrcTex);
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
+    ASSERT_GL_NO_ERROR();
+
+    // Create an EGL image from the texture.
+    constexpr EGLint attribs[] = {EGL_NONE};
+    const EGLImageKHR image    = eglCreateImageKHR(
+        window->getDisplay(), window->getContext(), EGL_GL_TEXTURE_2D_KHR,
+        (reinterpret_cast<EGLClientBuffer>(static_cast<size_t>(eglSrcTex))), attribs);
+    ASSERT_EGL_SUCCESS();
+
+    // Case 1: Texture attachment
+    {
+        GLTexture texFB;
+        glBindTexture(GL_TEXTURE_2D, texFB);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+        ASSERT_GL_NO_ERROR();
+
+        GLTexture texNonFB;
+        glBindTexture(GL_TEXTURE_2D, texNonFB);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+        ASSERT_GL_NO_ERROR();
+
+        GLFramebuffer fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texFB, 0);
+        ASSERT_GL_NO_ERROR();
+
+        GLTexture texPLS;
+        glBindTexture(GL_TEXTURE_2D, texPLS);
+        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
+        glFramebufferTexturePixelLocalStorageANGLE(0, texPLS, 0, 0, 0);
+        ASSERT_GL_NO_ERROR();
+
+        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
+        ASSERT_GL_NO_ERROR();
+
+        // Attempt to redefine texFB (bound to FB)
+        glBindTexture(GL_TEXTURE_2D, texFB);
+        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+        // Attempt to redefine texNonFB (NOT bound to FB)
+        glBindTexture(GL_TEXTURE_2D, texNonFB);
+        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+        EXPECT_GL_NO_ERROR();
+
+        if (EnsureGLExtensionEnabled("GL_EXT_EGL_image_storage"))
+        {
+            // Attempt to redefine texFB (bound to FB)
+            glBindTexture(GL_TEXTURE_2D, texFB);
+            glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, image, nullptr);
+            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+            // Attempt to redefine texNonFB (NOT bound to FB)
+            glBindTexture(GL_TEXTURE_2D, texNonFB);
+            glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, image, nullptr);
+            EXPECT_GL_NO_ERROR();
+        }
+
+        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
+        ASSERT_GL_NO_ERROR();
+    }
+
+    // Case 2: Renderbuffer attachment
+    {
+        GLRenderbuffer rboFB;
+        glBindRenderbuffer(GL_RENDERBUFFER, rboFB);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
+        ASSERT_GL_NO_ERROR();
+
+        GLRenderbuffer rboNonFB;
+        glBindRenderbuffer(GL_RENDERBUFFER, rboNonFB);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
+        ASSERT_GL_NO_ERROR();
+
+        GLFramebuffer fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboFB);
+        ASSERT_GL_NO_ERROR();
+
+        GLTexture texPLS;
+        glBindTexture(GL_TEXTURE_2D, texPLS);
+        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
+        glFramebufferTexturePixelLocalStorageANGLE(0, texPLS, 0, 0, 0);
+        ASSERT_GL_NO_ERROR();
+
+        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
+        ASSERT_GL_NO_ERROR();
+
+        // Attempt to redefine rboFB (bound to FB)
+        glBindRenderbuffer(GL_RENDERBUFFER, rboFB);
+        glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
+        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+        // Attempt to redefine rboNonFB (NOT bound to FB)
+        glBindRenderbuffer(GL_RENDERBUFFER, rboNonFB);
+        glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
+        EXPECT_GL_NO_ERROR();
+
+        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
+        ASSERT_GL_NO_ERROR();
+    }
+
+    // Clean up
+    eglDestroyImageKHR(window->getDisplay(), image);
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageTest);
 #define PLATFORM(API, BACKEND) API##_##BACKEND()
 #define PLS_INSTANTIATE_RENDERING_TEST_AND(TEST, API, ...)                                \
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.