Critical chrome UAF 🔧 Commit mapped

Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebGL
DescriptionUse after free in WebGL
ComponentWebGL
Bug ClassUAF
Tracker544163112
Fix commit87f5655006e4 (angle/angle) +132/-86
CISA KEVNot listed
CreditedLexi Groves (49016)
Disclosed2026-09-08

Background

ANGLE
Chrome’s translation layer that implements the WebGL/OpenGL ES API on top of native graphics backends, including the GL backend where this bug lives.
`TextureGL::setStorage()`
the ANGLE GL-backend routine that allocates immutable texture storage in response to glTexStorage3D-style calls.
`reattachTextureToFboAfterLayerIncrease`
an ANGLE feature flag whose workaround forces framebuffers to re-bind to a texture after its layer count grows and its backing storage is reallocated.
`TextureLayerCountIncreased`
a SubjectMessage broadcast to observers (such as an attached FBO) signaling that a _2DArray texture gained layers and must be re-attached to fresh storage.

Root Cause Analysis

When an FBO is attached to a layer of a gl::TextureType::_2DArray texture and the texture’s immutable storage is then (re)allocated through TextureGL::setStorage() with a larger size.depth (more array layers), the native GL driver allocates a new backing object, but the framebuffer attachment was left pointing at the old, now-freed storage. The pre-existing reattachTextureToFboAfterLayerIncrease workaround already emitted angle::SubjectMessage::TextureLayerCountIncreased on the mutable glTexImage3D path so observers would reattach, but setStorage() (the glTexStorage3D immutable path) never issued that notification, violating the invariant that any layer-count increase which reallocates storage must notify attached FBOs.

The fix mirrors the mutable-path logic inside setStorage(): it iterates the mip levels, compares the incoming size.depth against each gl::ImageDesc’s previous depth, and calls onStateChange(angle::SubjectMessage::TextureLayerCountIncreased) when depth grows, so the FBO reattaches to the new storage instead of dereferencing the stale one.

Key insight
The single mistake was that the FBO-reattachment workaround was implemented only for the mutable-storage code path and never applied to the immutable glTexStorage3D path, leaving attached framebuffers pointing at freed storage after a layer-count increase; the fix closes the gap by emitting the same TextureLayerCountIncreased notification from TextureGL::setStorage().

Attack Path

  1. Create and attach A page’s WebGL2 code creates a 2D_ARRAY texture, gives it storage, and attaches layer 0 of it to a framebuffer color attachment via glFramebufferTextureLayer.
  2. Reallocate via TexStorage3D The page calls glTexStorage3D (or otherwise re-drives setStorage()) with an increased depth, causing ANGLE to allocate new backing storage and free the old one without notifying the attached FBO.
  3. Dangle the attachment Because setStorage() never emitted TextureLayerCountIncreased, the framebuffer still references the freed storage, producing a use-after-free.
  4. Trigger the UAF The page renders to or samples from the framebuffer/texture, driving reads or writes through the dangling pointer to reclaimed GPU-process memory.

Impact Assessment

An attacker with a malicious WebGL2 page gains a use-after-free in Chrome’s GPU process, where ANGLE’s GL backend executes, potentially enabling controlled reads/writes into reclaimed memory and a path toward remote code execution in that process. Preconditions are only that the target uses the ANGLE GL backend with the affected code path and that the page can issue standard WebGL2 2D_ARRAY texture and framebuffer-layer calls; no special permissions are required beyond loading the page.

Files Changed

  • src/libANGLE/renderer/gl/TextureGL.cpp
  • src/tests/gl_tests/TextureTest.cpp

Audit Directions

  • Paired mutable/immutable paths
    When a workaround or invariant is added to a mutable resource-allocation path (glTexImage*), audit the immutable counterpart (glTexStorage*/setStorage) for the same notification or reattachment logic.
  • Reallocation without observer notification
    Flag any code that frees and reallocates backing storage but does not broadcast a SubjectMessage (e.g. ObjectReallocated, TextureLayerCountIncreased) to attachments such as FBOs.
  • Feature-flag-gated fixes
    Verify that security workarounds behind flags like reattachTextureToFboAfterLayerIncrease are consistently applied to every entry point that can trigger the vulnerable condition, not just the first one discovered.
