Chrome · GPU
CVE-2026-5272
OOB in GPU
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
include/platform/autogen/FeaturesGL_autogen.hinclude/platform/gl_features.jsonsrc/libANGLE/renderer/gl/TextureGL.cppsrc/libANGLE/renderer/gl/TextureGL.h
Patch
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.
References
On This Page