High chrome OOB 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in GPU
DescriptionHeap buffer overflow in GPU
ComponentGPU
Bug ClassOOB
Tracker491732188
Fix commitc7ac96e8c670 (angle/angle) +191/-27
CISA KEVNot listed
Creditedinspector-ambitious
Disclosed2026-03-31

Files Changed

  • include/platform/autogen/FeaturesGL_autogen.h
  • include/platform/gl_features.json
  • src/libANGLE/renderer/gl/TextureGL.cpp
  • src/libANGLE/renderer/gl/TextureGL.h
From c7ac96e8c6704de4b0376ef9e802144a5d6560eb Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <[email protected]>
Date: Thu, 19 Mar 2026 15:30:18 -0400
Subject: [PATCH] GL: Workaround mesa bug with glGenerateMipmap

Bug: chromium:491732188
Change-Id: Ia3800f8f884394ae34ad943dc045184f71140860
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7684734
Commit-Queue: Shahbaz Youssefi <[email protected]>
Reviewed-by: Geoff Lang <[email protected]>
---

diff --git a/include/platform/autogen/FeaturesGL_autogen.h b/include/platform/autogen/FeaturesGL_autogen.h
index d04f3d5..bac58e9 100644
--- a/include/platform/autogen/FeaturesGL_autogen.h
+++ b/include/platform/autogen/FeaturesGL_autogen.h
@@ -674,6 +674,12 @@
         &members,
     };
 
