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 <syoussefi@chromium.org>
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 <syoussefi@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
---
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