From 87f5655006e4a66842c004a722440a643cc03c22 Mon Sep 17 00:00:00 2001
From: Zhenyao Mo <[email protected]>
Date: Tue, 18 Aug 2026 14:49:13 -0700
Subject: [PATCH] GL: Apply reattachTextureToFboAfterLayerIncrease to TexStorage3D

This adds the workaround logic inside TextureGL::setStorage() for cases
where an FBO is attached before the immutable storage is allocated. Also
updates the related tests to verify this with both TexImage3D and
TexStorage3D.

Bug: chromium:544163112
Change-Id: Ibb50ffeed6eaa369bed4ab25b625c16a1e63796c
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8266365
Reviewed-by: Kenneth Russell <[email protected]>
Reviewed-by: Geoff Lang <[email protected]>
Commit-Queue: Zhenyao Mo <[email protected]>
---

diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp
index be2ccee..45ea907 100644
--- a/src/libANGLE/renderer/gl/TextureGL.cpp
+++ b/src/libANGLE/renderer/gl/TextureGL.cpp
@@ -1300,6 +1300,20 @@
         onStateChange(angle::SubjectMessage::ObjectReallocated);
     }
 
+    if (features.reattachTextureToFboAfterLayerIncrease.enabled &&
+        getType() == gl::TextureType::_2DArray)
+    {
+        for (size_t level = 0; level < levels; level++)
+        {
+            const gl::ImageDesc &desc = mState.getImageDesc(gl::TextureTarget::_2DArray, level);
+            if (size.depth > desc.size.depth)
+            {
+                onStateChange(angle::SubjectMessage::TextureLayerCountIncreased);
+                break;
+            }
+        }
+    }
+
     const gl::InternalFormat &originalInternalFormatInfo =
         gl::GetSizedInternalFormatInfo(internalFormat);
     nativegl::TexStorageFormat texStorageFormat =
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index 3e6bc69..f391bdd 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -9363,54 +9363,70 @@
     // layer count without releasing storage.
     ANGLE_SKIP_TEST_IF(IsMetal());
 
-    glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    for (bool useTexStorage : {false, true})
+    {
+        glDeleteTextures(1, &m2DArrayTexture);
+        glGenTextures(1, &m2DArrayTexture);
 
-    std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
-    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                 pixelsRed.data());
-    ASSERT_GL_NO_ERROR();
+        glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 
-    GLFramebuffer fbo;
-    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
-    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
+        std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
+        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                     pixelsRed.data());
+        ASSERT_GL_NO_ERROR();
 
-    // Increase layer count to 2.
-    std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
-    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                 pixelsGreen.data());
-    ASSERT_GL_NO_ERROR();
+        GLFramebuffer fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
 
-    // Verify layer 0 points to the new memory (green).
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
 
-    // Verify layer 1 is also green.
-    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+        // Increase layer count to 2.
+        std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
+        if (useTexStorage)
+        {
+            glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 4, 4, 2);
+            glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 4, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE,
+                            pixelsGreen.data());
+        }
+        else
+        {
+            glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                         pixelsGreen.data());
+        }
+        ASSERT_GL_NO_ERROR();
 
-    // Clear layer 1 to blue and verify.
-    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
-    glClear(GL_COLOR_BUFFER_BIT);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
+        // Verify layer 0 points to the new memory (green).
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
 
-    // Now sample from layer 0 and layer 1 using a shader to ensure texture memory matches FBO
-    // memory.
-    glBindFramebuffer(GL_FRAMEBUFFER, 0);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
-    glUseProgram(mProgram);
-    glUniform1i(mTextureArrayLocation, 0);
+        // Verify layer 1 is also green.
+        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
 
-    // Verify layer 0 is green.
-    glUniform1i(mTextureArraySliceUniformLocation, 0);
-    drawQuad(mProgram, "position", 0.5f);
-    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
+        // Clear layer 1 to blue and verify.
+        glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+        glClear(GL_COLOR_BUFFER_BIT);
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
 
-    // Verify layer 1 is blue.
-    glUniform1i(mTextureArraySliceUniformLocation, 1);
-    drawQuad(mProgram, "position", 0.5f);
-    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::blue);
+        // Now sample from layer 0 and layer 1 using a shader to ensure texture memory matches FBO
+        // memory.
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
+        glUseProgram(mProgram);
+        glUniform1i(mTextureArrayLocation, 0);
+
+        // Verify layer 0 is green.
+        glUniform1i(mTextureArraySliceUniformLocation, 0);
+        drawQuad(mProgram, "position", 0.5f);
+        EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
+
+        // Verify layer 1 is blue.
+        glUniform1i(mTextureArraySliceUniformLocation, 1);
+        drawQuad(mProgram, "position", 0.5f);
+        EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::blue);
+    }
 }
 
 // Test increasing layer count of a 2D array texture when one of its layers is attached to a