+    FeatureInfo recreateMipmapLevelsBeforeGenerate = {
+        "recreateMipmapLevelsBeforeGenerate",
+        FeatureCategory::OpenGLWorkarounds,
+        &members,
+    };
+
     FeatureInfo limitMaxBufferSizeTo1gb = {
         "limitMaxBufferSizeTo1gb",
         FeatureCategory::OpenGLWorkarounds,
diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json
index c7bd3fb..41f6567 100644
--- a/include/platform/gl_features.json
+++ b/include/platform/gl_features.json
@@ -878,6 +878,15 @@
             "issue": "http://crbug.com/475877320"
         },
         {
+            "name": "recreate_mipmap_levels_before_generate",
+            "category": "Workarounds",
+            "description": [
+                "Before calling glGenerateMipmap on a mutable texture, recreate levels that the driver is ",
+                "supposed to recreate to work around driver bugs."
+            ],
+            "issue": "http://crbug.com/491732188"
+        },
+        {
             "name": "limit_max_buffer_size_to_1gb",
             "category": "Workarounds",
             "description": [
diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp
index 9a2c293..05299c9 100644
--- a/src/libANGLE/renderer/gl/TextureGL.cpp
+++ b/src/libANGLE/renderer/gl/TextureGL.cpp
@@ -1497,6 +1497,13 @@
     StateManagerGL *stateManager      = GetStateManagerGL(context);
     const angle::FeaturesGL &features = GetFeaturesGL(context);
 
+    bool recreateMipmapLevelsBeforeGenerate =
+        features.recreateMipmapLevelsBeforeGenerate.enabled && !mState.getImmutableFormat();
+    if (recreateMipmapLevelsBeforeGenerate)
+    {
+        ANGLE_TRY(allocateMipmapLevelsForGeneration(context));
+    }
+
     const GLuint effectiveBaseLevel = mState.getEffectiveBaseLevel();
     const GLuint maxLevel           = mState.getMipmapMaxLevel();
 
@@ -1513,34 +1520,19 @@
           nativegl::SupportsNativeRendering(functions, mState.getType(),
                                             baseLevelInfo.nativeInternalFormat))))
     {
-        nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat(
-            functions, features, baseLevelInternalFormat.internalFormat,
-            baseLevelInternalFormat.format, baseLevelInternalFormat.type);
-
         // Manually allocate the mip levels of this texture if they don't exist
-        GLuint levelCount = maxLevel - effectiveBaseLevel + 1;
-        for (GLuint levelIdx = 1; levelIdx < levelCount; levelIdx++)
+        // This might already be done above if recreateMipmapLevelsBeforeGenerate is in effect.
+        if (!recreateMipmapLevelsBeforeGenerate)
         {
-            gl::Extents levelSize(std::max(baseLevelDesc.size.width >> levelIdx, 1),
-                                  std::max(baseLevelDesc.size.height >> levelIdx, 1), 1);
-
-            const gl::ImageDesc &levelDesc =
-                mState.getImageDesc(gl::TextureTarget::_2D, effectiveBaseLevel + levelIdx);
-
-            if (levelDesc.size != levelSize || *levelDesc.format.info != baseLevelInternalFormat)
-            {
-                // Make sure no pixel unpack buffer is bound
-                stateManager->bindBuffer(gl::BufferBinding::PixelUnpack, 0);
-
-                ANGLE_GL_TRY_ALWAYS_CHECK(
-                    context, functions->texImage2D(
-                                 ToGLenum(getType()), effectiveBaseLevel + levelIdx,
-                                 texImageFormat.internalFormat, levelSize.width, levelSize.height,
-                                 0, texImageFormat.format, texImageFormat.type, nullptr));
-            }
+            ANGLE_TRY(allocateMipmapLevelsForGeneration(context));
         }
 
         // Use the blitter to generate the mips
+        const nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat(
+            functions, features, baseLevelInternalFormat.internalFormat,
+            baseLevelInternalFormat.format, baseLevelInternalFormat.type);
+        const GLuint levelCount = maxLevel - effectiveBaseLevel + 1;
+
         BlitGL *blitter = GetBlitGL(context);
         if (baseLevelInternalFormat.colorEncoding == GL_SRGB)
         {
@@ -1565,6 +1557,84 @@
     return angle::Result::Continue;
 }
 
+angle::Result TextureGL::allocateMipmapLevelsForGeneration(const gl::Context *context)
+{
+    const FunctionsGL *functions      = GetFunctionsGL(context);
+    StateManagerGL *stateManager      = GetStateManagerGL(context);
+    const angle::FeaturesGL &features = GetFeaturesGL(context);
+
+    const GLuint effectiveBaseLevel = mState.getEffectiveBaseLevel();
+    const GLuint maxLevel           = mState.getMipmapMaxLevel();
+
+    const gl::ImageDesc &baseLevelDesc                = mState.getBaseLevelDesc();
+    const gl::InternalFormat &baseLevelInternalFormat = *baseLevelDesc.format.info;
+
+    nativegl::TexImageFormat texImageFormat =
+        nativegl::GetTexImageFormat(functions, features, baseLevelInternalFormat.internalFormat,
+                                    baseLevelInternalFormat.format, baseLevelInternalFormat.type);
+
+    const bool is3D                = getType() == gl::TextureType::_3D;
+    const gl::TextureTarget target = getType() == gl::TextureType::CubeMap
+                                         ? gl::TextureTarget::CubeMapPositiveX
+                                         : NonCubeTextureTypeToTarget(getType());
+
+    // Manually allocate the mip levels of this texture if they don't exist
+    GLuint levelCount = maxLevel - effectiveBaseLevel + 1;
+    for (GLuint levelIdx = 1; levelIdx < levelCount; levelIdx++)
+    {
+        gl::Extents levelSize(
+            std::max(baseLevelDesc.size.width >> levelIdx, 1),
+            std::max(baseLevelDesc.size.height >> levelIdx, 1),
+            is3D ? std::max(baseLevelDesc.size.depth >> levelIdx, 1) : baseLevelDesc.size.depth);
+
+        const gl::ImageDesc &levelDesc = mState.getImageDesc(target, effectiveBaseLevel + levelIdx);
+
+        if (levelDesc.size != levelSize || *levelDesc.format.info != baseLevelInternalFormat)
+        {
+            // Make sure no pixel unpack buffer is bound
+            stateManager->bindBuffer(gl::BufferBinding::PixelUnpack, 0);
+
+            switch (getType())
+            {
+                case gl::TextureType::_2D:
+                    ANGLE_GL_TRY_ALWAYS_CHECK(
+                        context,
+                        functions->texImage2D(ToGLenum(getType()), effectiveBaseLevel + levelIdx,
+                                              texImageFormat.internalFormat, levelSize.width,
+                                              levelSize.height, 0, texImageFormat.format,
+                                              texImageFormat.type, nullptr));
+                    break;
+                case gl::TextureType::_3D:
+                case gl::TextureType::_2DArray:
+                case gl::TextureType::CubeMapArray:
+                    ANGLE_GL_TRY_ALWAYS_CHECK(
+                        context,
+                        functions->texImage3D(ToGLenum(getType()), effectiveBaseLevel + levelIdx,
+                                              texImageFormat.internalFormat, levelSize.width,
+                                              levelSize.height, levelSize.depth, 0,
+                                              texImageFormat.format, texImageFormat.type, nullptr));
+                    break;
+                case gl::TextureType::CubeMap:
+                    for (gl::TextureTarget face : gl::AllCubeFaceTextureTargets())
+                    {
+                        ANGLE_GL_TRY_ALWAYS_CHECK(
+                            context,
+                            functions->texImage2D(ToGLenum(face), effectiveBaseLevel + levelIdx,
+                                                  texImageFormat.internalFormat, levelSize.width,
+                                                  levelSize.height, 0, texImageFormat.format,
+                                                  texImageFormat.type, nullptr));
+                    }
+                    break;
+                default:
+                    // Cannot call glGenerateMipmap with any other texture type
+                    UNREACHABLE();
+                    break;
+            }
+        }
+    }
+    return angle::Result::Continue;
+}
+
 angle::Result TextureGL::clearImage(const gl::Context *context,
                                     GLint level,
                                     GLenum format,
diff --git a/src/libANGLE/renderer/gl/TextureGL.h b/src/libANGLE/renderer/gl/TextureGL.h
index 95af317..03f4711 100644
--- a/src/libANGLE/renderer/gl/TextureGL.h
+++ b/src/libANGLE/renderer/gl/TextureGL.h
@@ -267,6 +267,7 @@
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index aa9ac1f..71c19db 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -49,7 +49,6 @@
 493505759 PIXEL10 GLES : SRGBTextureTestES3.SRGBDecodeTexelFetchInHelper/* = SKIP
 493505759 PIXEL10 GLES : SRGBTextureTestES3.SRGBDecodeTexelFetchWithSamplerInStruct/* = SKIP
 
-
 // Advanced blend ops currently fail on Windows/Intel.
 376899587 WIN INTEL VULKAN : AdvancedBlendTest.*/* = SKIP
 
@@ -171,6 +170,7 @@
 42266866 WIN INTEL OPENGL : GLSLTest_ES3.LargeInterfaceBlockArray/* = SKIP
 42266866 WIN INTEL OPENGL : GLSLTest_ES3.LargeInterfaceBlockNestedArray/* = SKIP
 463961767 WIN INTEL OPENGL : EGLSurfacelessContextTest.Switcheroo/* = SKIP
+494350632 WIN INTEL OPENGL : MipmapTestES3.MismatchingLevelFormats/* = SKIP
 
 // Failures on Intel Alder Lake S UHD Graphics 770 (8086:4680-31.0.101.5333)
 352085732 WIN INTEL OPENGL : GeometryShaderTest.LayeredFramebufferClear2DArrayColor/* = SKIP
@@ -473,6 +473,8 @@
 464927523 METAL : GLSLTest_ES3_PackUnpackEmulation.PackSnorm2x16/* = SKIP
 464927523 METAL : GLSLTest_ES3_PackUnpackEmulation.PackUnorm2x16/* = SKIP
 42266214 MAC APPLE METAL : Texture2DTest.ManySupersedingTextureUpdates/* = SKIP
+494392011 MAC METAL : MipmapTestES3.MismatchingLevelFormats/* = SKIP
+494341324 MAC OPENGL : MipmapTestES3.MismatchingLevelFormats/* = SKIP
 
 // The workaround is not intended to be enabled in this configuration so
 // skip it as the failure is likely a driver bug.
diff --git a/src/tests/gl_tests/MipmapTest.cpp b/src/tests/gl_tests/MipmapTest.cpp
index ce2a53d..cdcc7e2 100644
--- a/src/tests/gl_tests/MipmapTest.cpp
+++ b/src/tests/gl_tests/MipmapTest.cpp
@@ -2470,12 +2470,76 @@
     }
 }
 
+// Test glGenerateMipmap in the presence of mismatching level formats.  Regression test for a bug in
+// mesa.
+TEST_P(MipmapTestES3, MismatchingLevelFormats)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_rgtc"));
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D, texture);
+
+    const std::vector<GLColor> kAllGreen(1000, GLColor::green);
+    const std::vector<GLColor> kAllBlue(1000, GLColor::blue);
+    const std::vector<GLColor> kAllCyan(1000, GLColor::cyan);
+
+    // Create mips at levels that should not be touched by glGenerateMipmap.
+    glTexImage2D(GL_TEXTURE_2D, 5, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, kAllCyan.data());
+    glTexImage2D(GL_TEXTURE_2D, 6, GL_RGBA, 20, 30, 0, GL_RGBA, GL_UNSIGNED_BYTE, kAllBlue.data());
+
+    // Create RGTC1 at level 1.  A 16x12 image has 4x3 blocks of 8 bytes each.
+    const std::vector<uint8_t> redRGTC1((16 / 4) * (12 / 4) * 8, 0xFF);
+    glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RED_RGTC1_EXT, 16, 12, 0,
+                           static_cast<GLsizei>(redRGTC1.size()), redRGTC1.data());
+
+    // Trigger mesa bug: After uploading a 4x4 image at level 0 and 1x2 at level 2, calling
+    // glGenerateMipmap silently gets nooped.  While ANGLE considers mip 1 now to be in RGBA format,
+    // mesa still thinks it's RGTC1 due to the noop.
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, kAllGreen.data());
+    glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 1, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, kAllBlue.data());
+    glGenerateMipmap(GL_TEXTURE_2D);
+    ASSERT_GL_NO_ERROR();
+
+    // Now dirty mesa's tracking again by reuploading to level 0, then call glGenerateMipmap on
+    // level 1, which ANGLE lets through because that mip is supposed to be RGBA now.  Mesa supports
+    // glGenerateMipmap on RGTC1 textures.  Where the bug is present, this glGenerateMipmap crashes
+    // by mistakenly processing levels 5 and 6 above (out of mipmap range).
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, kAllBlue.data());
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
+    glGenerateMipmap(GL_TEXTURE_2D);
+    ASSERT_GL_NO_ERROR();
+
+    // Verify correctness
+    ANGLE_GL_PROGRAM(verify, essl3_shaders::vs::Texture2DLod(), essl3_shaders::fs::Texture2DLod());
+    glUseProgram(verify);
+    const GLint lodLoc = glGetUniformLocation(verify, essl3_shaders::LodUniform());
+
+    glUniform1i(glGetUniformLocation(verify, essl3_shaders::Texture2DUniform()), 0);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
+
+    glUniform1f(lodLoc, 0);
+    drawQuad(verify, essl3_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
+
+    glUniform1f(lodLoc, 1);
+    drawQuad(verify, essl3_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+
+    glUniform1f(lodLoc, 2);
+    drawQuad(verify, essl3_shaders::PositionAttrib(), 0.5f);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these
 // tests should be run against.
 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(
     MipmapTest,
     ES2_METAL().disable(Feature::AllowGenMultipleMipsPerPass),
-    ES2_OPENGLES().enable(Feature::UseIntermediateTextureForGenerateMipmap));
+    ES2_OPENGL().enable(Feature::RecreateMipmapLevelsBeforeGenerate),
+    ES2_OPENGLES().enable(Feature::UseIntermediateTextureForGenerateMipmap),
+    ES2_OPENGLES()
+        .enable(Feature::UseIntermediateTextureForGenerateMipmap)
+        .enable(Feature::UseIntermediateTextureForGenerateMipmap));
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Mipmap3DBoxFilterTest);
 ANGLE_INSTANTIATE_TEST(Mipmap3DBoxFilterTest,
@@ -2483,7 +2547,13 @@
                        ES2_METAL().disable(Feature::AllowGenMultipleMipsPerPass));
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MipmapTestES3);
-ANGLE_INSTANTIATE_TEST_ES3_AND(MipmapTestES3, ES3_WEBGPU());
+ANGLE_INSTANTIATE_TEST_ES3_AND(MipmapTestES3,
+                               ES3_OPENGL().enable(Feature::RecreateMipmapLevelsBeforeGenerate),
+                               ES3_OPENGLES().enable(Feature::RecreateMipmapLevelsBeforeGenerate),
+                               ES3_WEBGPU());
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MipmapTestES31);
-ANGLE_INSTANTIATE_TEST_ES31(MipmapTestES31);
+ANGLE_INSTANTIATE_TEST_ES31_AND(
+    MipmapTestES31,
+    ES31_OPENGL().enable(Feature::RecreateMipmapLevelsBeforeGenerate),
+    ES31_OPENGLES().enable(Feature::RecreateMipmapLevelsBeforeGenerate));
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.