@@ -9423,62 +9439,78 @@
     // layer count without releasing storage.
     ANGLE_SKIP_TEST_IF(IsMetal());
 
-    glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    for (bool useTexStorage : {false, true})
+    {
+        glDeleteTextures(1, &m2DArrayTexture);
+        glGenTextures(1, &m2DArrayTexture);
 
-    std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
-    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                 pixelsRed.data());
-    ASSERT_GL_NO_ERROR();
+        glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 
-    GLFramebuffer fbo;
-    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
-    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
+        std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
+        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                     pixelsRed.data());
+        ASSERT_GL_NO_ERROR();
 
-    // Set up and switch to a secondary context sharing resources with the current context.
-    EGLWindow *window          = getEGLWindow();
-    EGLDisplay display         = window->getDisplay();
-    EGLConfig config           = window->getConfig();
-    EGLSurface surface         = window->getSurface();
-    EGLint contextAttributes[] = {
-        EGL_CONTEXT_MAJOR_VERSION_KHR,
-        GetParam().majorVersion,
-        EGL_CONTEXT_MINOR_VERSION_KHR,
-        GetParam().minorVersion,
-        EGL_NONE,
-    };
-    EGLContext context1 = eglGetCurrentContext();
-    EGLContext context2 = eglCreateContext(display, config, context1, contextAttributes);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index 3e6bc69..f391bdd 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -9363,54 +9363,70 @@
     // layer count without releasing storage.
     ANGLE_SKIP_TEST_IF(IsMetal());
 
-    glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    for (bool useTexStorage : {false, true})
+    {
+        glDeleteTextures(1, &m2DArrayTexture);
+        glGenTextures(1, &m2DArrayTexture);
 
-    std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
-    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                 pixelsRed.data());
-    ASSERT_GL_NO_ERROR();
+        glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 
-    GLFramebuffer fbo;
-    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
-    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
+        std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
+        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                     pixelsRed.data());
+        ASSERT_GL_NO_ERROR();
 
-    // Increase layer count to 2.
-    std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
-    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                 pixelsGreen.data());
-    ASSERT_GL_NO_ERROR();
+        GLFramebuffer fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
 
-    // Verify layer 0 points to the new memory (green).
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
 
-    // Verify layer 1 is also green.
-    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+        // Increase layer count to 2.
+        std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
+        if (useTexStorage)
+        {
+            glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 4, 4, 2);
+            glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 4, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE,
+                            pixelsGreen.data());
+        }
+        else
+        {
+            glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                         pixelsGreen.data());
+        }
+        ASSERT_GL_NO_ERROR();
 
-    // Clear layer 1 to blue and verify.
-    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
-    glClear(GL_COLOR_BUFFER_BIT);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
+        // Verify layer 0 points to the new memory (green).
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
 
-    // Now sample from layer 0 and layer 1 using a shader to ensure texture memory matches FBO
-    // memory.
-    glBindFramebuffer(GL_FRAMEBUFFER, 0);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
-    glUseProgram(mProgram);
-    glUniform1i(mTextureArrayLocation, 0);
+        // Verify layer 1 is also green.
+        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
 
-    // Verify layer 0 is green.
-    glUniform1i(mTextureArraySliceUniformLocation, 0);
-    drawQuad(mProgram, "position", 0.5f);
-    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
+        // Clear layer 1 to blue and verify.
+        glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+        glClear(GL_COLOR_BUFFER_BIT);
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
 
-    // Verify layer 1 is blue.
-    glUniform1i(mTextureArraySliceUniformLocation, 1);
-    drawQuad(mProgram, "position", 0.5f);
-    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::blue);
+        // Now sample from layer 0 and layer 1 using a shader to ensure texture memory matches FBO
+        // memory.
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
+        glUseProgram(mProgram);
+        glUniform1i(mTextureArrayLocation, 0);
+
+        // Verify layer 0 is green.
+        glUniform1i(mTextureArraySliceUniformLocation, 0);
+        drawQuad(mProgram, "position", 0.5f);
+        EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
+
+        // Verify layer 1 is blue.
+        glUniform1i(mTextureArraySliceUniformLocation, 1);
+        drawQuad(mProgram, "position", 0.5f);
+        EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::blue);
+    }
 }
 
 // Test increasing layer count of a 2D array texture when one of its layers is attached to a
@@ -9423,62 +9439,78 @@
     // layer count without releasing storage.
     ANGLE_SKIP_TEST_IF(IsMetal());
 
-    glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    for (bool useTexStorage : {false, true})
+    {
+        glDeleteTextures(1, &m2DArrayTexture);
+        glGenTextures(1, &m2DArrayTexture);
 
-    std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
-    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                 pixelsRed.data());
-    ASSERT_GL_NO_ERROR();
+        glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 
-    GLFramebuffer fbo;
-    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
-    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
+        std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
+        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                     pixelsRed.data());
+        ASSERT_GL_NO_ERROR();
 
-    // Set up and switch to a secondary context sharing resources with the current context.
-    EGLWindow *window          = getEGLWindow();
-    EGLDisplay display         = window->getDisplay();
-    EGLConfig config           = window->getConfig();
-    EGLSurface surface         = window->getSurface();
-    EGLint contextAttributes[] = {
-        EGL_CONTEXT_MAJOR_VERSION_KHR,
-        GetParam().majorVersion,
-        EGL_CONTEXT_MINOR_VERSION_KHR,
-        GetParam().minorVersion,
-        EGL_NONE,
-    };
-    EGLContext context1 = eglGetCurrentContext();
-    EGLContext context2 = eglCreateContext(display, config, context1, contextAttributes);
-    ASSERT_NE(context2, EGL_NO_CONTEXT);
-    eglMakeCurrent(display, surface, surface, context2);
+        GLFramebuffer fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
 
-    // In the secondary context, bind the texture and increase layer count to 2.
-    glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
-    std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
-    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                 pixelsGreen.data());
-    ASSERT_GL_NO_ERROR();
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
 
-    // Switch back to the primary context.
-    eglMakeCurrent(display, surface, surface, context1);
+        // Set up and switch to a secondary context sharing resources with the current context.
+        EGLWindow *window          = getEGLWindow();
+        EGLDisplay display         = window->getDisplay();
+        EGLConfig config           = window->getConfig();
+        EGLSurface surface         = window->getSurface();
+        EGLint contextAttributes[] = {
+            EGL_CONTEXT_MAJOR_VERSION_KHR,
+            GetParam().majorVersion,
+            EGL_CONTEXT_MINOR_VERSION_KHR,
+            GetParam().minorVersion,
+            EGL_NONE,
+        };
+        EGLContext context1 = eglGetCurrentContext();
+        EGLContext context2 = eglCreateContext(display, config, context1, contextAttributes);
+        ASSERT_NE(context2, EGL_NO_CONTEXT);
+        eglMakeCurrent(display, surface, surface, context2);
 
-    // Verify layer 0 points to the new memory (green).
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+        // In the secondary context, bind the texture and increase layer count to 2.
+        glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+        std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
+        if (useTexStorage)
+        {
+            glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 4, 4, 2);
+            glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 4, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE,
+                            pixelsGreen.data());
+        }
+        else
+        {
+            glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                         pixelsGreen.data());
+        }
+        ASSERT_GL_NO_ERROR();
 
-    // Attach layer 1 to the FBO in context1 and verify. Explicitly calling
-    // glFramebufferTextureLayer attaches to the newly allocated texture storage.
-    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+        // Switch back to the primary context.
+        eglMakeCurrent(display, surface, surface, context1);
 
-    // Clear layer 1 to blue and verify.
-    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
-    glClear(GL_COLOR_BUFFER_BIT);
-    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
+        // Verify layer 0 points to the new memory (green).
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
 
-    // Clean up secondary context.
-    eglDestroyContext(display, context2);
+        // Attach layer 1 to the FBO in context1 and verify. Explicitly calling
+        // glFramebufferTextureLayer attaches to the newly allocated texture storage.
+        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+        // Clear layer 1 to blue and verify.
+        glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+        glClear(GL_COLOR_BUFFER_BIT);
+        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
+
+        // Clean up secondary context.
+        eglDestroyContext(display, context2);
+    }
 }
 
 // Create a 3D texture, use it, then redefine one level without changing dimensions.
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.